Repository: tetratelabs/wazero
Pull request #255 (2022-02-17 09:22:52)

wasi: Implement proc_exit
Will do rebase and update the docs after the refactoring PR is merged


nullpo-head: now rebasing..!
nullpo-head: rebase is done. for your awareness @mathetake 

Patch:
diff --git a/internal/wasi/wasi.go b/internal/wasi/wasi.go
index 82ffc886..453b2e27 100644
--- a/internal/wasi/wasi.go
+++ b/internal/wasi/wasi.go
@@ -112,9 +112,22 @@ const (
 	FunctionPathSymlink          = "path_symlink"
 	FunctionPathUnlinkFile       = "path_unlink_file"
 	FunctionPollOneoff           = "poll_oneoff"
-	FunctionProcExit             = "proc_exit"
-	FunctionProcRaise            = "proc_raise"
-	FunctionSchedYield           = "sched_yield"
+
+	// ProcExit terminates the execution of the module with an exit code.
+	// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#proc_exit
+	FunctionProcExit = "proc_exit"
+
+	// ImportProcExit is the WebAssembly 1.0 (MVP) Text format import of ProcExit
+	//
+	// See ImportProcExit
+	// See API.ProcExit
+	// See FunctionProcExit
+	// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#proc_exit
+	ImportProcExit = `(import "wasi_snapshot_preview1" "proc_exit"
+    (func $wasi.proc_exit (param $rval i32)))`
+
+	FunctionProcRaise  = "proc_raise"
+	FunctionSchedYield = "sched_yield"
 
 	// FunctionRandomGet write random data in buffer
 	// See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-random_getbuf-pointeru8-buf_len-size---errno
@@ -314,7 +327,20 @@ type SnapshotPreview1 interface {
 	// TODO: PathSymlink
 	// TODO: PathUnlinkFile
 	// TODO: PollOneoff
-	// TODO: ProcExit
+
+	// ProcExit is the WASI function that terminates the execution of the module with an exit code.
+	// An exit code of 0 indicates successful termination. The meanings of other values are not defined by WASI.
+	//
+	// * rval - The exit code.
+	//
+	// In wazero, if ProcExit is called, the calling function returns immediately, returning the given exit code as the error.
+	// You can get the exit code by casting the error to wasi.ExitCode.
+	// See wasi.ExitCode
+	//
+	// Note: ImportProcExit shows this signature in the WebAssembly 1.0 (MVP) Text Format.
+	// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#proc_exit
+	ProcExit(ctx wasm.HostFunctionCallContext, rval uint32)
+
 	// TODO: ProcRaise
 	// TODO: SchedYield
 
@@ -401,7 +427,7 @@ func SnapshotPreview1Functions(opts ...Option) (a *wasiAPI, nameToGoFunc map[str
 		// TODO: FunctionPathSymlink
 		// TODO: FunctionPathUnlinkFile
 		// TODO: FunctionPollOneoff
-		FunctionProcExit: proc_exit,
+		FunctionProcExit: a.ProcExit,
 		// TODO: FunctionProcRaise
 		// TODO: FunctionSchedYield
 		FunctionRandomGet: a.RandomGet,
@@ -479,6 +505,13 @@ func (a *wasiAPI) ClockTimeGet(ctx wasm.HostFunctionCallContext, id uint32, prec
 	return wasi.ErrnoSuccess
 }
 
+// ProcExit implements API.ProcExit
+func (a *wasiAPI) ProcExit(ctx wasm.HostFunctionCallContext, exitCode uint32) {
+	// Panic in a host function is caught by the engines, and the value of the panic is returned as the error of the CallFunction.
+	// See the document of API.ProcExit.
+	panic(wasi.ExitCode(exitCode))
+}
+
 type fileEntry struct {
 	path    string
 	fileSys wasi.FS
@@ -774,10 +807,6 @@ func (a *wasiAPI) RandomGet(ctx wasm.HostFunctionCallContext, buf uint32, bufLen
 	return wasi.ErrnoSuccess
 }
 
-func proc_exit(wasm.HostFunctionCallContext, uint32) {
-	// TODO: implement
-}
-
 func ValidateWASICommand(module *internalwasm.Module, moduleName string) error {
 	if start, err := requireExport(module, moduleName, FunctionStart, internalwasm.ExportKindFunc); err != nil {
 		return err
diff --git a/wasi/wasi.go b/wasi/wasi.go
index 9fd5f9d6..54c2508b 100644
--- a/wasi/wasi.go
+++ b/wasi/wasi.go
@@ -293,3 +293,24 @@ var errnoToString = [...]string{
 	ErrnoXdev:           "EXDEV",
 	ErrnoNotcapable:     "ENOTCAPABLE",
 }
+
+// ExitCode is an arbitrary uint32 number returned by proc_exit.
+// An exit code of 0 indicates successful termination. The meanings of other values are not defined by WASI.
+//
+// In wazero, if ProcExit is called, the calling function returns immediately, returning the given exit code as the error.
+// You can get the exit code by casting the error as follows.
+//
+//   wasmFunction := m.GetFunctionVoidReturn(/* omitted */)  // Some function which may call proc_exit
+//   err := wasmFunction()
+//   var exitCode wasi.ExitCode
+//   if errors.As(err, &exitCode) {
+//   	// The function is terminated by proc_exit.
+//   }
+//
+// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#exitcode
+// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#proc_exit
+type ExitCode uint32
+
+func (err ExitCode) Error() string {
+	return fmt.Sprintf("terminated by proc_exit(%d)", uint32(err))
+}


Test patch:
diff --git a/internal/wasi/wasi_test.go b/internal/wasi/wasi_test.go
index 7366fd37..0b9d4d4a 100644
--- a/internal/wasi/wasi_test.go
+++ b/internal/wasi/wasi_test.go
@@ -491,7 +491,38 @@ func TestAPI_ClockTimeGet_Errors(t *testing.T) {
 // TODO: TestAPI_PathSymlink TestAPI_PathSymlink_Errors
 // TODO: TestAPI_PathUnlinkFile TestAPI_PathUnlinkFile_Errors
 // TODO: TestAPI_PollOneoff TestAPI_PollOneoff_Errors
-// TODO: TestAPI_ProcExit TestAPI_ProcExit_Errors
+
+func TestAPI_ProcExit(t *testing.T) {
+	store, _ := instantiateWasmStore(t, FunctionProcExit, ImportProcExit, "test")
+
+	tests := []struct {
+		name     string
+		exitCode uint32
+	}{
+		{
+			name:     "success (exitcode 0)",
+			exitCode: 0,
+		},
+
+		{
+			name:     "arbitrary non-zero exitcode",
+			exitCode: 42,
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.name, func(t *testing.T) {
+			// When ProcExit is called, store.CallFunction returns immediately, returning the exit code as the error.
+			_, _, err := store.CallFunction(context.Background(), "test", FunctionProcExit, uint64(tc.exitCode))
+			var code wasi.ExitCode
+			require.ErrorAs(t, err, &code)
+			require.Equal(t, code, wasi.ExitCode(tc.exitCode))
+		})
+	}
+}
+
 // TODO: TestAPI_ProcRaise TestAPI_ProcRaise_Errors
 // TODO: TestAPI_SchedYield TestAPI_SchedYield_Errors