runtime: implement command line arguments in hosted environments
Implement command line arguments for Linux, MacOS and WASI.
Этот коммит содержится в:
родитель
c47cdfa66f
коммит
7b761fac78
8 изменённых файлов: 102 добавлений и 18 удалений
|
@ -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
|
// which case this call won't even get to this point but will
|
||||||
// already be emitted in initAll.
|
// already be emitted in initAll.
|
||||||
continue
|
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:
|
// These functions should be run at runtime. Specifically:
|
||||||
// * Print and panic functions are best emitted directly without
|
// * Print and panic functions are best emitted directly without
|
||||||
// interpreting them, otherwise we get a ton of putchar (etc.)
|
// 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.
|
// * runtime.hashmapGet tries to access the map value directly.
|
||||||
// This is not possible as the map value is treated as a special
|
// This is not possible as the map value is treated as a special
|
||||||
// kind of object in this package.
|
// 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)
|
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, mem, err
|
return nil, mem, err
|
||||||
|
|
24
main_test.go
24
main_test.go
|
@ -127,7 +127,7 @@ func TestCompiler(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
runTestWithConfig("stdlib.go", "", t, &compileopts.Options{
|
runTestWithConfig("stdlib.go", "", t, &compileopts.Options{
|
||||||
Opt: "1",
|
Opt: "1",
|
||||||
}, nil)
|
}, nil, nil)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Test with only the bare minimum of optimizations enabled.
|
// Test with only the bare minimum of optimizations enabled.
|
||||||
|
@ -136,7 +136,7 @@ func TestCompiler(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
runTestWithConfig("print.go", "", t, &compileopts.Options{
|
runTestWithConfig("print.go", "", t, &compileopts.Options{
|
||||||
Opt: "0",
|
Opt: "0",
|
||||||
}, nil)
|
}, nil, nil)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("ldflags", func(t *testing.T) {
|
t.Run("ldflags", func(t *testing.T) {
|
||||||
|
@ -148,7 +148,7 @@ func TestCompiler(t *testing.T) {
|
||||||
"someGlobal": "foobar",
|
"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
|
name := name // redefine to avoid race condition
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
runTest(name, target, t, nil)
|
runTest(name, target, t, nil, nil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if target == "wasi" || target == "" {
|
if target == "wasi" || target == "" {
|
||||||
t.Run("filesystem.go", func(t *testing.T) {
|
t.Run("filesystem.go", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
runTest("filesystem.go", target, t, nil)
|
runTest("filesystem.go", target, t, nil, nil)
|
||||||
})
|
})
|
||||||
t.Run("env.go", func(t *testing.T) {
|
t.Run("env.go", func(t *testing.T) {
|
||||||
t.Parallel()
|
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)
|
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{
|
options := &compileopts.Options{
|
||||||
Target: target,
|
Target: target,
|
||||||
Opt: "z",
|
Opt: "z",
|
||||||
|
@ -198,10 +198,10 @@ func runTest(name, target string, t *testing.T, environmentVars []string) {
|
||||||
PrintSizes: "",
|
PrintSizes: "",
|
||||||
WasmAbi: "",
|
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.
|
// Get the expected output for this test.
|
||||||
// Note: not using filepath.Join as it strips the path separator at the end
|
// Note: not using filepath.Join as it strips the path separator at the end
|
||||||
// of the path.
|
// of the path.
|
||||||
|
@ -244,6 +244,7 @@ func runTestWithConfig(name, target string, t *testing.T, options *compileopts.O
|
||||||
if target == "" {
|
if target == "" {
|
||||||
cmd = exec.Command(binary)
|
cmd = exec.Command(binary)
|
||||||
cmd.Env = append(cmd.Env, environmentVars...)
|
cmd.Env = append(cmd.Env, environmentVars...)
|
||||||
|
cmd.Args = append(cmd.Args, cmdArgs...)
|
||||||
} else {
|
} else {
|
||||||
spec, err := compileopts.LoadTarget(target)
|
spec, err := compileopts.LoadTarget(target)
|
||||||
if err != nil {
|
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" {
|
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 {
|
for _, v := range environmentVars {
|
||||||
cmd.Args = append(cmd.Args, "--env", v)
|
cmd.Args = append(cmd.Args, "--env", v)
|
||||||
}
|
}
|
||||||
// Allow reading from the current directory.
|
cmd.Args = append(cmd.Args, cmdArgs...)
|
||||||
cmd.Args = append(cmd.Args, "--dir=.")
|
|
||||||
} else {
|
} else {
|
||||||
cmd.Env = append(cmd.Env, environmentVars...)
|
cmd.Env = append(cmd.Env, environmentVars...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,9 @@ func GOROOT() string {
|
||||||
return "/usr/local/go"
|
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"}
|
var args = []string{"/proc/self/exe"}
|
||||||
|
|
||||||
//go:linkname os_runtime_args os.runtime_args
|
//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).
|
// llvm.memset.p0i8.i32(ptr, 0, size, false).
|
||||||
func memzero(ptr unsafe.Pointer, size uintptr)
|
func memzero(ptr unsafe.Pointer, size uintptr)
|
||||||
|
|
||||||
|
//export strlen
|
||||||
|
func strlen(ptr unsafe.Pointer) uintptr
|
||||||
|
|
||||||
// Compare two same-size buffers for equality.
|
// Compare two same-size buffers for equality.
|
||||||
func memequal(x, y unsafe.Pointer, n uintptr) bool {
|
func memequal(x, y unsafe.Pointer, n uintptr) bool {
|
||||||
for i := uintptr(0); i < n; i++ {
|
for i := uintptr(0); i < n; i++ {
|
||||||
|
|
|
@ -43,9 +43,34 @@ func postinit() {}
|
||||||
|
|
||||||
// Entry point for Go. Initialize all packages and call main.main().
|
// Entry point for Go. Initialize all packages and call main.main().
|
||||||
//export main
|
//export main
|
||||||
func main() int {
|
func main(argc int32, argv *unsafe.Pointer) int {
|
||||||
preinit()
|
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.
|
// Obtain the initial stack pointer right before calling the run() function.
|
||||||
// The run function has been moved to a separate (non-inlined) function so
|
// The run function has been moved to a separate (non-inlined) function so
|
||||||
// that the correct stack pointer is read.
|
// that the correct stack pointer is read.
|
||||||
|
@ -65,9 +90,6 @@ func runMain() {
|
||||||
//go:extern environ
|
//go:extern environ
|
||||||
var environ *unsafe.Pointer
|
var environ *unsafe.Pointer
|
||||||
|
|
||||||
//export strlen
|
|
||||||
func strlen(ptr unsafe.Pointer) uintptr
|
|
||||||
|
|
||||||
//go:linkname syscall_runtime_envs syscall.runtime_envs
|
//go:linkname syscall_runtime_envs syscall.runtime_envs
|
||||||
func syscall_runtime_envs() []string {
|
func syscall_runtime_envs() []string {
|
||||||
// Count how many environment variables there are.
|
// Count how many environment variables there are.
|
||||||
|
|
|
@ -15,6 +15,11 @@ func _start() {
|
||||||
run()
|
run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:linkname syscall_runtime_envs syscall.runtime_envs
|
||||||
|
func syscall_runtime_envs() []string {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var handleEvent func()
|
var handleEvent func()
|
||||||
|
|
||||||
//go:linkname setEventHandler syscall/js.setEventHandler
|
//go:linkname setEventHandler syscall/js.setEventHandler
|
||||||
|
|
|
@ -21,6 +21,33 @@ func _start() {
|
||||||
run()
|
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 {
|
func ticksToNanoseconds(ticks timeUnit) int64 {
|
||||||
return int64(ticks)
|
return int64(ticks)
|
||||||
}
|
}
|
||||||
|
@ -62,7 +89,15 @@ func ticks() timeUnit {
|
||||||
return timeUnit(nano)
|
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
|
//go:wasm-module wasi_snapshot_preview1
|
||||||
//export clock_time_get
|
//export clock_time_get
|
||||||
|
|
9
testdata/env.go
предоставленный
9
testdata/env.go
предоставленный
|
@ -5,10 +5,19 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// Check for environment variables (set by the test runner).
|
||||||
println("ENV1:", os.Getenv("ENV1"))
|
println("ENV1:", os.Getenv("ENV1"))
|
||||||
v, ok := os.LookupEnv("ENV2")
|
v, ok := os.LookupEnv("ENV2")
|
||||||
if !ok {
|
if !ok {
|
||||||
println("ENV2 not found")
|
println("ENV2 not found")
|
||||||
}
|
}
|
||||||
println("ENV2:", v)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
3
testdata/env.txt
предоставленный
3
testdata/env.txt
предоставленный
|
@ -1,2 +1,5 @@
|
||||||
ENV1: VALUE1
|
ENV1: VALUE1
|
||||||
ENV2: VALUE2
|
ENV2: VALUE2
|
||||||
|
|
||||||
|
arg: first
|
||||||
|
arg: second
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче