Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion interp/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,14 +172,17 @@ 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.)
// calls.
// * 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
Expand Down
26 changes: 14 additions & 12 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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) {
Expand All @@ -148,7 +148,7 @@ func TestCompiler(t *testing.T) {
"someGlobal": "foobar",
},
},
}, nil)
}, nil, nil)
})
})
}
Expand All @@ -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 == "" && runtime.GOOS == "darwin" {
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"})
})
}
}
Expand All @@ -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",
Expand All @@ -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.
Expand Down Expand Up @@ -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 {
Expand All @@ -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...)
}
Expand Down
12 changes: 6 additions & 6 deletions src/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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++ {
Expand Down Expand Up @@ -89,8 +94,3 @@ func AdjustTimeOffset(offset int64) {
func os_sigpipe() {
runtimePanic("too many writes on closed pipe")
}

//go:linkname syscall_runtime_envs syscall.runtime_envs
func syscall_runtime_envs() []string {
return nil
}
58 changes: 57 additions & 1 deletion src/runtime/runtime_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -62,6 +87,37 @@ func runMain() {
run()
}

//go:extern environ
var environ *unsafe.Pointer

//go:linkname syscall_runtime_envs syscall.runtime_envs
func syscall_runtime_envs() []string {
// Count how many environment variables there are.
env := environ
numEnvs := 0
for *env != nil {
numEnvs++
env = (*unsafe.Pointer)(unsafe.Pointer(uintptr(unsafe.Pointer(env)) + unsafe.Sizeof(environ)))
}

// Create a string slice of all environment variables.
// This requires just a single heap allocation.
env = environ
envs := make([]string, 0, numEnvs)
for *env != nil {
ptr := *env
length := strlen(ptr)
s := _string{
ptr: (*byte)(ptr),
length: length,
}
envs = append(envs, *(*string)(unsafe.Pointer(&s)))
env = (*unsafe.Pointer)(unsafe.Pointer(uintptr(unsafe.Pointer(env)) + unsafe.Sizeof(environ)))
}

return envs
}

func putchar(c byte) {
_putchar(int(c))
}
Expand Down
5 changes: 5 additions & 0 deletions src/runtime/runtime_wasm_js.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 36 additions & 1 deletion src/runtime/runtime_wasm_wasi.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
Expand Down
15 changes: 12 additions & 3 deletions testdata/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,19 @@ import (
)

func main() {
println(os.Getenv("ENV1"))
// Check for environment variables (set by the test runner).
println("ENV1:", os.Getenv("ENV1"))
v, ok := os.LookupEnv("ENV2")
if !ok {
panic("ENV2 not found")
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)
}
println(v)
}
7 changes: 5 additions & 2 deletions testdata/env.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
VALUE1
VALUE2
ENV1: VALUE1
ENV2: VALUE2

arg: first
arg: second