cherry/wasmtest: a test program for wasmexport

Change-Id: I54700d85b52e7be12f337fc72b39a479605dc38f
Reviewed-on: https://go-review.googlesource.com/c/scratch/+/604235
TryBot-Bypass: Cherry Mui <cherryyz@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
diff --git a/cherry/wasmtest/go.mod b/cherry/wasmtest/go.mod
new file mode 100644
index 0000000..63eef9f
--- /dev/null
+++ b/cherry/wasmtest/go.mod
@@ -0,0 +1,5 @@
+module golang.org/x/scratch/cherry/wasmtest
+
+go 1.22
+
+require github.com/tetratelabs/wazero v1.7.3
diff --git a/cherry/wasmtest/go.sum b/cherry/wasmtest/go.sum
new file mode 100644
index 0000000..b9dbbc9
--- /dev/null
+++ b/cherry/wasmtest/go.sum
@@ -0,0 +1,2 @@
+github.com/tetratelabs/wazero v1.7.3 h1:PBH5KVahrt3S2AHgEjKu4u+LlDbbk+nsGE3KLucy6Rw=
+github.com/tetratelabs/wazero v1.7.3/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
diff --git a/cherry/wasmtest/testprog/x.go b/cherry/wasmtest/testprog/x.go
new file mode 100644
index 0000000..d4f2355
--- /dev/null
+++ b/cherry/wasmtest/testprog/x.go
@@ -0,0 +1,62 @@
+package main
+
+import (
+	"runtime"
+	"runtime/debug"
+)
+
+func init() {
+	println("init function called")
+}
+
+var ch = make(chan float64)
+
+//go:wasmexport E
+func E(a int64, b int32, c float64, d float32) { // various types of args, no result
+	println("=== E ===")
+	// goroutine
+	go func() { ch <- float64(a) + float64(b) + c + float64(d) + 100 }()
+	debug.PrintStack() // traceback
+	grow([100]int{10}) // stack growth
+	runtime.GC()       // GC
+	println("=== E end ===")
+}
+
+//go:wasmexport F
+func F() int64 { // no arg, has result
+	f := int64(<-ch * 100) // force a goroutine switch
+	println("F =", f)
+	return f
+}
+
+//go:wasmexport G
+func G(x int32) {
+	println("G", x)
+	if x%2 == 0 {
+		G(x - 1) // simple recursion within this module
+	} else {
+		J(x - 1) // mutual recursion between host and this module
+	}
+	println("G", x, "end")
+}
+
+//go:wasmimport test I
+func I() int64
+
+//go:wasmimport test J
+func J(int32)
+
+func main() {
+	println("hello")
+	println("main: I =", I())
+}
+
+func grow(x [100]int) {
+	if x[0] == 0 {
+		println("=== grow ===")
+		debug.PrintStack()
+		return
+	}
+	x[0]--
+	grow(x)
+}
diff --git a/cherry/wasmtest/w.go b/cherry/wasmtest/w.go
new file mode 100644
index 0000000..f2ad733
--- /dev/null
+++ b/cherry/wasmtest/w.go
@@ -0,0 +1,128 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// A program for testing wasmexport.
+// This is the driver/host program, which provides the imports
+// and calls the exports. testprog is the source of the Wasm
+// module, which can be compiled to either an executable or a
+// library.
+//
+// To build it as executable:
+// GOARCH=wasm GOOS=wasip1 go build -o /tmp/x.wasm ./testprog
+//
+// To build it as a library:
+// GOARCH=wasm GOOS=wasip1 go build -buildmode=c-shared -o /tmp/x.wasm ./testprog
+//
+// Then run the driver (which works for both modes):
+// go run w.go /tmp/x.wasm
+package main
+
+import (
+	"context"
+	"fmt"
+	"os"
+
+	"github.com/tetratelabs/wazero"
+	"github.com/tetratelabs/wazero/api"
+	"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
+)
+
+// exported from wasm
+var E func(a int64, b int32, c float64, d float32)
+var F func() int64
+var G func(int32)
+
+func I() int64 {
+	println("I start")
+	E(20, 3, 0.4, 0.05)
+	r := F() * 2
+	G(4)
+	println("I end =", r)
+	return r
+}
+
+func J(x int32) {
+	println("J", x)
+	if x > 0 {
+		G(x)
+	}
+	println("J", x, "end")
+}
+
+func main() {
+	ctx := context.Background()
+
+	r := wazero.NewRuntime(ctx)
+	defer r.Close(ctx)
+
+	// provide import functions from host
+	_, err := r.NewHostModuleBuilder("test").
+		NewFunctionBuilder().WithFunc(I).Export("I").
+		NewFunctionBuilder().WithFunc(J).Export("J").
+		Instantiate(ctx)
+	if err != nil {
+		panic(err)
+	}
+
+	buf, err := os.ReadFile(os.Args[1])
+	if err != nil {
+		panic(err)
+	}
+
+	config := wazero.NewModuleConfig().
+		WithStdout(os.Stdout).WithStderr(os.Stderr).
+		WithStartFunctions() // don't call _start
+
+	wasi_snapshot_preview1.MustInstantiate(ctx, r)
+
+	m, err := r.InstantiateWithConfig(ctx, buf, config)
+	if err != nil {
+		panic(err)
+	}
+
+	// get export functions from the module
+	E = func(a int64, b int32, c float64, d float32) {
+		exp := m.ExportedFunction("E")
+		_, err := exp.Call(ctx, api.EncodeI64(a), api.EncodeI32(b), api.EncodeF64(c), api.EncodeF32(d))
+		if err != nil {
+			panic(err)
+		}
+	}
+	F = func() int64 {
+		exp := m.ExportedFunction("F")
+		r, err := exp.Call(ctx)
+		if err != nil {
+			panic(err)
+		}
+		rr := int64(r[0])
+		println("host: F =", rr)
+		return rr
+	}
+	G = func(x int32) {
+		exp := m.ExportedFunction("G")
+		_, err := exp.Call(ctx, api.EncodeI32(x))
+		if err != nil {
+			panic(err)
+		}
+	}
+
+	entry := m.ExportedFunction("_start")
+	if entry != nil {
+		// Executable mode.
+		fmt.Println("Executable mode: start")
+		_, err := entry.Call(ctx)
+		fmt.Println(err)
+		return
+	}
+
+	// Library mode.
+	entry = m.ExportedFunction("_initialize")
+	fmt.Println("Library mode: initialize")
+	_, err = entry.Call(ctx)
+	if err != nil {
+		panic(err)
+	}
+	fmt.Println("\nLibrary mode: call export functions")
+	I()
+}