Removes api.ImportRenamer for a different approach to "env" (#680)
Before, we introduced a type `api.ImportRenamer` to resolve conflicts where the "env" module was shared between AssemblyScript and user-defined functions. This API was never used in GitHub, and is complicated. AssemblyScript also isn't the only ABI to share the "env" module, as other web APIs like Emscripten do also. The less complicated approach is to have packages that need to share "env" use `ModuleBuilder.ExportFunctions` instead, and use namespaces as needed if there is overlap. Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
27
api/wasm.go
27
api/wasm.go
@@ -409,33 +409,6 @@ func DecodeF64(input uint64) float64 {
|
|||||||
return math.Float64frombits(input)
|
return math.Float64frombits(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImportRenamer applies during compilation after a module has been decoded from wasm, but before it is instantiated.
|
|
||||||
//
|
|
||||||
// For example, you may have a module like below, but the exported functions are in two different modules:
|
|
||||||
// (import "js" "increment" (func $increment (result i32)))
|
|
||||||
// (import "js" "decrement" (func $decrement (result i32)))
|
|
||||||
// (import "js" "wasm_increment" (func $wasm_increment (result i32)))
|
|
||||||
// (import "js" "wasm_decrement" (func $wasm_decrement (result i32)))
|
|
||||||
//
|
|
||||||
// The below breaks up the imports: "increment" and "decrement" from the module "go" and other functions from "wasm":
|
|
||||||
// renamer := func(externType api.ExternType, oldModule, oldName string) (string, string) {
|
|
||||||
// if externType != api.ExternTypeFunc {
|
|
||||||
// return oldModule, oldName
|
|
||||||
// }
|
|
||||||
// switch oldName {
|
|
||||||
// case "increment", "decrement": return "go", oldName
|
|
||||||
// default: return "wasm", oldName
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// The resulting CompiledModule imports will look identical to this:
|
|
||||||
// (import "go" "increment" (func $increment (result i32)))
|
|
||||||
// (import "go" "decrement" (func $decrement (result i32)))
|
|
||||||
// (import "wasm" "wasm_increment" (func $wasm_increment (result i32)))
|
|
||||||
// (import "wasm" "wasm_decrement" (func $wasm_decrement (result i32)))
|
|
||||||
//
|
|
||||||
type ImportRenamer func(externType ExternType, oldModule, oldName string) (newModule, newName string)
|
|
||||||
|
|
||||||
// MemorySizer applies during compilation after a module has been decoded from wasm, but before it is instantiated.
|
// MemorySizer applies during compilation after a module has been decoded from wasm, but before it is instantiated.
|
||||||
// This determines the amount of memory pages (65536 bytes per page) to use when a memory is instantiated as a []byte.
|
// This determines the amount of memory pages (65536 bytes per page) to use when a memory is instantiated as a []byte.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
// Package assemblyscript contains Go-defined special functions imported by AssemblyScript under the module name "env".
|
// Package assemblyscript contains Go-defined special functions imported by
|
||||||
|
// AssemblyScript under the module name "env".
|
||||||
//
|
//
|
||||||
// Special Functions
|
// Special Functions
|
||||||
//
|
//
|
||||||
// AssemblyScript code import the below special functions when not using WASI. Sometimes only "abort"
|
// AssemblyScript code import the below special functions when not using WASI.
|
||||||
|
// Note: Sometimes only "abort" is imported.
|
||||||
//
|
//
|
||||||
// * "abort" - exits with 255 with an abort message written to wazero.ModuleConfig WithStderr.
|
// * "abort" - exits with 255 with an abort message written to
|
||||||
|
// wazero.ModuleConfig WithStderr.
|
||||||
// * "trace" - no output unless.
|
// * "trace" - no output unless.
|
||||||
// * "seed" - uses wazero.ModuleConfig WithRandSource as the source of seed values.
|
// * "seed" - uses wazero.ModuleConfig WithRandSource as the source of seed
|
||||||
|
// values.
|
||||||
//
|
//
|
||||||
// Relationship to WASI
|
// Relationship to WASI
|
||||||
//
|
//
|
||||||
// A program compiled to use WASI, via "import wasi" in any file, won't import these functions.
|
// A program compiled to use WASI, via "import wasi" in any file, won't import
|
||||||
// See wasi_snapshot_preview1.InstantiateSnapshotPreview1
|
// these functions.
|
||||||
//
|
//
|
||||||
// See https://www.assemblyscript.org/concepts.html#special-imports
|
// See wasi_snapshot_preview1.Instantiate and
|
||||||
|
// https://www.assemblyscript.org/concepts.html#special-imports
|
||||||
package assemblyscript
|
package assemblyscript
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -31,44 +36,46 @@ import (
|
|||||||
"github.com/tetratelabs/wazero/sys"
|
"github.com/tetratelabs/wazero/sys"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Instantiate instantiates the "env" module used by AssemblyScript into the runtime default namespace.
|
// Instantiate instantiates the "env" module used by AssemblyScript into the
|
||||||
|
// runtime default namespace.
|
||||||
//
|
//
|
||||||
// Notes
|
// Notes
|
||||||
//
|
//
|
||||||
// * Closing the wazero.Runtime has the same effect as closing the result.
|
// * Closing the wazero.Runtime has the same effect as closing the result.
|
||||||
// * To instantiate into another wazero.Namespace, use NewBuilder instead.
|
// * To add more functions to the "env" module, use FunctionExporter.
|
||||||
|
// * To instantiate into another wazero.Namespace, use FunctionExporter.
|
||||||
func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) {
|
func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) {
|
||||||
return NewBuilder(r).Instantiate(ctx, r)
|
return r.NewModuleBuilder("env").
|
||||||
|
ExportFunctions(NewFunctionExporter().ExportFunctions()).
|
||||||
|
Instantiate(ctx, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Builder configures the "env" module used by AssemblyScript for later use via Compile or Instantiate.
|
// FunctionExporter configures the functions in the "env" module used by
|
||||||
type Builder interface {
|
// AssemblyScript.
|
||||||
// WithAbortMessageDisabled configures the AssemblyScript abort function to discard any message.
|
type FunctionExporter interface {
|
||||||
WithAbortMessageDisabled() Builder
|
// WithAbortMessageDisabled configures the AssemblyScript abort function to
|
||||||
|
// discard any message.
|
||||||
|
WithAbortMessageDisabled() FunctionExporter
|
||||||
|
|
||||||
// WithTraceToStdout configures the AssemblyScript trace function to output messages to Stdout, as configured by
|
// WithTraceToStdout configures the AssemblyScript trace function to output
|
||||||
// wazero.ModuleConfig WithStdout.
|
// messages to Stdout, as configured by wazero.ModuleConfig WithStdout.
|
||||||
WithTraceToStdout() Builder
|
WithTraceToStdout() FunctionExporter
|
||||||
|
|
||||||
// WithTraceToStderr configures the AssemblyScript trace function to output messages to Stderr, as configured by
|
// WithTraceToStderr configures the AssemblyScript trace function to output
|
||||||
// wazero.ModuleConfig WithStderr. Because of the potential volume of trace messages, it is often more appropriate
|
// messages to Stderr, as configured by wazero.ModuleConfig WithStderr.
|
||||||
// to use WithTraceToStdout instead.
|
|
||||||
WithTraceToStderr() Builder
|
|
||||||
|
|
||||||
// Compile compiles the "env" module that can instantiated in any namespace (wazero.Namespace).
|
|
||||||
//
|
//
|
||||||
// Note: This has the same effect as the same function on wazero.ModuleBuilder.
|
// Because of the potential volume of trace messages, it is often more
|
||||||
Compile(context.Context, wazero.CompileConfig) (wazero.CompiledModule, error)
|
// appropriate to use WithTraceToStdout instead.
|
||||||
|
WithTraceToStderr() FunctionExporter
|
||||||
|
|
||||||
// Instantiate instantiates the "env" module into the provided namespace.
|
// ExportFunctions builds functions to export with a wazero.ModuleBuilder
|
||||||
//
|
// named "env".
|
||||||
// Note: This has the same effect as the same function on wazero.ModuleBuilder.
|
ExportFunctions() (nameToGoFunc map[string]interface{})
|
||||||
Instantiate(context.Context, wazero.Namespace) (api.Closer, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBuilder returns a new Builder with trace disabled.
|
// NewFunctionExporter returns a FunctionExporter object with trace disabled.
|
||||||
func NewBuilder(r wazero.Runtime) Builder {
|
func NewFunctionExporter() FunctionExporter {
|
||||||
return &builder{r: r, traceMode: traceDisabled}
|
return &functionExporter{traceMode: traceDisabled}
|
||||||
}
|
}
|
||||||
|
|
||||||
type traceMode int
|
type traceMode int
|
||||||
@@ -79,54 +86,44 @@ const (
|
|||||||
traceStderr traceMode = 2
|
traceStderr traceMode = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
type builder struct {
|
type functionExporter struct {
|
||||||
r wazero.Runtime
|
|
||||||
abortMessageDisabled bool
|
abortMessageDisabled bool
|
||||||
traceMode traceMode
|
traceMode traceMode
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithAbortMessageDisabled implements Builder.WithAbortMessageDisabled
|
// WithAbortMessageDisabled implements FunctionExporter.WithAbortMessageDisabled
|
||||||
func (b *builder) WithAbortMessageDisabled() Builder {
|
func (e *functionExporter) WithAbortMessageDisabled() FunctionExporter {
|
||||||
ret := *b // copy
|
ret := *e // copy
|
||||||
ret.abortMessageDisabled = true
|
ret.abortMessageDisabled = true
|
||||||
return &ret
|
return &ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithTraceToStdout implements Builder.WithTraceToStdout
|
// WithTraceToStdout implements FunctionExporter.WithTraceToStdout
|
||||||
func (b *builder) WithTraceToStdout() Builder {
|
func (e *functionExporter) WithTraceToStdout() FunctionExporter {
|
||||||
ret := *b // copy
|
ret := *e // copy
|
||||||
ret.traceMode = traceStdout
|
ret.traceMode = traceStdout
|
||||||
return &ret
|
return &ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithTraceToStderr implements Builder.WithTraceToStderr
|
// WithTraceToStderr implements FunctionExporter.WithTraceToStderr
|
||||||
func (b *builder) WithTraceToStderr() Builder {
|
func (e *functionExporter) WithTraceToStderr() FunctionExporter {
|
||||||
ret := *b // copy
|
ret := *e // copy
|
||||||
ret.traceMode = traceStderr
|
ret.traceMode = traceStderr
|
||||||
return &ret
|
return &ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// moduleBuilder returns a new wazero.ModuleBuilder
|
// ExportFunctions implements FunctionExporter.ExportFunctions
|
||||||
func (b *builder) moduleBuilder() wazero.ModuleBuilder {
|
func (e *functionExporter) ExportFunctions() (nameToGoFunc map[string]interface{}) {
|
||||||
env := &assemblyscript{abortMessageDisabled: b.abortMessageDisabled, traceMode: b.traceMode}
|
env := &assemblyscript{abortMessageDisabled: e.abortMessageDisabled, traceMode: e.traceMode}
|
||||||
return b.r.NewModuleBuilder("env").
|
return map[string]interface{}{
|
||||||
ExportFunction("abort", env.abort).
|
"abort": env.abort,
|
||||||
ExportFunction("trace", env.trace).
|
"trace": env.trace,
|
||||||
ExportFunction("seed", env.seed)
|
"seed": env.seed,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile implements Builder.Compile
|
// assemblyScript includes "Special imports" only used In AssemblyScript when a
|
||||||
func (b *builder) Compile(ctx context.Context, config wazero.CompileConfig) (wazero.CompiledModule, error) {
|
// user didn't add `import "wasi"` to their entry file.
|
||||||
return b.moduleBuilder().Compile(ctx, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instantiate implements Builder.Instantiate
|
|
||||||
func (b *builder) Instantiate(ctx context.Context, ns wazero.Namespace) (api.Closer, error) {
|
|
||||||
return b.moduleBuilder().Instantiate(ctx, ns)
|
|
||||||
}
|
|
||||||
|
|
||||||
// assemblyScript includes "Special imports" only used In AssemblyScript when a user didn't add `import "wasi"` to their
|
|
||||||
// entry file.
|
|
||||||
//
|
//
|
||||||
// See https://www.assemblyscript.org/concepts.html#special-imports
|
// See https://www.assemblyscript.org/concepts.html#special-imports
|
||||||
// See https://www.assemblyscript.org/concepts.html#targeting-wasi
|
// See https://www.assemblyscript.org/concepts.html#targeting-wasi
|
||||||
@@ -137,13 +134,15 @@ type assemblyscript struct {
|
|||||||
traceMode traceMode
|
traceMode traceMode
|
||||||
}
|
}
|
||||||
|
|
||||||
// abort is called on unrecoverable errors. This is typically present in Wasm compiled from AssemblyScript, if
|
// abort is called on unrecoverable errors. This is typically present in Wasm
|
||||||
// assertions are enabled or errors are thrown.
|
// compiled from AssemblyScript, if assertions are enabled or errors are
|
||||||
|
// thrown.
|
||||||
//
|
//
|
||||||
// The implementation writes the message to stderr, unless abortMessageDisabled, and closes the module with exit code
|
// The implementation writes the message to stderr, unless
|
||||||
// 255.
|
// abortMessageDisabled, and closes the module with exit code 255.
|
||||||
//
|
//
|
||||||
// Here's the import in a user's module that ends up using this, in WebAssembly 1.0 (MVP) Text Format:
|
// Here's the import in a user's module that ends up using this, in WebAssembly
|
||||||
|
// 1.0 (MVP) Text Format:
|
||||||
// (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32)))
|
// (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32)))
|
||||||
//
|
//
|
||||||
// See https://github.com/AssemblyScript/assemblyscript/blob/fa14b3b03bd4607efa52aaff3132bea0c03a7989/std/assembly/wasi/index.ts#L18
|
// See https://github.com/AssemblyScript/assemblyscript/blob/fa14b3b03bd4607efa52aaff3132bea0c03a7989/std/assembly/wasi/index.ts#L18
|
||||||
@@ -179,9 +178,11 @@ func (a *assemblyscript) abort(
|
|||||||
panic(sys.NewExitError(mod.Name(), exitCode))
|
panic(sys.NewExitError(mod.Name(), exitCode))
|
||||||
}
|
}
|
||||||
|
|
||||||
// trace implements the same named function in AssemblyScript (ex. trace('Hello World!'))
|
// trace implements the same named function in AssemblyScript. Ex.
|
||||||
|
// trace('Hello World!')
|
||||||
//
|
//
|
||||||
// Here's the import in a user's module that ends up using this, in WebAssembly 1.0 (MVP) Text Format:
|
// Here's the import in a user's module that ends up using this, in WebAssembly
|
||||||
|
// 1.0 (MVP) Text Format:
|
||||||
// (import "env" "trace" (func $~lib/builtins/trace (param i32 i32 f64 f64 f64 f64 f64)))
|
// (import "env" "trace" (func $~lib/builtins/trace (param i32 i32 f64 f64 f64 f64 f64)))
|
||||||
//
|
//
|
||||||
// See https://github.com/AssemblyScript/assemblyscript/blob/fa14b3b03bd4607efa52aaff3132bea0c03a7989/std/assembly/wasi/index.ts#L61
|
// See https://github.com/AssemblyScript/assemblyscript/blob/fa14b3b03bd4607efa52aaff3132bea0c03a7989/std/assembly/wasi/index.ts#L61
|
||||||
@@ -236,9 +237,11 @@ func formatFloat(f float64) string {
|
|||||||
return strconv.FormatFloat(f, 'g', -1, 64)
|
return strconv.FormatFloat(f, 'g', -1, 64)
|
||||||
}
|
}
|
||||||
|
|
||||||
// seed is called when the AssemblyScript's random number generator needs to be seeded
|
// seed is called when the AssemblyScript's random number generator needs to be
|
||||||
|
// seeded.
|
||||||
//
|
//
|
||||||
// Here's the import in a user's module that ends up using this, in WebAssembly 1.0 (MVP) Text Format:
|
// Here's the import in a user's module that ends up using this, in WebAssembly
|
||||||
|
// 1.0 (MVP) Text Format:
|
||||||
// (import "env" "seed" (func $~lib/builtins/seed (result f64)))
|
// (import "env" "seed" (func $~lib/builtins/seed (result f64)))
|
||||||
//
|
//
|
||||||
// See https://github.com/AssemblyScript/assemblyscript/blob/fa14b3b03bd4607efa52aaff3132bea0c03a7989/std/assembly/wasi/index.ts#L111
|
// See https://github.com/AssemblyScript/assemblyscript/blob/fa14b3b03bd4607efa52aaff3132bea0c03a7989/std/assembly/wasi/index.ts#L111
|
||||||
|
|||||||
45
assemblyscript/assemblyscript_example_test.go
Normal file
45
assemblyscript/assemblyscript_example_test.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package assemblyscript_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_ "embed"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/tetratelabs/wazero"
|
||||||
|
"github.com/tetratelabs/wazero/assemblyscript"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This shows how to instantiate AssemblyScript's special imports.
|
||||||
|
func Example_instantiate() {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
r := wazero.NewRuntime()
|
||||||
|
defer r.Close(ctx) // This closes everything this Runtime created.
|
||||||
|
|
||||||
|
// This adds the "env" module to the runtime, with AssemblyScript's special
|
||||||
|
// function imports.
|
||||||
|
if _, err := assemblyscript.Instantiate(ctx, r); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
}
|
||||||
|
|
||||||
|
// This shows how to instantiate AssemblyScript's special imports when you also
|
||||||
|
// need other functions in the "env" module.
|
||||||
|
func Example_functionExporter() {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
r := wazero.NewRuntime()
|
||||||
|
defer r.Close(ctx) // This closes everything this Runtime created.
|
||||||
|
|
||||||
|
// First construct your own module builder for "env"
|
||||||
|
envBuilder := r.NewModuleBuilder("env").
|
||||||
|
ExportFunction("get_int", func() uint32 { return 1 })
|
||||||
|
|
||||||
|
// Now, add AssemblyScript special function imports into it.
|
||||||
|
envBuilder.ExportFunctions(assemblyscript.NewFunctionExporter().
|
||||||
|
WithAbortMessageDisabled().ExportFunctions())
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
}
|
||||||
@@ -41,17 +41,17 @@ var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
|
|||||||
func TestAbort(t *testing.T) {
|
func TestAbort(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
enabled bool
|
exporter FunctionExporter
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "enabled",
|
name: "enabled",
|
||||||
enabled: true,
|
exporter: NewFunctionExporter(),
|
||||||
expected: "message at filename:1:2\n",
|
expected: "message at filename:1:2\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "disabled",
|
name: "disabled",
|
||||||
enabled: false,
|
exporter: NewFunctionExporter().WithAbortMessageDisabled(),
|
||||||
expected: "",
|
expected: "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -65,13 +65,10 @@ func TestAbort(t *testing.T) {
|
|||||||
|
|
||||||
out := &bytes.Buffer{}
|
out := &bytes.Buffer{}
|
||||||
|
|
||||||
if tc.enabled {
|
_, err := r.NewModuleBuilder("env").
|
||||||
_, err := Instantiate(testCtx, r)
|
ExportFunctions(tc.exporter.ExportFunctions()).
|
||||||
require.NoError(t, err)
|
Instantiate(testCtx, r)
|
||||||
} else {
|
require.NoError(t, err)
|
||||||
_, err := NewBuilder(r).WithAbortMessageDisabled().Instantiate(testCtx, r)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
abortWasm, err := watzero.Wat2Wasm(abortWat)
|
abortWasm, err := watzero.Wat2Wasm(abortWat)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -93,6 +90,54 @@ func TestAbort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAbort_Error(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
messageUTF16 []byte
|
||||||
|
fileNameUTF16 []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "bad message",
|
||||||
|
messageUTF16: encodeUTF16("message")[:5],
|
||||||
|
fileNameUTF16: encodeUTF16("filename"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad filename",
|
||||||
|
messageUTF16: encodeUTF16("message"),
|
||||||
|
fileNameUTF16: encodeUTF16("filename")[:5],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tc := tt
|
||||||
|
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
r := wazero.NewRuntime()
|
||||||
|
defer r.Close(testCtx)
|
||||||
|
|
||||||
|
_, err := Instantiate(testCtx, r)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
abortWasm, err := watzero.Wat2Wasm(abortWat)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
compiled, err := r.CompileModule(testCtx, abortWasm, wazero.NewCompileConfig())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
out := &bytes.Buffer{}
|
||||||
|
exporter := wazero.NewModuleConfig().WithName(t.Name()).WithStdout(out)
|
||||||
|
mod, err := r.InstantiateModule(testCtx, compiled, exporter)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
messageOff, filenameOff := writeAbortMessageAndFileName(t, mod.Memory(), tc.messageUTF16, tc.fileNameUTF16)
|
||||||
|
|
||||||
|
_, err = mod.ExportedFunction("abort").Call(testCtx, uint64(messageOff), uint64(filenameOff), 1, 2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "", out.String()) // nothing output if strings fail to read.
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var unreachableAfterAbort = `(module
|
var unreachableAfterAbort = `(module
|
||||||
(import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32)))
|
(import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32)))
|
||||||
(func $main
|
(func $main
|
||||||
@@ -106,12 +151,15 @@ var unreachableAfterAbort = `(module
|
|||||||
(start $main)
|
(start $main)
|
||||||
)`
|
)`
|
||||||
|
|
||||||
// TestAbort_StopsExecution ensures code that follows an abort isn't invoked.
|
// TestAbort_UnreachableAfter ensures code that follows an abort isn't invoked.
|
||||||
func TestAbort_StopsExecution(t *testing.T) {
|
func TestAbort_UnreachableAfter(t *testing.T) {
|
||||||
r := wazero.NewRuntime()
|
r := wazero.NewRuntime()
|
||||||
defer r.Close(testCtx)
|
defer r.Close(testCtx)
|
||||||
|
|
||||||
_, err := NewBuilder(r).WithAbortMessageDisabled().Instantiate(testCtx, r)
|
_, err := r.NewModuleBuilder("env").
|
||||||
|
// Disable the abort message as we are passing invalid memory offsets.
|
||||||
|
ExportFunctions(NewFunctionExporter().WithAbortMessageDisabled().ExportFunctions()).
|
||||||
|
Instantiate(testCtx, r)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
abortWasm, err := watzero.Wat2Wasm(unreachableAfterAbort)
|
abortWasm, err := watzero.Wat2Wasm(unreachableAfterAbort)
|
||||||
@@ -149,47 +197,95 @@ func TestSeed(t *testing.T) {
|
|||||||
require.Equal(t, uint64(506097522914230528), res[0])
|
require.Equal(t, uint64(506097522914230528), res[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSeed_error(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
source io.Reader
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "not 8 bytes",
|
||||||
|
source: bytes.NewReader([]byte{0, 1}),
|
||||||
|
expectedErr: `error reading random seed: unexpected EOF (recovered by wazero)
|
||||||
|
wasm stack trace:
|
||||||
|
env.seed() f64`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "error reading",
|
||||||
|
source: iotest.ErrReader(errors.New("ice cream")),
|
||||||
|
expectedErr: `error reading random seed: ice cream (recovered by wazero)
|
||||||
|
wasm stack trace:
|
||||||
|
env.seed() f64`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tc := tt
|
||||||
|
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
r := wazero.NewRuntime()
|
||||||
|
defer r.Close(testCtx)
|
||||||
|
|
||||||
|
_, err := Instantiate(testCtx, r)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
seedWasm, err := watzero.Wat2Wasm(seedWat)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
compiled, err := r.CompileModule(testCtx, seedWasm, wazero.NewCompileConfig())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
exporter := wazero.NewModuleConfig().WithName(t.Name()).WithRandSource(tc.source)
|
||||||
|
mod, err := r.InstantiateModule(testCtx, compiled, exporter)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = mod.ExportedFunction("seed").Call(testCtx)
|
||||||
|
require.EqualError(t, err, tc.expectedErr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTrace(t *testing.T) {
|
func TestTrace(t *testing.T) {
|
||||||
noArgs := []uint64{4, 0, 0, 0, 0, 0, 0}
|
noArgs := []uint64{4, 0, 0, 0, 0, 0, 0}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
mode traceMode
|
exporter FunctionExporter
|
||||||
params []uint64
|
params []uint64
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{
|
|
||||||
name: "stderr",
|
|
||||||
mode: traceStderr,
|
|
||||||
params: noArgs,
|
|
||||||
expected: "trace: hello\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "stdout",
|
|
||||||
mode: traceStdout,
|
|
||||||
params: noArgs,
|
|
||||||
expected: "trace: hello\n",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "disabled",
|
name: "disabled",
|
||||||
mode: traceDisabled,
|
exporter: NewFunctionExporter(),
|
||||||
params: noArgs,
|
params: noArgs,
|
||||||
expected: "",
|
expected: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "one",
|
name: "ToStderr",
|
||||||
mode: traceStdout,
|
exporter: NewFunctionExporter().WithTraceToStderr(),
|
||||||
|
params: noArgs,
|
||||||
|
expected: "trace: hello\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ToStdout - no args",
|
||||||
|
exporter: NewFunctionExporter().WithTraceToStdout(),
|
||||||
|
params: noArgs,
|
||||||
|
expected: "trace: hello\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ToStdout - one arg",
|
||||||
|
exporter: NewFunctionExporter().WithTraceToStdout(),
|
||||||
params: []uint64{4, 1, api.EncodeF64(1), 0, 0, 0, 0},
|
params: []uint64{4, 1, api.EncodeF64(1), 0, 0, 0, 0},
|
||||||
expected: "trace: hello 1\n",
|
expected: "trace: hello 1\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "two",
|
name: "ToStdout - two args",
|
||||||
mode: traceStdout,
|
exporter: NewFunctionExporter().WithTraceToStdout(),
|
||||||
params: []uint64{4, 2, api.EncodeF64(1), api.EncodeF64(2), 0, 0, 0},
|
params: []uint64{4, 2, api.EncodeF64(1), api.EncodeF64(2), 0, 0, 0},
|
||||||
expected: "trace: hello 1,2\n",
|
expected: "trace: hello 1,2\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "five",
|
name: "ToStdout - five args",
|
||||||
mode: traceStdout,
|
exporter: NewFunctionExporter().WithTraceToStdout(),
|
||||||
params: []uint64{
|
params: []uint64{
|
||||||
4,
|
4,
|
||||||
5,
|
5,
|
||||||
@@ -210,24 +306,9 @@ func TestTrace(t *testing.T) {
|
|||||||
r := wazero.NewRuntime()
|
r := wazero.NewRuntime()
|
||||||
defer r.Close(testCtx)
|
defer r.Close(testCtx)
|
||||||
|
|
||||||
out := &bytes.Buffer{}
|
_, err := r.NewModuleBuilder("env").
|
||||||
|
ExportFunctions(tc.exporter.ExportFunctions()).
|
||||||
as := NewBuilder(r)
|
Instantiate(testCtx, r)
|
||||||
modConfig := wazero.NewModuleConfig()
|
|
||||||
switch tc.mode {
|
|
||||||
case traceStderr:
|
|
||||||
as = as.WithTraceToStderr()
|
|
||||||
modConfig = modConfig.WithStderr(out)
|
|
||||||
case traceStdout:
|
|
||||||
as = as.WithTraceToStdout()
|
|
||||||
modConfig = modConfig.WithStdout(out)
|
|
||||||
case traceDisabled:
|
|
||||||
// Set but not used
|
|
||||||
modConfig = modConfig.WithStderr(out)
|
|
||||||
modConfig = modConfig.WithStdout(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := as.Instantiate(testCtx, r)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
traceWasm, err := watzero.Wat2Wasm(traceWat)
|
traceWasm, err := watzero.Wat2Wasm(traceWat)
|
||||||
@@ -236,7 +317,15 @@ func TestTrace(t *testing.T) {
|
|||||||
code, err := r.CompileModule(testCtx, traceWasm, wazero.NewCompileConfig())
|
code, err := r.CompileModule(testCtx, traceWasm, wazero.NewCompileConfig())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
mod, err := r.InstantiateModule(testCtx, code, modConfig)
|
out := &bytes.Buffer{}
|
||||||
|
config := wazero.NewModuleConfig()
|
||||||
|
if tc.name == "ToStderr" {
|
||||||
|
config.WithStderr(out)
|
||||||
|
} else {
|
||||||
|
config.WithStdout(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
mod, err := r.InstantiateModule(testCtx, code, wazero.NewModuleConfig().WithStdout(out).WithStderr(out))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
message := encodeUTF16("hello")
|
message := encodeUTF16("hello")
|
||||||
@@ -252,7 +341,65 @@ func TestTrace(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadAssemblyScriptString(t *testing.T) {
|
func TestTrace_error(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
message []byte
|
||||||
|
out io.Writer
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "not 8 bytes",
|
||||||
|
message: encodeUTF16("hello")[:5],
|
||||||
|
out: &bytes.Buffer{},
|
||||||
|
expectedErr: `read an odd number of bytes for utf-16 string: 5 (recovered by wazero)
|
||||||
|
wasm stack trace:
|
||||||
|
env.trace(i32,i32,f64,f64,f64,f64,f64)`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "error writing",
|
||||||
|
message: encodeUTF16("hello"),
|
||||||
|
out: &errWriter{err: errors.New("ice cream")},
|
||||||
|
expectedErr: `ice cream (recovered by wazero)
|
||||||
|
wasm stack trace:
|
||||||
|
env.trace(i32,i32,f64,f64,f64,f64,f64)`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tc := tt
|
||||||
|
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
r := wazero.NewRuntime()
|
||||||
|
defer r.Close(testCtx)
|
||||||
|
|
||||||
|
_, err := r.NewModuleBuilder("env").
|
||||||
|
ExportFunctions(NewFunctionExporter().WithTraceToStdout().ExportFunctions()).
|
||||||
|
Instantiate(testCtx, r)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
traceWasm, err := watzero.Wat2Wasm(traceWat)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
compiled, err := r.CompileModule(testCtx, traceWasm, wazero.NewCompileConfig())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
exporter := wazero.NewModuleConfig().WithName(t.Name()).WithStdout(tc.out)
|
||||||
|
mod, err := r.InstantiateModule(testCtx, compiled, exporter)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ok := mod.Memory().WriteUint32Le(testCtx, 0, uint32(len(tc.message)))
|
||||||
|
require.True(t, ok)
|
||||||
|
ok = mod.Memory().Write(testCtx, uint32(4), tc.message)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
_, err = mod.ExportedFunction("trace").Call(testCtx, 4, 0, 0, 0, 0, 0, 0)
|
||||||
|
require.EqualError(t, err, tc.expectedErr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_readAssemblyScriptString(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
memory func(api.Memory)
|
memory func(api.Memory)
|
||||||
@@ -325,54 +472,6 @@ func TestReadAssemblyScriptString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAbort_error(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
messageUTF16 []byte
|
|
||||||
fileNameUTF16 []byte
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "bad message",
|
|
||||||
messageUTF16: encodeUTF16("message")[:5],
|
|
||||||
fileNameUTF16: encodeUTF16("filename"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bad filename",
|
|
||||||
messageUTF16: encodeUTF16("message"),
|
|
||||||
fileNameUTF16: encodeUTF16("filename")[:5],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
tc := tt
|
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
r := wazero.NewRuntime()
|
|
||||||
defer r.Close(testCtx)
|
|
||||||
|
|
||||||
_, err := Instantiate(testCtx, r)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
abortWasm, err := watzero.Wat2Wasm(abortWat)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
compiled, err := r.CompileModule(testCtx, abortWasm, wazero.NewCompileConfig())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
out := &bytes.Buffer{}
|
|
||||||
config := wazero.NewModuleConfig().WithName(t.Name()).WithStdout(out)
|
|
||||||
mod, err := r.InstantiateModule(testCtx, compiled, config)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
messageOff, filenameOff := writeAbortMessageAndFileName(t, mod.Memory(), tc.messageUTF16, tc.fileNameUTF16)
|
|
||||||
|
|
||||||
_, err = mod.ExportedFunction("abort").Call(testCtx, uint64(messageOff), uint64(filenameOff), 1, 2)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "", out.String()) // nothing output if strings fail to read.
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeAbortMessageAndFileName(t *testing.T, mem api.Memory, messageUTF16, fileNameUTF16 []byte) (int, int) {
|
func writeAbortMessageAndFileName(t *testing.T, mem api.Memory, messageUTF16, fileNameUTF16 []byte) (int, int) {
|
||||||
off := 0
|
off := 0
|
||||||
ok := mem.WriteUint32Le(testCtx, uint32(off), uint32(len(messageUTF16)))
|
ok := mem.WriteUint32Le(testCtx, uint32(off), uint32(len(messageUTF16)))
|
||||||
@@ -391,110 +490,6 @@ func writeAbortMessageAndFileName(t *testing.T, mem api.Memory, messageUTF16, fi
|
|||||||
return messageOff, filenameOff
|
return messageOff, filenameOff
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSeed_error(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
source io.Reader
|
|
||||||
expectedErr string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "not 8 bytes",
|
|
||||||
source: bytes.NewReader([]byte{0, 1}),
|
|
||||||
expectedErr: `error reading random seed: unexpected EOF (recovered by wazero)
|
|
||||||
wasm stack trace:
|
|
||||||
env.seed() f64`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "error reading",
|
|
||||||
source: iotest.ErrReader(errors.New("ice cream")),
|
|
||||||
expectedErr: `error reading random seed: ice cream (recovered by wazero)
|
|
||||||
wasm stack trace:
|
|
||||||
env.seed() f64`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
tc := tt
|
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
r := wazero.NewRuntime()
|
|
||||||
defer r.Close(testCtx)
|
|
||||||
|
|
||||||
_, err := Instantiate(testCtx, r)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
seedWasm, err := watzero.Wat2Wasm(seedWat)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
compiled, err := r.CompileModule(testCtx, seedWasm, wazero.NewCompileConfig())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
config := wazero.NewModuleConfig().WithName(t.Name()).WithRandSource(tc.source)
|
|
||||||
mod, err := r.InstantiateModule(testCtx, compiled, config)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = mod.ExportedFunction("seed").Call(testCtx)
|
|
||||||
require.EqualError(t, err, tc.expectedErr)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTrace_error(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
message []byte
|
|
||||||
out io.Writer
|
|
||||||
expectedErr string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "not 8 bytes",
|
|
||||||
message: encodeUTF16("hello")[:5],
|
|
||||||
out: &bytes.Buffer{},
|
|
||||||
expectedErr: `read an odd number of bytes for utf-16 string: 5 (recovered by wazero)
|
|
||||||
wasm stack trace:
|
|
||||||
env.trace(i32,i32,f64,f64,f64,f64,f64)`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "error writing",
|
|
||||||
message: encodeUTF16("hello"),
|
|
||||||
out: &errWriter{err: errors.New("ice cream")},
|
|
||||||
expectedErr: `ice cream (recovered by wazero)
|
|
||||||
wasm stack trace:
|
|
||||||
env.trace(i32,i32,f64,f64,f64,f64,f64)`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
tc := tt
|
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
r := wazero.NewRuntime()
|
|
||||||
defer r.Close(testCtx)
|
|
||||||
|
|
||||||
_, err := NewBuilder(r).WithTraceToStdout().Instantiate(testCtx, r)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
traceWasm, err := watzero.Wat2Wasm(traceWat)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
compiled, err := r.CompileModule(testCtx, traceWasm, wazero.NewCompileConfig())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
config := wazero.NewModuleConfig().WithName(t.Name()).WithStdout(tc.out)
|
|
||||||
mod, err := r.InstantiateModule(testCtx, compiled, config)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
ok := mod.Memory().WriteUint32Le(testCtx, 0, uint32(len(tc.message)))
|
|
||||||
require.True(t, ok)
|
|
||||||
ok = mod.Memory().Write(testCtx, uint32(4), tc.message)
|
|
||||||
require.True(t, ok)
|
|
||||||
|
|
||||||
_, err = mod.ExportedFunction("trace").Call(testCtx, 4, 0, 0, 0, 0, 0, 0)
|
|
||||||
require.EqualError(t, err, tc.expectedErr)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeUTF16(s string) []byte {
|
func encodeUTF16(s string) []byte {
|
||||||
runes := utf16.Encode([]rune(s))
|
runes := utf16.Encode([]rune(s))
|
||||||
b := make([]byte, len(runes)*2)
|
b := make([]byte, len(runes)*2)
|
||||||
|
|||||||
25
config.go
25
config.go
@@ -291,32 +291,23 @@ func (c *compiledModule) Close(_ context.Context) error {
|
|||||||
// CompileConfig allows you to override what was decoded from wasm, prior to compilation (ModuleBuilder.Compile or
|
// CompileConfig allows you to override what was decoded from wasm, prior to compilation (ModuleBuilder.Compile or
|
||||||
// Runtime.CompileModule).
|
// Runtime.CompileModule).
|
||||||
//
|
//
|
||||||
// For example, WithImportRenamer allows you to override hard-coded names that don't match your requirements.
|
// For example, WithMemorySizer allows you to override memoryc size that doesn't match your requirements.
|
||||||
//
|
//
|
||||||
// Note: CompileConfig is immutable. Each WithXXX function returns a new instance including the corresponding change.
|
// Note: CompileConfig is immutable. Each WithXXX function returns a new instance including the corresponding change.
|
||||||
type CompileConfig interface {
|
type CompileConfig interface {
|
||||||
|
|
||||||
// WithImportRenamer can rename imports or break them into different modules. No default.
|
|
||||||
// A nil function is invalid and ignored.
|
|
||||||
//
|
|
||||||
// Note: This is currently not relevant for ModuleBuilder as it has no means to define imports.
|
|
||||||
WithImportRenamer(api.ImportRenamer) CompileConfig
|
|
||||||
|
|
||||||
// WithMemorySizer are the allocation parameters used for a Wasm memory.
|
// WithMemorySizer are the allocation parameters used for a Wasm memory.
|
||||||
// The default is to set cap=min and max=65536 if unset. A nil function is invalid and ignored.
|
// The default is to set cap=min and max=65536 if unset. A nil function is invalid and ignored.
|
||||||
WithMemorySizer(api.MemorySizer) CompileConfig
|
WithMemorySizer(api.MemorySizer) CompileConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type compileConfig struct {
|
type compileConfig struct {
|
||||||
importRenamer api.ImportRenamer
|
memorySizer api.MemorySizer
|
||||||
memorySizer api.MemorySizer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCompileConfig returns a CompileConfig that can be used for configuring module compilation.
|
// NewCompileConfig returns a CompileConfig that can be used for configuring module compilation.
|
||||||
func NewCompileConfig() CompileConfig {
|
func NewCompileConfig() CompileConfig {
|
||||||
return &compileConfig{
|
return &compileConfig{
|
||||||
importRenamer: nil,
|
memorySizer: wasm.MemorySizer,
|
||||||
memorySizer: wasm.MemorySizer,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,16 +317,6 @@ func (c *compileConfig) clone() *compileConfig {
|
|||||||
return &ret
|
return &ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithImportRenamer implements CompileConfig.WithImportRenamer
|
|
||||||
func (c *compileConfig) WithImportRenamer(importRenamer api.ImportRenamer) CompileConfig {
|
|
||||||
if importRenamer == nil {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
ret := c.clone()
|
|
||||||
ret.importRenamer = importRenamer
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMemorySizer implements CompileConfig.WithMemorySizer
|
// WithMemorySizer implements CompileConfig.WithMemorySizer
|
||||||
func (c *compileConfig) WithMemorySizer(memorySizer api.MemorySizer) CompileConfig {
|
func (c *compileConfig) WithMemorySizer(memorySizer api.MemorySizer) CompileConfig {
|
||||||
if memorySizer == nil {
|
if memorySizer == nil {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"testing/fstest"
|
"testing/fstest"
|
||||||
|
|
||||||
"github.com/tetratelabs/wazero/api"
|
|
||||||
internalsys "github.com/tetratelabs/wazero/internal/sys"
|
internalsys "github.com/tetratelabs/wazero/internal/sys"
|
||||||
testfs "github.com/tetratelabs/wazero/internal/testing/fs"
|
testfs "github.com/tetratelabs/wazero/internal/testing/fs"
|
||||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||||
@@ -190,12 +189,6 @@ func TestRuntimeConfig_FeatureToggle(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCompileConfig(t *testing.T) {
|
func TestCompileConfig(t *testing.T) {
|
||||||
im := func(externType api.ExternType, oldModule, oldName string) (newModule, newName string) {
|
|
||||||
return "a", oldName
|
|
||||||
}
|
|
||||||
im2 := func(externType api.ExternType, oldModule, oldName string) (newModule, newName string) {
|
|
||||||
return "b", oldName
|
|
||||||
}
|
|
||||||
mp := func(minPages uint32, maxPages *uint32) (min, capacity, max uint32) {
|
mp := func(minPages uint32, maxPages *uint32) (min, capacity, max uint32) {
|
||||||
return 0, 1, 1
|
return 0, 1, 1
|
||||||
}
|
}
|
||||||
@@ -204,20 +197,6 @@ func TestCompileConfig(t *testing.T) {
|
|||||||
with func(CompileConfig) CompileConfig
|
with func(CompileConfig) CompileConfig
|
||||||
expected *compileConfig
|
expected *compileConfig
|
||||||
}{
|
}{
|
||||||
{
|
|
||||||
name: "WithImportRenamer",
|
|
||||||
with: func(c CompileConfig) CompileConfig {
|
|
||||||
return c.WithImportRenamer(im)
|
|
||||||
},
|
|
||||||
expected: &compileConfig{importRenamer: im},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "WithImportRenamer twice",
|
|
||||||
with: func(c CompileConfig) CompileConfig {
|
|
||||||
return c.WithImportRenamer(im).WithImportRenamer(im2)
|
|
||||||
},
|
|
||||||
expected: &compileConfig{importRenamer: im2},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "WithMemorySizer",
|
name: "WithMemorySizer",
|
||||||
with: func(c CompileConfig) CompileConfig {
|
with: func(c CompileConfig) CompileConfig {
|
||||||
@@ -242,7 +221,6 @@ func TestCompileConfig(t *testing.T) {
|
|||||||
|
|
||||||
// We cannot compare func, but we can compare reflect.Value
|
// We cannot compare func, but we can compare reflect.Value
|
||||||
// See https://go.dev/ref/spec#Comparison_operators
|
// See https://go.dev/ref/spec#Comparison_operators
|
||||||
require.Equal(t, reflect.ValueOf(tc.expected.importRenamer), reflect.ValueOf(rc.importRenamer))
|
|
||||||
require.Equal(t, reflect.ValueOf(tc.expected.memorySizer), reflect.ValueOf(rc.memorySizer))
|
require.Equal(t, reflect.ValueOf(tc.expected.memorySizer), reflect.ValueOf(rc.memorySizer))
|
||||||
// The source wasn't modified
|
// The source wasn't modified
|
||||||
require.Equal(t, &compileConfig{}, input)
|
require.Equal(t, &compileConfig{}, input)
|
||||||
|
|||||||
1
examples/replace-import/.gitignore
vendored
1
examples/replace-import/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
replace-import
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
## Replace import example
|
|
||||||
|
|
||||||
This example shows how to override a module name hard-coded in a WebAssembly
|
|
||||||
module. This is similar to what some tools call "linking".
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
_ "embed"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/tetratelabs/wazero"
|
|
||||||
"github.com/tetratelabs/wazero/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// needsImportWasm was generated by the following:
|
|
||||||
// cd testdata; wat2wasm --debug-names needs_import.wat
|
|
||||||
//go:embed testdata/needs_import.wasm
|
|
||||||
var needsImportWasm []byte
|
|
||||||
|
|
||||||
// main shows how to override a module or function name hard-coded in a
|
|
||||||
// WebAssembly module. This is similar to what some tools call "linking".
|
|
||||||
func main() {
|
|
||||||
// Choose the context to use for function calls.
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// Create a new WebAssembly Runtime.
|
|
||||||
r := wazero.NewRuntime()
|
|
||||||
defer r.Close(ctx) // This closes everything this Runtime created.
|
|
||||||
|
|
||||||
// Instantiate a Go-defined module named "assemblyscript" that exports a
|
|
||||||
// function to close the module that calls "abort".
|
|
||||||
host, err := r.NewModuleBuilder("assemblyscript").
|
|
||||||
ExportFunction("abort", func(ctx context.Context, m api.Module, messageOffset, fileNameOffset, line, col uint32) {
|
|
||||||
_ = m.CloseWithExitCode(ctx, 255)
|
|
||||||
}).Instantiate(ctx, r)
|
|
||||||
if err != nil {
|
|
||||||
log.Panicln(err)
|
|
||||||
}
|
|
||||||
defer host.Close(ctx)
|
|
||||||
|
|
||||||
// Compile the WebAssembly module, replacing the import "env.abort" with
|
|
||||||
// "assemblyscript.abort".
|
|
||||||
compileConfig := wazero.NewCompileConfig().
|
|
||||||
WithImportRenamer(func(externType api.ExternType, oldModule, oldName string) (newModule, newName string) {
|
|
||||||
if oldModule == "env" && oldName == "abort" {
|
|
||||||
return "assemblyscript", "abort"
|
|
||||||
}
|
|
||||||
return oldModule, oldName
|
|
||||||
})
|
|
||||||
|
|
||||||
code, err := r.CompileModule(ctx, needsImportWasm, compileConfig)
|
|
||||||
if err != nil {
|
|
||||||
log.Panicln(err)
|
|
||||||
}
|
|
||||||
defer code.Close(ctx)
|
|
||||||
|
|
||||||
// Instantiate the WebAssembly module.
|
|
||||||
mod, err := r.InstantiateModule(ctx, code, wazero.NewModuleConfig())
|
|
||||||
if err != nil {
|
|
||||||
log.Panicln(err)
|
|
||||||
}
|
|
||||||
defer mod.Close(ctx)
|
|
||||||
|
|
||||||
// Since the above worked, the exported function closes the module.
|
|
||||||
_, err = mod.ExportedFunction("abort").Call(ctx, 0, 0, 0, 0)
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/tetratelabs/wazero/internal/testing/maintester"
|
|
||||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test_main ensures the following will work:
|
|
||||||
//
|
|
||||||
// go run replace-import.go
|
|
||||||
func Test_main(t *testing.T) {
|
|
||||||
stdout, _ := maintester.TestMain(t, main, "replace-import")
|
|
||||||
require.Equal(t, "module \"needs-import\" closed with exit_code(255)\n", stdout)
|
|
||||||
}
|
|
||||||
BIN
examples/replace-import/testdata/needs_import.wasm
vendored
BIN
examples/replace-import/testdata/needs_import.wasm
vendored
Binary file not shown.
@@ -1,3 +0,0 @@
|
|||||||
(module $needs-import
|
|
||||||
(func (export "abort") (import "env" "abort") (param i32 i32 i32 i32))
|
|
||||||
)
|
|
||||||
@@ -174,13 +174,6 @@ func (r *runtime) CompileModule(ctx context.Context, binary []byte, cConfig Comp
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace imports if any configuration exists to do so.
|
|
||||||
if importRenamer := config.importRenamer; importRenamer != nil {
|
|
||||||
for _, i := range internal.ImportSection {
|
|
||||||
i.Module, i.Name = importRenamer(i.Type, i.Module, i.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal.AssignModuleID(binary)
|
internal.AssignModuleID(binary)
|
||||||
|
|
||||||
if err = r.store.Engine.CompileModule(ctx, internal); err != nil {
|
if err = r.store.Engine.CompileModule(ctx, internal); err != nil {
|
||||||
|
|||||||
@@ -78,56 +78,6 @@ func TestRuntime_CompileModule(t *testing.T) {
|
|||||||
Max: 3,
|
Max: 3,
|
||||||
}, code.module.MemorySection)
|
}, code.module.MemorySection)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("WithImportReplacements", func(t *testing.T) {
|
|
||||||
testBin, err := watzero.Wat2Wasm(`(module
|
|
||||||
(import "js" "increment" (func $increment (result i32)))
|
|
||||||
(import "js" "decrement" (func $decrement (result i32)))
|
|
||||||
(import "js" "wasm_increment" (func $wasm_increment (result i32)))
|
|
||||||
(import "js" "wasm_decrement" (func $wasm_decrement (result i32)))
|
|
||||||
)`)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
m, err := r.CompileModule(testCtx, testBin, NewCompileConfig().
|
|
||||||
WithImportRenamer(func(externType api.ExternType, oldModule, oldName string) (string, string) {
|
|
||||||
if externType != api.ExternTypeFunc {
|
|
||||||
return oldModule, oldName
|
|
||||||
}
|
|
||||||
switch oldName {
|
|
||||||
case "increment", "decrement":
|
|
||||||
return "go", oldName
|
|
||||||
case "wasm_increment", "wasm_decrement":
|
|
||||||
return "wasm", oldName
|
|
||||||
default:
|
|
||||||
return oldModule, oldName
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
require.NoError(t, err)
|
|
||||||
code := m.(*compiledModule)
|
|
||||||
|
|
||||||
require.Equal(t, []*wasm.Import{
|
|
||||||
{
|
|
||||||
Module: "go", Name: "increment",
|
|
||||||
Type: wasm.ExternTypeFunc,
|
|
||||||
DescFunc: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Module: "go", Name: "decrement",
|
|
||||||
Type: wasm.ExternTypeFunc,
|
|
||||||
DescFunc: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Module: "wasm", Name: "wasm_increment",
|
|
||||||
Type: wasm.ExternTypeFunc,
|
|
||||||
DescFunc: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Module: "wasm", Name: "wasm_decrement",
|
|
||||||
Type: wasm.ExternTypeFunc,
|
|
||||||
DescFunc: 0,
|
|
||||||
},
|
|
||||||
}, code.module.ImportSection)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRuntime_CompileModule_Errors(t *testing.T) {
|
func TestRuntime_CompileModule_Errors(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user