aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2021-04-16 15:20:20 +0200
committerRon Evans <[email protected]>2021-04-21 10:32:09 +0200
commit7b761fac7805083dd0263ed376b10e106726ef8f (patch)
tree4a984aeda71f2b902ba14b1d2b6730f9d862187e
parentc47cdfa66fe38cfad268615e093969bb136bae07 (diff)
downloadtinygo-7b761fac7805083dd0263ed376b10e106726ef8f.tar.gz
tinygo-7b761fac7805083dd0263ed376b10e106726ef8f.zip
runtime: implement command line arguments in hosted environments
Implement command line arguments for Linux, MacOS and WASI.
-rw-r--r--interp/interpreter.go5
-rw-r--r--main_test.go24
-rw-r--r--src/runtime/runtime.go7
-rw-r--r--src/runtime/runtime_unix.go30
-rw-r--r--src/runtime/runtime_wasm_js.go5
-rw-r--r--src/runtime/runtime_wasm_wasi.go37
-rw-r--r--testdata/env.go9
-rw-r--r--testdata/env.txt3
8 files changed, 102 insertions, 18 deletions
diff --git a/interp/interpreter.go b/interp/interpreter.go
index 71be6957c..ec71c56e0 100644
--- a/interp/interpreter.go
+++ b/interp/interpreter.go
@@ -196,7 +196,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// which case this call won't even get to this point but will
// already be emitted in initAll.
continue
- case strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet":
+ case strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet" || callFn.name == "os.runtime_args":
// These functions should be run at runtime. Specifically:
// * Print and panic functions are best emitted directly without
// interpreting them, otherwise we get a ton of putchar (etc.)
@@ -204,6 +204,9 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// * runtime.hashmapGet tries to access the map value directly.
// This is not possible as the map value is treated as a special
// kind of object in this package.
+ // * os.runtime_args reads globals that are initialized outside
+ // the view of the interp package so it always needs to be run
+ // at runtime.
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
if err != nil {
return nil, mem, err
diff --git a/main_test.go b/main_test.go
index c9b02ba8c..224a71b0b 100644
--- a/main_test.go
+++ b/main_test.go
@@ -127,7 +127,7 @@ func TestCompiler(t *testing.T) {
t.Parallel()
runTestWithConfig("stdlib.go", "", t, &compileopts.Options{
Opt: "1",
- }, nil)
+ }, nil, nil)
})
// Test with only the bare minimum of optimizations enabled.
@@ -136,7 +136,7 @@ func TestCompiler(t *testing.T) {
t.Parallel()
runTestWithConfig("print.go", "", t, &compileopts.Options{
Opt: "0",
- }, nil)
+ }, nil, nil)
})
t.Run("ldflags", func(t *testing.T) {
@@ -148,7 +148,7 @@ func TestCompiler(t *testing.T) {
"someGlobal": "foobar",
},
},
- }, nil)
+ }, nil, nil)
})
})
}
@@ -160,17 +160,17 @@ func runPlatTests(target string, tests []string, t *testing.T) {
name := name // redefine to avoid race condition
t.Run(name, func(t *testing.T) {
t.Parallel()
- runTest(name, target, t, nil)
+ runTest(name, target, t, nil, nil)
})
}
if target == "wasi" || target == "" {
t.Run("filesystem.go", func(t *testing.T) {
t.Parallel()
- runTest("filesystem.go", target, t, nil)
+ runTest("filesystem.go", target, t, nil, nil)
})
t.Run("env.go", func(t *testing.T) {
t.Parallel()
- runTest("env.go", target, t, []string{"ENV1=VALUE1", "ENV2=VALUE2"})
+ runTest("env.go", target, t, []string{"first", "second"}, []string{"ENV1=VALUE1", "ENV2=VALUE2"})
})
}
}
@@ -187,7 +187,7 @@ func runBuild(src, out string, opts *compileopts.Options) error {
return Build(src, out, opts)
}
-func runTest(name, target string, t *testing.T, environmentVars []string) {
+func runTest(name, target string, t *testing.T, cmdArgs, environmentVars []string) {
options := &compileopts.Options{
Target: target,
Opt: "z",
@@ -198,10 +198,10 @@ func runTest(name, target string, t *testing.T, environmentVars []string) {
PrintSizes: "",
WasmAbi: "",
}
- runTestWithConfig(name, target, t, options, environmentVars)
+ runTestWithConfig(name, target, t, options, cmdArgs, environmentVars)
}
-func runTestWithConfig(name, target string, t *testing.T, options *compileopts.Options, environmentVars []string) {
+func runTestWithConfig(name, target string, t *testing.T, options *compileopts.Options, cmdArgs, environmentVars []string) {
// Get the expected output for this test.
// Note: not using filepath.Join as it strips the path separator at the end
// of the path.
@@ -244,6 +244,7 @@ func runTestWithConfig(name, target string, t *testing.T, options *compileopts.O
if target == "" {
cmd = exec.Command(binary)
cmd.Env = append(cmd.Env, environmentVars...)
+ cmd.Args = append(cmd.Args, cmdArgs...)
} else {
spec, err := compileopts.LoadTarget(target)
if err != nil {
@@ -257,11 +258,12 @@ func runTestWithConfig(name, target string, t *testing.T, options *compileopts.O
}
if len(spec.Emulator) != 0 && spec.Emulator[0] == "wasmtime" {
+ // Allow reading from the current directory.
+ cmd.Args = append(cmd.Args, "--dir=.")
for _, v := range environmentVars {
cmd.Args = append(cmd.Args, "--env", v)
}
- // Allow reading from the current directory.
- cmd.Args = append(cmd.Args, "--dir=.")
+ cmd.Args = append(cmd.Args, cmdArgs...)
} else {
cmd.Env = append(cmd.Env, environmentVars...)
}
diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go
index 1bc09ce0a..7b1b4f5ae 100644
--- a/src/runtime/runtime.go
+++ b/src/runtime/runtime.go
@@ -23,7 +23,9 @@ func GOROOT() string {
return "/usr/local/go"
}
-// TODO: fill with real args.
+// This is the default set of arguments, if nothing else has been set.
+// This may be overriden by modifying this global at runtime init (for example,
+// on Linux where there are real command line arguments).
var args = []string{"/proc/self/exe"}
//go:linkname os_runtime_args os.runtime_args
@@ -47,6 +49,9 @@ func memmove(dst, src unsafe.Pointer, size uintptr)
// llvm.memset.p0i8.i32(ptr, 0, size, false).
func memzero(ptr unsafe.Pointer, size uintptr)
+//export strlen
+func strlen(ptr unsafe.Pointer) uintptr
+
// Compare two same-size buffers for equality.
func memequal(x, y unsafe.Pointer, n uintptr) bool {
for i := uintptr(0); i < n; i++ {
diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go
index c0a0ec63c..dc6d74856 100644
--- a/src/runtime/runtime_unix.go
+++ b/src/runtime/runtime_unix.go
@@ -43,9 +43,34 @@ func postinit() {}
// Entry point for Go. Initialize all packages and call main.main().
//export main
-func main() int {
+func main(argc int32, argv *unsafe.Pointer) int {
preinit()
+ // Make args global big enough so that it can store all command line
+ // arguments. Unfortunately this has to be done with some magic as the heap
+ // is not yet initialized.
+ argsSlice := (*struct {
+ ptr unsafe.Pointer
+ len uintptr
+ cap uintptr
+ })(unsafe.Pointer(&args))
+ argsSlice.ptr = malloc(uintptr(argc) * (unsafe.Sizeof(uintptr(0))) * 3)
+ argsSlice.len = 0
+ argsSlice.cap = uintptr(argc)
+
+ // Initialize command line parameters.
+ for *argv != nil {
+ // Convert the C string to a Go string.
+ length := strlen(*argv)
+ argString := _string{
+ length: length,
+ ptr: (*byte)(*argv),
+ }
+ args = append(args, *(*string)(unsafe.Pointer(&argString)))
+ // This is the Go equivalent of "argc++" in C.
+ argv = (*unsafe.Pointer)(unsafe.Pointer(uintptr(unsafe.Pointer(argv)) + unsafe.Sizeof(argv)))
+ }
+
// Obtain the initial stack pointer right before calling the run() function.
// The run function has been moved to a separate (non-inlined) function so
// that the correct stack pointer is read.
@@ -65,9 +90,6 @@ func runMain() {
//go:extern environ
var environ *unsafe.Pointer
-//export strlen
-func strlen(ptr unsafe.Pointer) uintptr
-
//go:linkname syscall_runtime_envs syscall.runtime_envs
func syscall_runtime_envs() []string {
// Count how many environment variables there are.
diff --git a/src/runtime/runtime_wasm_js.go b/src/runtime/runtime_wasm_js.go
index ea1cc6e41..5e2eb1f51 100644
--- a/src/runtime/runtime_wasm_js.go
+++ b/src/runtime/runtime_wasm_js.go
@@ -15,6 +15,11 @@ func _start() {
run()
}
+//go:linkname syscall_runtime_envs syscall.runtime_envs
+func syscall_runtime_envs() []string {
+ return nil
+}
+
var handleEvent func()
//go:linkname setEventHandler syscall/js.setEventHandler
diff --git a/src/runtime/runtime_wasm_wasi.go b/src/runtime/runtime_wasm_wasi.go
index 97659a10d..02fa02601 100644
--- a/src/runtime/runtime_wasm_wasi.go
+++ b/src/runtime/runtime_wasm_wasi.go
@@ -21,6 +21,33 @@ func _start() {
run()
}
+// Read the command line arguments from WASI.
+// For example, they can be passed to a program with wasmtime like this:
+//
+// wasmtime ./program.wasm arg1 arg2
+func init() {
+ // 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)
+
+ // 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))
+ }
+}
+
func ticksToNanoseconds(ticks timeUnit) int64 {
return int64(ticks)
}
@@ -62,7 +89,15 @@ func ticks() timeUnit {
return timeUnit(nano)
}
-// Implementations of wasi_unstable APIs
+// Implementations of WASI APIs
+
+//go:wasm-module wasi_snapshot_preview1
+//export args_get
+func args_get(argv *unsafe.Pointer, argv_buf unsafe.Pointer) (errno uint16)
+
+//go:wasm-module wasi_snapshot_preview1
+//export args_sizes_get
+func args_sizes_get(argc *uint32, argv_buf_size *uint32) (errno uint16)
//go:wasm-module wasi_snapshot_preview1
//export clock_time_get
diff --git a/testdata/env.go b/testdata/env.go
index 74d271946..115da6d3d 100644
--- a/testdata/env.go
+++ b/testdata/env.go
@@ -5,10 +5,19 @@ import (
)
func main() {
+ // Check for environment variables (set by the test runner).
println("ENV1:", os.Getenv("ENV1"))
v, ok := os.LookupEnv("ENV2")
if !ok {
println("ENV2 not found")
}
println("ENV2:", v)
+
+ // Check for command line arguments.
+ // Argument 0 is skipped because it is the program name, which varies by
+ // test run.
+ println()
+ for _, arg := range os.Args[1:] {
+ println("arg:", arg)
+ }
}
diff --git a/testdata/env.txt b/testdata/env.txt
index 7aadfb7b4..8ba50a7fd 100644
--- a/testdata/env.txt
+++ b/testdata/env.txt
@@ -1,2 +1,5 @@
ENV1: VALUE1
ENV2: VALUE2
+
+arg: first
+arg: second