mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-03 10:37:46 +00:00
wasm: use wasip1 API for parameter/env passing
Instead of hardcoding the command line parameters and the environment variables in the binary, pass them at runtime to Node.js and use the WASIp1 API to retrieve them in wasm_exec.js. The only real benefit right now is that it becomes possible to change `go.argv` and `go.env` before running a wasm binary. This also changes the syscall package for GOOS=js: it now becomes more like a libc (using wasi-libc), which means error values like `syscall.EEXIST` will actually match the one returned by the relevant libc function. Wasm binary size for packages that import the os package will be increased somewhat.
This commit is contained in:
@@ -770,12 +770,11 @@ func Run(pkgName string, options *compileopts.Options, cmdArgs []string) error {
|
||||
// for the given emulator.
|
||||
func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, cmdArgs, environmentVars []string, timeout time.Duration, run func(cmd *exec.Cmd, result builder.BuildResult) error) (builder.BuildResult, error) {
|
||||
// Determine whether we're on a system that supports environment variables
|
||||
// and command line parameters (operating systems, WASI) or not (baremetal,
|
||||
// WebAssembly in the browser). If we're on a system without an environment,
|
||||
// we need to pass command line arguments and environment variables through
|
||||
// global variables (built into the binary directly) instead of the
|
||||
// conventional way.
|
||||
needsEnvInVars := config.GOOS() == "js"
|
||||
// and command line parameters (operating systems, WASI) or not (baremetal).
|
||||
// If we're on a system without an environment, we need to pass command line
|
||||
// arguments and environment variables through global variables (built into
|
||||
// the binary directly) instead of the conventional way.
|
||||
needsEnvInVars := false
|
||||
for _, tag := range config.BuildTags() {
|
||||
if tag == "baremetal" {
|
||||
needsEnvInVars = true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build baremetal || js || wasm_unknown
|
||||
//go:build baremetal || wasm_unknown
|
||||
|
||||
package runtime
|
||||
|
||||
|
||||
@@ -29,6 +29,44 @@ func proc_exit(exitcode uint32)
|
||||
//export __stdio_exit
|
||||
func __stdio_exit()
|
||||
|
||||
var args []string
|
||||
|
||||
//go:linkname os_runtime_args os.runtime_args
|
||||
func os_runtime_args() []string {
|
||||
if args == nil {
|
||||
// Read the number of args (argc) and the buffer size required to store
|
||||
// all these args (argv).
|
||||
var argc, argv_buf_size uint32
|
||||
args_sizes_get(&argc, &argv_buf_size)
|
||||
if argc == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Obtain the command line arguments
|
||||
argsSlice := make([]unsafe.Pointer, argc)
|
||||
buf := make([]byte, argv_buf_size)
|
||||
args_get(&argsSlice[0], unsafe.Pointer(&buf[0]))
|
||||
|
||||
// Convert the array of C strings to an array of Go strings.
|
||||
args = make([]string, argc)
|
||||
for i, cstr := range argsSlice {
|
||||
length := strlen(cstr)
|
||||
argString := _string{
|
||||
length: length,
|
||||
ptr: (*byte)(cstr),
|
||||
}
|
||||
args[i] = *(*string)(unsafe.Pointer(&argString))
|
||||
}
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
//go:wasmimport wasi_snapshot_preview1 args_get
|
||||
func args_get(argv *unsafe.Pointer, argv_buf unsafe.Pointer) (errno uint16)
|
||||
|
||||
//go:wasmimport wasi_snapshot_preview1 args_sizes_get
|
||||
func args_sizes_get(argc *uint32, argv_buf_size *uint32) (errno uint16)
|
||||
|
||||
const (
|
||||
putcharBufferSize = 120
|
||||
stdout = 1
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type timeUnit int64
|
||||
|
||||
// libc constructors
|
||||
@@ -21,38 +17,6 @@ func init() {
|
||||
__wasm_call_ctors()
|
||||
}
|
||||
|
||||
var args []string
|
||||
|
||||
//go:linkname os_runtime_args os.runtime_args
|
||||
func os_runtime_args() []string {
|
||||
if args == nil {
|
||||
// Read the number of args (argc) and the buffer size required to store
|
||||
// all these args (argv).
|
||||
var argc, argv_buf_size uint32
|
||||
args_sizes_get(&argc, &argv_buf_size)
|
||||
if argc == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Obtain the command line arguments
|
||||
argsSlice := make([]unsafe.Pointer, argc)
|
||||
buf := make([]byte, argv_buf_size)
|
||||
args_get(&argsSlice[0], unsafe.Pointer(&buf[0]))
|
||||
|
||||
// Convert the array of C strings to an array of Go strings.
|
||||
args = make([]string, argc)
|
||||
for i, cstr := range argsSlice {
|
||||
length := strlen(cstr)
|
||||
argString := _string{
|
||||
length: length,
|
||||
ptr: (*byte)(cstr),
|
||||
}
|
||||
args[i] = *(*string)(unsafe.Pointer(&argString))
|
||||
}
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func ticksToNanoseconds(ticks timeUnit) int64 {
|
||||
return int64(ticks)
|
||||
}
|
||||
@@ -97,12 +61,6 @@ func beforeExit() {
|
||||
|
||||
// Implementations of WASI APIs
|
||||
|
||||
//go:wasmimport wasi_snapshot_preview1 args_get
|
||||
func args_get(argv *unsafe.Pointer, argv_buf unsafe.Pointer) (errno uint16)
|
||||
|
||||
//go:wasmimport wasi_snapshot_preview1 args_sizes_get
|
||||
func args_sizes_get(argc *uint32, argv_buf_size *uint32) (errno uint16)
|
||||
|
||||
//go:wasmimport wasi_snapshot_preview1 clock_time_get
|
||||
func clock_time_get(clockid uint32, precision uint64, time *uint64) (errno uint16)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build nintendoswitch || wasip1
|
||||
//go:build js || nintendoswitch || wasip1
|
||||
|
||||
package syscall
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !wasip1 && !wasip2
|
||||
//go:build !js && !wasip1 && !wasip2
|
||||
|
||||
package syscall
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build wasip1
|
||||
//go:build js || wasip1
|
||||
|
||||
package syscall
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build nintendoswitch || wasip1 || wasip2
|
||||
//go:build js || nintendoswitch || wasip1 || wasip2
|
||||
|
||||
package syscall
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build wasip1 || wasip2
|
||||
//go:build js || wasip1 || wasip2
|
||||
|
||||
package syscall
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build baremetal || js || wasm_unknown
|
||||
//go:build baremetal || wasm_unknown
|
||||
|
||||
package syscall
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build baremetal || nintendoswitch || js || wasm_unknown
|
||||
//go:build baremetal || nintendoswitch || wasm_unknown
|
||||
|
||||
package syscall
|
||||
|
||||
|
||||
+54
-1
@@ -135,6 +135,8 @@
|
||||
|
||||
global.Go = class {
|
||||
constructor() {
|
||||
this.argv = ["js"];
|
||||
this.env = {};
|
||||
this._callbackTimeouts = new Map();
|
||||
this._nextCallbackTimeoutID = 1;
|
||||
|
||||
@@ -235,9 +237,58 @@
|
||||
return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr, len));
|
||||
}
|
||||
|
||||
const storeStringArraySizes = (array, num_ptr, buf_size_ptr) => {
|
||||
let buf_size = 0;
|
||||
for (let s of array) {
|
||||
buf_size += s.length + 1;
|
||||
}
|
||||
mem().setUint32(num_ptr, array.length, true);
|
||||
mem().setUint32(buf_size_ptr, buf_size, true);
|
||||
}
|
||||
|
||||
const storeStringArray = (array, ptrs_ptr, buf_ptr) => {
|
||||
for (let s of array) {
|
||||
// Put string data in buffer.
|
||||
let data = encoder.encode(s);
|
||||
let dest = new Uint8Array(this._inst.exports.memory.buffer, buf_ptr, data.length);
|
||||
dest.set(data);
|
||||
mem().setUint8(buf_ptr+data.length, 0);
|
||||
|
||||
// Put pointer to buffer in pointers array.
|
||||
mem().setUint32(ptrs_ptr, buf_ptr, true);
|
||||
|
||||
// Advance to the next element in the array.
|
||||
ptrs_ptr += 4;
|
||||
buf_ptr += data.length + 1;
|
||||
}
|
||||
}
|
||||
|
||||
const envArray = () => {
|
||||
let array = [];
|
||||
for (let [key, value] of Object.entries(this.env)) {
|
||||
array.push(`${key}=${value}`);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
const timeOrigin = Date.now() - performance.now();
|
||||
this.importObject = {
|
||||
wasi_snapshot_preview1: {
|
||||
args_sizes_get: (argc_ptr, argv_buf_size_ptr) => {
|
||||
storeStringArraySizes(this.argv, argc_ptr, argv_buf_size_ptr);
|
||||
return 0;
|
||||
},
|
||||
args_get: (argv_ptr, argv_buf_ptr) => {
|
||||
storeStringArray(this.argv, argv_ptr, argv_buf_ptr);
|
||||
return 0;
|
||||
},
|
||||
environ_get: (env_ptr, env_buf_ptr) => {
|
||||
storeStringArray(envArray(), env_ptr, env_buf_ptr);
|
||||
},
|
||||
environ_sizes_get: (env_ptr, env_buf_size_ptr) => {
|
||||
storeStringArraySizes(envArray(), env_ptr, env_buf_size_ptr);
|
||||
return 0;
|
||||
},
|
||||
// https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_write
|
||||
fd_write: function(fd, iovs_ptr, iovs_len, nwritten_ptr) {
|
||||
let nwritten = 0;
|
||||
@@ -504,12 +555,14 @@
|
||||
global.process.versions &&
|
||||
!global.process.versions.electron
|
||||
) {
|
||||
if (process.argv.length != 3) {
|
||||
if (process.argv.length < 3) {
|
||||
console.error("usage: go_js_wasm_exec [wasm binary] [arguments]");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const go = new Go();
|
||||
go.argv = process.argv.slice(2);
|
||||
go.env = process.env;
|
||||
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
|
||||
return go.run(result.instance);
|
||||
}).catch((err) => {
|
||||
|
||||
Reference in New Issue
Block a user