Propagates context to all api interface methods that aren't constant (#502)

This prepares for exposing operations like Memory.Grow while keeping the
ability to trace what did that, by adding a `context.Context` initial
parameter. This adds this to all API methods that mutate or return
mutated data.

Before, we made a change to trace functions and general lifecycle
commands, but we missed this part. Ex. We track functions, but can't
track what closed the module, changed memory or a mutable constant.
Changing to do this now is not only more consistent, but helps us
optimize at least the interpreter to help users identify otherwise
opaque code that can cause harm. This is critical before we add more
functions that can cause harm, such as Memory.Grow.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Crypt Keeper
2022-04-25 08:13:18 +08:00
committed by GitHub
parent 98676fbc64
commit 45ff2fe12f
44 changed files with 1059 additions and 746 deletions

View File

@@ -26,7 +26,7 @@ func main() {
// Instantiate the module and return its exported functions
module, _ := wazero.NewRuntime().InstantiateModuleFromCode(ctx, source)
defer module.Close()
defer module.Close(ctx)
// Discover 7! is 5040
fmt.Println(module.ExportedFunction("fac").Call(ctx, 7))
@@ -63,7 +63,7 @@ env, err := r.NewModuleBuilder("env").
if err != nil {
log.Fatal(err)
}
defer env.Close()
defer env.Close(ctx)
```
While not a standards body like W3C, there is another dominant community in the
@@ -77,11 +77,11 @@ For example, here's how you can allow WebAssembly modules to read
"/work/home/a.txt" as "/a.txt" or "./a.txt":
```go
wm, err := wasi.InstantiateSnapshotPreview1(ctx, r)
defer wm.Close()
defer wm.Close(ctx)
config := wazero.ModuleConfig().WithFS(os.DirFS("/work/home"))
module, err := r.InstantiateModule(ctx, binary, config)
defer module.Close()
defer module.Close(ctx)
...
```
@@ -302,7 +302,7 @@ top-level project. That said, Takeshi's original motivation is as relevant
today as when he started the project, and worthwhile reading:
If you want to provide Wasm host environments in your Go programs, currently
there's no other choice than using CGO andleveraging the state-of-the-art
there's no other choice than using CGO leveraging the state-of-the-art
runtimes written in C++/Rust (e.g. V8, Wasmtime, Wasmer, WAVM, etc.), and
there's no pure Go Wasm runtime out there. (There's only one exception named
[wagon](https://github.com/go-interpreter/wagon), but it was archived with the
@@ -313,7 +313,7 @@ plugin systems in your Go project and want these plugin systems to be
safe/fast/flexible, and enable users to write plugins in their favorite
languages. That's where Wasm comes into play. You write your own Wasm host
environments and embed Wasm runtime in your projects, and now users are able to
write plugins in their own favorite lanugages (AssembyScript, C, C++, Rust,
write plugins in their own favorite languages (AssemblyScript, C, C++, Rust,
Zig, etc.). As a specific example, you maybe write proxy severs in Go and want
to allow users to extend the proxy via [Proxy-Wasm ABI](https://github.com/proxy-wasm/spec).
Maybe you are writing server-side rendering applications via Wasm, or

View File

@@ -70,8 +70,8 @@ type Module interface {
Name() string
// Close is a convenience that invokes CloseWithExitCode with zero.
Close() error
// ^^ not io.Closer as the rationale (static analysis of leaks) is invalid when there are multiple close methods.
// Note: When the context is nil, it defaults to context.Background.
Close(context.Context) error
// CloseWithExitCode releases resources allocated for this Module. Use a non-zero exitCode parameter to indicate a
// failure to ExportedFunction callers.
@@ -82,7 +82,8 @@ type Module interface {
//
// Calling this inside a host function is safe, and may cause ExportedFunction callers to receive a sys.ExitError
// with the exitCode.
CloseWithExitCode(exitCode uint32) error
// Note: When the context is nil, it defaults to context.Background.
CloseWithExitCode(ctx context.Context, exitCode uint32) error
// Memory returns a memory defined in this module or nil if there are none wasn't.
Memory() Memory
@@ -121,7 +122,7 @@ type Function interface {
// encoded according to ResultTypes. An error is returned for any failure looking up or invoking the function
// including signature mismatch.
//
// Note: when `ctx` is nil, it defaults to context.Background.
// Note: When the context is nil, it defaults to context.Background.
// Note: If Module.Close or Module.CloseWithExitCode were invoked during this call, the error returned may be a
// sys.ExitError. Interpreting this is specific to the module. For example, some "main" functions always call a
// function that exits.
@@ -153,7 +154,9 @@ type Global interface {
// Get returns the last known value of this global.
// See Type for how to encode this value from a Go type.
Get() uint64
//
// Note: When the context is nil, it defaults to context.Background.
Get(context.Context) uint64
}
// MutableGlobal is a Global whose value can be updated at runtime (variable).
@@ -162,11 +165,14 @@ type MutableGlobal interface {
// Set updates the value of this global.
// See Global.Type for how to decode this value to a Go type.
Set(v uint64)
//
// Note: When the context is nil, it defaults to context.Background.
Set(ctx context.Context, v uint64)
}
// Memory allows restricted access to a module's memory. Notably, this does not allow growing.
//
// Note: All functions accept a context.Context, which when nil, default to context.Background.
// Note: This is an interface for decoupling, not third-party implementations. All implementations are in wazero.
// Note: This includes all value types available in WebAssembly 1.0 (20191205) and all are encoded little-endian.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#storage%E2%91%A0
@@ -177,59 +183,67 @@ type Memory interface {
// memory has min 0 and max 2 pages, this returns zero.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#-hrefsyntax-instr-memorymathsfmemorysize%E2%91%A0
Size() uint32
Size(context.Context) uint32
// IndexByte returns the index of the first instance of c in the underlying buffer at the offset or returns false if
// not found or out of range.
IndexByte(offset uint32, c byte) (uint32, bool)
IndexByte(ctx context.Context, offset uint32, c byte) (uint32, bool)
// ReadByte reads a single byte from the underlying buffer at the offset or returns false if out of range.
ReadByte(offset uint32) (byte, bool)
ReadByte(ctx context.Context, offset uint32) (byte, bool)
// ReadUint16Le reads a uint16 in little-endian encoding from the underlying buffer at the offset in or returns
// false if out of range.
ReadUint16Le(ctx context.Context, offset uint32) (uint16, bool)
// ReadUint32Le reads a uint32 in little-endian encoding from the underlying buffer at the offset in or returns
// false if out of range.
ReadUint32Le(offset uint32) (uint32, bool)
ReadUint32Le(ctx context.Context, offset uint32) (uint32, bool)
// ReadFloat32Le reads a float32 from 32 IEEE 754 little-endian encoded bits in the underlying buffer at the offset
// or returns false if out of range.
// See math.Float32bits
ReadFloat32Le(offset uint32) (float32, bool)
ReadFloat32Le(ctx context.Context, offset uint32) (float32, bool)
// ReadUint64Le reads a uint64 in little-endian encoding from the underlying buffer at the offset or returns false
// if out of range.
ReadUint64Le(offset uint32) (uint64, bool)
ReadUint64Le(ctx context.Context, offset uint32) (uint64, bool)
// ReadFloat64Le reads a float64 from 64 IEEE 754 little-endian encoded bits in the underlying buffer at the offset
// or returns false if out of range.
// See math.Float64bits
ReadFloat64Le(offset uint32) (float64, bool)
ReadFloat64Le(ctx context.Context, offset uint32) (float64, bool)
// Read reads byteCount bytes from the underlying buffer at the offset or returns false if out of range.
Read(offset, byteCount uint32) ([]byte, bool)
Read(ctx context.Context, offset, byteCount uint32) ([]byte, bool)
// WriteByte writes a single byte to the underlying buffer at the offset in or returns false if out of range.
WriteByte(offset uint32, v byte) bool
WriteByte(ctx context.Context, offset uint32, v byte) bool
// WriteUint16Le writes the value in little-endian encoding to the underlying buffer at the offset in or returns
// false if out of range.
WriteUint16Le(ctx context.Context, offset uint32, v uint16) bool
// WriteUint32Le writes the value in little-endian encoding to the underlying buffer at the offset in or returns
// false if out of range.
WriteUint32Le(offset, v uint32) bool
WriteUint32Le(ctx context.Context, offset, v uint32) bool
// WriteFloat32Le writes the value in 32 IEEE 754 little-endian encoded bits to the underlying buffer at the offset
// or returns false if out of range.
// See math.Float32bits
WriteFloat32Le(offset uint32, v float32) bool
WriteFloat32Le(ctx context.Context, offset uint32, v float32) bool
// WriteUint64Le writes the value in little-endian encoding to the underlying buffer at the offset in or returns
// false if out of range.
WriteUint64Le(offset uint32, v uint64) bool
WriteUint64Le(ctx context.Context, offset uint32, v uint64) bool
// WriteFloat64Le writes the value in 64 IEEE 754 little-endian encoded bits to the underlying buffer at the offset
// or returns false if out of range.
// See math.Float64bits
WriteFloat64Le(offset uint32, v float64) bool
WriteFloat64Le(ctx context.Context, offset uint32, v float64) bool
// Write writes the slice to the underlying buffer at the offset or returns false if out of range.
Write(offset uint32, v []byte) bool
Write(ctx context.Context, offset uint32, v []byte) bool
}
// EncodeI32 encodes the input as a ValueTypeI32.

View File

@@ -25,10 +25,10 @@ import (
// env, _ := r.NewModuleBuilder("env").ExportFunction("get_random_string", getRandomString).Build(ctx)
//
// env1, _ := r.InstantiateModuleWithConfig(ctx, env, NewModuleConfig().WithName("env.1"))
// defer env1.Close()
// defer env1.Close(ctx)
//
// env2, _ := r.InstantiateModuleWithConfig(ctx, env, NewModuleConfig().WithName("env.2"))
// defer env2.Close()
// defer env2.Close(ctx)
//
// Note: Builder methods do not return errors, to allow chaining. Any validation errors are deferred until Build.
// Note: Insertion order is not retained. Anything defined by this builder is sorted lexicographically on Build.
@@ -53,17 +53,17 @@ type ModuleBuilder interface {
//
// Ex. This uses a Go Context:
//
// addInts := func(m context.Context, x uint32, uint32) uint32 {
// addInts := func(ctx context.Context, x uint32, uint32) uint32 {
// // add a little extra if we put some in the context!
// return x + y + m.Value(extraKey).(uint32)
// return x + y + ctx.Value(extraKey).(uint32)
// }
//
// Ex. This uses an api.Module to reads the parameters from memory. This is important because there are only numeric
// types in Wasm. The only way to share other data is via writing memory and sharing offsets.
//
// addInts := func(m api.Module, offset uint32) uint32 {
// x, _ := m.Memory().ReadUint32Le(offset)
// y, _ := m.Memory().ReadUint32Le(offset + 4) // 32 bits == 4 bytes!
// addInts := func(ctx context.Context, m api.Module, offset uint32) uint32 {
// x, _ := m.Memory().ReadUint32Le(ctx, offset)
// y, _ := m.Memory().ReadUint32Le(ctx, offset + 4) // 32 bits == 4 bytes!
// return x + y
// }
//
@@ -151,12 +151,12 @@ type ModuleBuilder interface {
ExportGlobalF64(name string, v float64) ModuleBuilder
// Build returns a module to instantiate, or returns an error if any of the configuration is invalid.
Build(ctx context.Context) (*CompiledCode, error)
Build(context.Context) (*CompiledCode, error)
// Instantiate is a convenience that calls Build, then Runtime.InstantiateModule
//
// Note: Fields in the builder are copied during instantiation: Later changes do not affect the instantiated result.
Instantiate(ctx context.Context) (api.Module, error)
Instantiate(context.Context) (api.Module, error)
}
// moduleBuilder implements ModuleBuilder
@@ -274,8 +274,8 @@ func (b *moduleBuilder) Instantiate(ctx context.Context) (api.Module, error) {
if err = b.r.store.Engine.CompileModule(ctx, module.module); err != nil {
return nil, err
}
// *wasm.ModuleInstance cannot be tracked, so we release the cache inside of this function.
defer module.Close()
// *wasm.ModuleInstance cannot be tracked, so we release the cache inside this function.
defer module.Close(ctx)
return b.r.InstantiateModuleWithConfig(ctx, module, NewModuleConfig().WithName(b.moduleName))
}
}

View File

@@ -1,6 +1,7 @@
package wazero
import (
"context"
"errors"
"fmt"
"io"
@@ -151,13 +152,12 @@ type CompiledCode struct {
compiledEngine wasm.Engine
}
// compile-time check to ensure CompiledCode implements io.Closer (consistent with api.Module)
var _ io.Closer = &CompiledCode{}
// Close releases all the allocated resources for this CompiledCode.
//
// Note: It is safe to call Close while having outstanding calls from Modules instantiated from this *CompiledCode.
func (c *CompiledCode) Close() error {
func (c *CompiledCode) Close(_ context.Context) error {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
c.compiledEngine.DeleteCompiledModule(c.module)
// It is possible the underlying may need to return an error later, but in any case this matches api.Module.Close.
return nil

View File

@@ -1,6 +1,7 @@
package wazero
import (
"context"
"io"
"math"
"testing"
@@ -777,3 +778,27 @@ func requireSysContext(t *testing.T, max uint32, args, environ []string, stdin i
require.NoError(t, err)
return sys
}
func TestCompiledCode_Close(t *testing.T) {
for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil!
e := &mockEngine{name: "1", cachedModules: map[*wasm.Module]struct{}{}}
var cs []*CompiledCode
for i := 0; i < 10; i++ {
m := &wasm.Module{}
err := e.CompileModule(ctx, m)
require.NoError(t, err)
cs = append(cs, &CompiledCode{module: m, compiledEngine: e})
}
// Before Close.
require.Equal(t, 10, len(e.cachedModules))
for _, c := range cs {
require.NoError(t, c.Close(ctx))
}
// After Close.
require.Zero(t, len(e.cachedModules))
}
}

View File

@@ -29,7 +29,7 @@ func Example() {
if err != nil {
log.Fatal(err)
}
defer mod.Close()
defer mod.Close(ctx)
// Get a function that can be reused until its module is closed:
add := mod.ExportedFunction("add")

View File

@@ -34,7 +34,7 @@ func main() {
if err != nil {
log.Fatal(err)
}
defer env.Close()
defer env.Close(ctx)
// Instantiate a WebAssembly module that imports the "log" function defined
// in "env" and exports "memory" and functions we'll use in this example.
@@ -42,7 +42,7 @@ func main() {
if err != nil {
log.Fatal(err)
}
defer mod.Close()
defer mod.Close(ctx)
// Get references to WebAssembly functions we'll use in this example.
greet := mod.ExportedFunction("greet")
@@ -67,9 +67,9 @@ func main() {
defer deallocate.Call(ctx, namePtr, nameSize)
// The pointer is a linear memory offset, which is where we write the name.
if !mod.Memory().Write(uint32(namePtr), []byte(name)) {
if !mod.Memory().Write(ctx, uint32(namePtr), []byte(name)) {
log.Fatalf("Memory.Write(%d, %d) out of range of memory size %d",
namePtr, nameSize, mod.Memory().Size())
namePtr, nameSize, mod.Memory().Size(ctx))
}
// Now, we can call "greet", which reads the string we wrote to memory!
@@ -91,16 +91,16 @@ func main() {
defer deallocate.Call(ctx, uint64(greetingPtr), uint64(greetingSize))
// The pointer is a linear memory offset, which is where we write the name.
if bytes, ok := mod.Memory().Read(greetingPtr, greetingSize); !ok {
if bytes, ok := mod.Memory().Read(ctx, greetingPtr, greetingSize); !ok {
log.Fatalf("Memory.Read(%d, %d) out of range of memory size %d",
greetingPtr, greetingSize, mod.Memory().Size())
greetingPtr, greetingSize, mod.Memory().Size(ctx))
} else {
fmt.Println("go >>", string(bytes))
}
}
func logString(m api.Module, offset, byteCount uint32) {
buf, ok := m.Memory().Read(offset, byteCount)
func logString(ctx context.Context, m api.Module, offset, byteCount uint32) {
buf, ok := m.Memory().Read(ctx, offset, byteCount)
if !ok {
log.Fatalf("Memory.Read(%d, %d) out of range", offset, byteCount)
}

View File

@@ -35,7 +35,7 @@ func main() {
if err != nil {
log.Fatal(err)
}
defer env.Close()
defer env.Close(ctx)
// Note: testdata/greet.go doesn't use WASI, but TinyGo needs it to
// implement functions such as panic.
@@ -43,7 +43,7 @@ func main() {
if err != nil {
log.Fatal(err)
}
defer wm.Close()
defer wm.Close(ctx)
// Instantiate a WebAssembly module that imports the "log" function defined
// in "env" and exports "memory" and functions we'll use in this example.
@@ -51,7 +51,7 @@ func main() {
if err != nil {
log.Fatal(err)
}
defer mod.Close()
defer mod.Close(ctx)
// Get references to WebAssembly functions we'll use in this example.
greet := mod.ExportedFunction("greet")
@@ -77,9 +77,9 @@ func main() {
defer free.Call(ctx, namePtr)
// The pointer is a linear memory offset, which is where we write the name.
if !mod.Memory().Write(uint32(namePtr), []byte(name)) {
if !mod.Memory().Write(ctx, uint32(namePtr), []byte(name)) {
log.Fatalf("Memory.Write(%d, %d) out of range of memory size %d",
namePtr, nameSize, mod.Memory().Size())
namePtr, nameSize, mod.Memory().Size(ctx))
}
// Now, we can call "greet", which reads the string we wrote to memory!
@@ -98,16 +98,16 @@ func main() {
greetingPtr := uint32(ptrSize[0] >> 32)
greetingSize := uint32(ptrSize[0])
// The pointer is a linear memory offset, which is where we write the name.
if bytes, ok := mod.Memory().Read(greetingPtr, greetingSize); !ok {
if bytes, ok := mod.Memory().Read(ctx, greetingPtr, greetingSize); !ok {
log.Fatalf("Memory.Read(%d, %d) out of range of memory size %d",
greetingPtr, greetingSize, mod.Memory().Size())
greetingPtr, greetingSize, mod.Memory().Size(ctx))
} else {
fmt.Println("go >>", string(bytes))
}
}
func logString(m api.Module, offset, byteCount uint32) {
buf, ok := m.Memory().Read(offset, byteCount)
func logString(ctx context.Context, m api.Module, offset, byteCount uint32) {
buf, ok := m.Memory().Read(ctx, offset, byteCount)
if !ok {
log.Fatalf("Memory.Read(%d, %d) out of range", offset, byteCount)
}

View File

@@ -32,7 +32,7 @@ func main() {
if err != nil {
log.Fatal(err)
}
defer wasm.Close()
defer wasm.Close(ctx)
// Add a module to the runtime named "host/math" which exports one function "add", implemented in Go.
host, err := r.NewModuleBuilder("host/math").
@@ -42,7 +42,7 @@ func main() {
if err != nil {
log.Fatal(err)
}
defer host.Close()
defer host.Close(ctx)
// Read two args to add.
x, y := readTwoArgs()

View File

@@ -44,7 +44,7 @@ func main() {
if err != nil {
log.Fatal(err)
}
defer env.Close()
defer env.Close(ctx)
// Instantiate a WebAssembly module named "age-calculator" that imports
// functions defined in "env".
@@ -87,7 +87,7 @@ func main() {
if err != nil {
log.Fatal(err)
}
defer ageCalculator.Close()
defer ageCalculator.Close(ctx)
// Read the birthYear from the arguments to main
birthYear, err := strconv.ParseUint(os.Args[1], 10, 64)

View File

@@ -36,14 +36,14 @@ func main() {
if err != nil {
log.Fatal(err)
}
defer wasm.Close()
defer wasm.Close(ctx)
// Add a module that uses offset parameters for multiple results, with functions defined in Go.
host, err := resultOffsetHostFunctions(ctx, runtime)
if err != nil {
log.Fatal(err)
}
defer host.Close()
defer host.Close(ctx)
// wazero enables only W3C recommended features by default. Opt-in to other features like so:
runtimeWithMultiValue := wazero.NewRuntimeWithConfig(
@@ -56,14 +56,14 @@ func main() {
if err != nil {
log.Fatal(err)
}
defer wasmWithMultiValue.Close()
defer wasmWithMultiValue.Close(ctx)
// Add a module that uses multiple results values, with functions defined in Go.
hostWithMultiValue, err := multiValueHostFunctions(ctx, runtimeWithMultiValue)
if err != nil {
log.Fatal(err)
}
defer hostWithMultiValue.Close()
defer hostWithMultiValue.Close(ctx)
// Call the same function in all modules and print the results to the console.
for _, mod := range []api.Module{wasm, host, wasmWithMultiValue, hostWithMultiValue} {
@@ -121,8 +121,8 @@ func resultOffsetHostFunctions(ctx context.Context, r wazero.Runtime) (api.Modul
// To use result parameters, we need scratch memory. Allocate the least possible: 1 page (64KB).
ExportMemoryWithMax("mem", 1, 1).
// Define a function that returns a result, while a second result is written to memory.
ExportFunction("get_age", func(m api.Module, resultOffsetAge uint32) (errno uint32) {
if m.Memory().WriteUint64Le(resultOffsetAge, 37) {
ExportFunction("get_age", func(ctx context.Context, m api.Module, resultOffsetAge uint32) (errno uint32) {
if m.Memory().WriteUint64Le(ctx, resultOffsetAge, 37) {
return 0
}
return 1 // overflow
@@ -132,7 +132,7 @@ func resultOffsetHostFunctions(ctx context.Context, r wazero.Runtime) (api.Modul
ExportFunction("call_get_age", func(ctx context.Context, m api.Module) (age uint64) {
resultOffsetAge := uint32(8) // arbitrary memory offset (in bytes)
_, _ = m.ExportedFunction("get_age").Call(ctx, uint64(resultOffsetAge))
age, _ = m.Memory().ReadUint64Le(resultOffsetAge)
age, _ = m.Memory().ReadUint64Le(ctx, resultOffsetAge)
return
}).Instantiate(ctx)
}

View File

@@ -22,13 +22,13 @@ func main() {
// 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(m api.Module, messageOffset, fileNameOffset, line, col uint32) {
_ = m.CloseWithExitCode(255)
ExportFunction("abort", func(ctx context.Context, m api.Module, messageOffset, fileNameOffset, line, col uint32) {
_ = m.CloseWithExitCode(ctx, 255)
}).Instantiate(ctx)
if err != nil {
log.Fatal(err)
}
defer host.Close()
defer host.Close(ctx)
// Compile WebAssembly code that needs the function "env.abort".
code, err := r.CompileModule(ctx, []byte(`(module $needs-import
@@ -39,7 +39,7 @@ func main() {
if err != nil {
log.Fatal(err)
}
defer code.Close()
defer code.Close(ctx)
// Instantiate the WebAssembly module, replacing the import "env.abort"
// with "assemblyscript.abort".
@@ -48,7 +48,7 @@ func main() {
if err != nil {
log.Fatal(err)
}
defer mod.Close()
defer mod.Close(ctx)
// Since the above worked, the exported function closes the module.
_, err = mod.ExportedFunction("abort").Call(ctx, 0, 0, 0, 0)

View File

@@ -45,7 +45,7 @@ func main() {
if err != nil {
log.Fatal(err)
}
defer wm.Close()
defer wm.Close(ctx)
// InstantiateModuleFromCodeWithConfig runs the "_start" function which is what TinyGo compiles "main" to.
// * Set the program name (arg[0]) to "wasi" and add args to write "test.txt" to stdout twice.
@@ -54,5 +54,5 @@ func main() {
if err != nil {
log.Fatal(err)
}
defer cat.Close()
defer cat.Close(ctx)
}

View File

@@ -23,13 +23,13 @@ var caseWasm []byte
func BenchmarkInvocation(b *testing.B) {
b.Run("interpreter", func(b *testing.B) {
m := instantiateHostFunctionModuleWithEngine(b, wazero.NewRuntimeConfigInterpreter())
defer m.Close()
defer m.Close(testCtx)
runAllInvocationBenches(b, m)
})
if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" {
b.Run("jit", func(b *testing.B) {
m := instantiateHostFunctionModuleWithEngine(b, wazero.NewRuntimeConfigJIT())
defer m.Close()
defer m.Close(testCtx)
runAllInvocationBenches(b, m)
})
}
@@ -54,14 +54,14 @@ func runInitializationBench(b *testing.B, r wazero.Runtime) {
if err != nil {
b.Fatal(err)
}
defer compiled.Close()
defer compiled.Close(testCtx)
b.ResetTimer()
for i := 0; i < b.N; i++ {
mod, err := r.InstantiateModule(testCtx, compiled)
if err != nil {
b.Fatal(err)
}
mod.Close()
mod.Close(testCtx)
}
}
@@ -172,11 +172,11 @@ func createRuntime(b *testing.B, engine *wazero.RuntimeConfig) wazero.Runtime {
}
offset := uint32(results[0])
m.Memory().WriteUint32Le(retBufPtr, offset)
m.Memory().WriteUint32Le(retBufSize, 10)
m.Memory().WriteUint32Le(ctx, retBufPtr, offset)
m.Memory().WriteUint32Le(ctx, retBufSize, 10)
b := make([]byte, 10)
_, _ = rand.Read(b)
m.Memory().Write(offset, b)
m.Memory().Write(ctx, offset, b)
}
r := wazero.NewRuntimeWithConfig(engine)

View File

@@ -8,13 +8,13 @@ import (
func BenchmarkMemory(b *testing.B) {
var mem = &wasm.MemoryInstance{Buffer: make([]byte, wasm.MemoryPageSize), Min: 1}
if !mem.WriteByte(10, 16) {
if !mem.WriteByte(testCtx, 10, 16) {
b.Fail()
}
b.Run("ReadByte", func(b *testing.B) {
for i := 0; i < b.N; i++ {
if v, ok := mem.ReadByte(10); !ok || v != 16 {
if v, ok := mem.ReadByte(testCtx, 10); !ok || v != 16 {
b.Fail()
}
}
@@ -22,7 +22,7 @@ func BenchmarkMemory(b *testing.B) {
b.Run("ReadUint32Le", func(b *testing.B) {
for i := 0; i < b.N; i++ {
if v, ok := mem.ReadUint32Le(10); !ok || v != 16 {
if v, ok := mem.ReadUint32Le(testCtx, 10); !ok || v != 16 {
b.Fail()
}
}
@@ -30,7 +30,7 @@ func BenchmarkMemory(b *testing.B) {
b.Run("WriteByte", func(b *testing.B) {
for i := 0; i < b.N; i++ {
if !mem.WriteByte(10, 16) {
if !mem.WriteByte(testCtx, 10, 16) {
b.Fail()
}
}
@@ -38,7 +38,7 @@ func BenchmarkMemory(b *testing.B) {
b.Run("WriteUint32Le", func(b *testing.B) {
for i := 0; i < b.N; i++ {
if !mem.WriteUint32Le(10, 16) {
if !mem.WriteUint32Le(testCtx, 10, 16) {
b.Fail()
}
}

View File

@@ -64,7 +64,7 @@ var (
func testHugeStack(t *testing.T, r wazero.Runtime) {
module, err := r.InstantiateModuleFromCode(testCtx, hugestackWasm)
require.NoError(t, err)
defer module.Close()
defer module.Close(testCtx)
fn := module.ExportedFunction("main")
require.NotNil(t, fn)
@@ -83,7 +83,7 @@ func testUnreachable(t *testing.T, r wazero.Runtime) {
module, err := r.InstantiateModuleFromCode(testCtx, unreachableWasm)
require.NoError(t, err)
defer module.Close()
defer module.Close(testCtx)
_, err = module.ExportedFunction("main").Call(testCtx)
exp := `panic in host function (recovered by wazero)
@@ -106,7 +106,7 @@ func testRecursiveEntry(t *testing.T, r wazero.Runtime) {
module, err := r.InstantiateModuleFromCode(testCtx, recursiveWasm)
require.NoError(t, err)
defer module.Close()
defer module.Close(testCtx)
_, err = module.ExportedFunction("main").Call(testCtx, 1)
require.NoError(t, err)
@@ -121,8 +121,8 @@ func TestImportedAndExportedFunc(t *testing.T) {
// Notably, this uses memory, which ensures api.Module is valid in both interpreter and JIT engines.
func testImportedAndExportedFunc(t *testing.T, r wazero.Runtime) {
var memory *wasm.MemoryInstance
storeInt := func(m api.Module, offset uint32, val uint64) uint32 {
if !m.Memory().WriteUint64Le(offset, val) {
storeInt := func(ctx context.Context, m api.Module, offset uint32, val uint64) uint32 {
if !m.Memory().WriteUint64Le(ctx, offset, val) {
return 1
}
// sneak a reference to the memory, so we can check it later
@@ -132,7 +132,7 @@ func testImportedAndExportedFunc(t *testing.T, r wazero.Runtime) {
host, err := r.NewModuleBuilder("").ExportFunction("store_int", storeInt).Instantiate(testCtx)
require.NoError(t, err)
defer host.Close()
defer host.Close(testCtx)
module, err := r.InstantiateModuleFromCode(testCtx, []byte(`(module $test
(import "" "store_int"
@@ -143,7 +143,7 @@ func testImportedAndExportedFunc(t *testing.T, r wazero.Runtime) {
(export "store_int" (func $store_int))
)`))
require.NoError(t, err)
defer module.Close()
defer module.Close(testCtx)
// Call store_int and ensure it didn't return an error code.
fn := module.ExportedFunction("store_int")
@@ -177,7 +177,7 @@ func testHostFunctionContextParameter(t *testing.T, r wazero.Runtime) {
imported, err := r.NewModuleBuilder(importedName).ExportFunctions(fns).Instantiate(testCtx)
require.NoError(t, err)
defer imported.Close()
defer imported.Close(testCtx)
for test := range fns {
t.Run(test, func(t *testing.T) {
@@ -188,7 +188,7 @@ func testHostFunctionContextParameter(t *testing.T, r wazero.Runtime) {
(export "call->%[3]s" (func $call_%[3]s))
)`, importingName, importedName, test)))
require.NoError(t, err)
defer importing.Close()
defer importing.Close(testCtx)
results, err := importing.ExportedFunction("call->"+test).Call(testCtx, math.MaxUint32-1)
require.NoError(t, err)
@@ -219,7 +219,7 @@ func testHostFunctionNumericParameter(t *testing.T, r wazero.Runtime) {
imported, err := r.NewModuleBuilder(importedName).ExportFunctions(fns).Instantiate(testCtx)
require.NoError(t, err)
defer imported.Close()
defer imported.Close(testCtx)
for _, test := range []struct {
name string
@@ -254,7 +254,7 @@ func testHostFunctionNumericParameter(t *testing.T, r wazero.Runtime) {
(export "call->%[3]s" (func $call_%[3]s))
)`, importingName, importedName, test.name)))
require.NoError(t, err)
defer importing.Close()
defer importing.Close(testCtx)
results, err := importing.ExportedFunction("call->"+test.name).Call(testCtx, test.input)
require.NoError(t, err)
@@ -322,18 +322,18 @@ func testCloseInFlight(t *testing.T, r wazero.Runtime) {
var importingCode, importedCode *wazero.CompiledCode
var imported, importing api.Module
var err error
closeAndReturn := func(x uint32) uint32 {
closeAndReturn := func(ctx context.Context, x uint32) uint32 {
if tc.closeImporting != 0 {
require.NoError(t, importing.CloseWithExitCode(tc.closeImporting))
require.NoError(t, importing.CloseWithExitCode(ctx, tc.closeImporting))
}
if tc.closeImported != 0 {
require.NoError(t, imported.CloseWithExitCode(tc.closeImported))
require.NoError(t, imported.CloseWithExitCode(ctx, tc.closeImported))
}
if tc.closeImportedCode {
importedCode.Close()
importedCode.Close(testCtx)
}
if tc.closeImportingCode {
importingCode.Close()
importingCode.Close(testCtx)
}
return x
}
@@ -345,7 +345,7 @@ func testCloseInFlight(t *testing.T, r wazero.Runtime) {
imported, err = r.InstantiateModule(testCtx, importedCode)
require.NoError(t, err)
defer imported.Close()
defer imported.Close(testCtx)
// Import that module.
source := callReturnImportSource(imported.Name(), t.Name()+"-importing")
@@ -354,7 +354,7 @@ func testCloseInFlight(t *testing.T, r wazero.Runtime) {
importing, err = r.InstantiateModule(testCtx, importingCode)
require.NoError(t, err)
defer importing.Close()
defer importing.Close(testCtx)
var expectedErr error
if tc.closeImported != 0 && tc.closeImporting != 0 {
@@ -388,7 +388,7 @@ func testMemOps(t *testing.T, r wazero.Runtime) {
(export "memory" (memory 0))
)`))
require.NoError(t, err)
defer memory.Close()
defer memory.Close(testCtx)
// Check the export worked
require.Equal(t, memory.Memory(), memory.ExportedMemory("memory"))
@@ -397,7 +397,7 @@ func testMemOps(t *testing.T, r wazero.Runtime) {
results, err := memory.ExportedFunction("size").Call(testCtx)
require.NoError(t, err)
require.Zero(t, results[0])
require.Zero(t, memory.ExportedMemory("memory").Size())
require.Zero(t, memory.ExportedMemory("memory").Size(testCtx))
// Try to grow the memory by one page
results, err = memory.ExportedFunction("grow").Call(testCtx, 1)
@@ -408,7 +408,7 @@ func testMemOps(t *testing.T, r wazero.Runtime) {
results, err = memory.ExportedFunction("size").Call(testCtx)
require.NoError(t, err)
require.Equal(t, uint64(1), results[0]) // 1 page
require.Equal(t, uint32(65536), memory.Memory().Size()) // 64KB
require.Equal(t, uint32(65536), memory.Memory().Size(testCtx)) // 64KB
}
func testMultipleInstantiation(t *testing.T, r wazero.Runtime) {
@@ -422,16 +422,16 @@ func testMultipleInstantiation(t *testing.T, r wazero.Runtime) {
(export "store" (func $store))
)`))
require.NoError(t, err)
defer compiled.Close()
defer compiled.Close(testCtx)
// Instantiate multiple modules with the same source (*CompiledCode).
for i := 0; i < 100; i++ {
module, err := r.InstantiateModuleWithConfig(testCtx, compiled, wazero.NewModuleConfig().WithName(strconv.Itoa(i)))
require.NoError(t, err)
defer module.Close()
defer module.Close(testCtx)
// Ensure that compilation cache doesn't cause race on memory instance.
before, ok := module.Memory().ReadUint64Le(1)
before, ok := module.Memory().ReadUint64Le(testCtx, 1)
require.True(t, ok)
// Value must be zero as the memory must not be affected by the previously instantiated modules.
require.Zero(t, before)
@@ -443,7 +443,7 @@ func testMultipleInstantiation(t *testing.T, r wazero.Runtime) {
require.NoError(t, err)
// After the call, the value must be set properly.
after, ok := module.Memory().ReadUint64Le(1)
after, ok := module.Memory().ReadUint64Le(testCtx, 1)
require.True(t, ok)
require.Equal(t, uint64(1000), after)
}

View File

@@ -31,7 +31,7 @@ func TestEngineInterpreter_hammer(t *testing.T) {
func closeImportingModuleWhileInUse(t *testing.T, r wazero.Runtime) {
closeModuleWhileInUse(t, r, func(imported, importing api.Module) (api.Module, api.Module) {
// Close the importing module, despite calls being in-flight.
require.NoError(t, importing.Close())
require.NoError(t, importing.Close(testCtx))
// Prove a module can be redefined even with in-flight calls.
source := callReturnImportSource(imported.Name(), importing.Name())
@@ -44,8 +44,8 @@ func closeImportingModuleWhileInUse(t *testing.T, r wazero.Runtime) {
func closeImportedModuleWhileInUse(t *testing.T, r wazero.Runtime) {
closeModuleWhileInUse(t, r, func(imported, importing api.Module) (api.Module, api.Module) {
// Close the importing and imported module, despite calls being in-flight.
require.NoError(t, importing.Close())
require.NoError(t, imported.Close())
require.NoError(t, importing.Close(testCtx))
require.NoError(t, imported.Close(testCtx))
// Redefine the imported module, with a function that no longer blocks.
imported, err := r.NewModuleBuilder(imported.Name()).ExportFunction("return_input", func(x uint32) uint32 {
@@ -80,13 +80,13 @@ func closeModuleWhileInUse(t *testing.T, r wazero.Runtime, closeFn func(imported
imported, err := r.NewModuleBuilder(t.Name()+"-imported").
ExportFunction("return_input", blockAndReturn).Instantiate(testCtx)
require.NoError(t, err)
defer imported.Close()
defer imported.Close(testCtx)
// Import that module.
source := callReturnImportSource(imported.Name(), t.Name()+"-importing")
importing, err := r.InstantiateModuleFromCode(testCtx, source)
require.NoError(t, err)
defer importing.Close()
defer importing.Close(testCtx)
// As this is a blocking function call, only run 1 per goroutine.
i := importing // pin the module used inside goroutines
@@ -99,8 +99,8 @@ func closeModuleWhileInUse(t *testing.T, r wazero.Runtime, closeFn func(imported
calls.Add(-P)
})
// As references may have changed, ensure we close both.
defer imported.Close()
defer importing.Close()
defer imported.Close(testCtx)
defer importing.Close(testCtx)
if t.Failed() {
return // At least one test failed, so return now.
}

View File

@@ -39,7 +39,7 @@ func testMultiValue(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig)
r := wazero.NewRuntimeWithConfig(newRuntimeConfig().WithFeatureMultiValue(true))
module, err := r.InstantiateModuleFromCode(testCtx, multiValueWasm)
require.NoError(t, err)
defer module.Close()
defer module.Close(testCtx)
swap := module.ExportedFunction("swap")
results, err := swap.Call(testCtx, 100, 200)
@@ -92,7 +92,7 @@ var brWasm []byte
func testBr(t *testing.T, r wazero.Runtime) {
module, err := r.InstantiateModuleFromCode(testCtx, brWasm)
require.NoError(t, err)
defer module.Close()
defer module.Close(testCtx)
testFunctions(t, module, []funcTest{
{name: "type-i32-i32"}, {name: "type-i64-i64"}, {name: "type-f32-f32"}, {name: "type-f64-f64"},
@@ -115,7 +115,7 @@ var callWasm []byte
func testCall(t *testing.T, r wazero.Runtime) {
module, err := r.InstantiateModuleFromCode(testCtx, callWasm)
require.NoError(t, err)
defer module.Close()
defer module.Close(testCtx)
testFunctions(t, module, []funcTest{
{name: "type-i32-i64", expected: []uint64{0x132, 0x164}},
@@ -136,7 +136,7 @@ var callIndirectWasm []byte
func testCallIndirect(t *testing.T, r wazero.Runtime) {
module, err := r.InstantiateModuleFromCode(testCtx, callIndirectWasm)
require.NoError(t, err)
defer module.Close()
defer module.Close(testCtx)
testFunctions(t, module, []funcTest{
{name: "type-f64-i32", expected: []uint64{api.EncodeF64(0xf64), 32}},
@@ -158,7 +158,7 @@ var facWasm []byte
func testFac(t *testing.T, r wazero.Runtime) {
module, err := r.InstantiateModuleFromCode(testCtx, facWasm)
require.NoError(t, err)
defer module.Close()
defer module.Close(testCtx)
fac := module.ExportedFunction("fac-ssa")
results, err := fac.Call(testCtx, 25)
@@ -173,7 +173,7 @@ var funcWasm []byte
func testFunc(t *testing.T, r wazero.Runtime) {
module, err := r.InstantiateModuleFromCode(testCtx, funcWasm)
require.NoError(t, err)
defer module.Close()
defer module.Close(testCtx)
testFunctions(t, module, []funcTest{
{name: "value-i32-f64", expected: []uint64{77, api.EncodeF64(7)}},
@@ -221,7 +221,7 @@ var ifWasm []byte
func testIf(t *testing.T, r wazero.Runtime) {
module, err := r.InstantiateModuleFromCode(testCtx, ifWasm)
require.NoError(t, err)
defer module.Close()
defer module.Close(testCtx)
testFunctions(t, module, []funcTest{
{name: "multi", params: []uint64{0}, expected: []uint64{9, api.EncodeI32(-1)}},
@@ -273,7 +273,7 @@ var loopWasm []byte
func testLoop(t *testing.T, r wazero.Runtime) {
module, err := r.InstantiateModuleFromCode(testCtx, loopWasm)
require.NoError(t, err)
defer module.Close()
defer module.Close(testCtx)
testFunctions(t, module, []funcTest{
{name: "as-binary-operands", expected: []uint64{12}},

View File

@@ -389,7 +389,7 @@ func runTest(t *testing.T, newEngine func(wasm.Features) wasm.Engine) {
expType = wasm.ValueTypeF64
}
require.Equal(t, expType, global.Type(), msg)
require.Equal(t, exps[0], global.Get(), msg)
require.Equal(t, exps[0], global.Get(testCtx), msg)
default:
t.Fatalf("unsupported action type type: %v", c)
}

View File

@@ -76,7 +76,7 @@ func benchmarkCompile(b *testing.B, rtCfg *runtimeConfig) {
if err := rt.Compile(testCtx, rtCfg); err != nil {
b.Fatal(err)
}
if err := rt.Close(); err != nil {
if err := rt.Close(testCtx); err != nil {
b.Fatal(err)
}
}
@@ -92,7 +92,7 @@ func benchmarkInstantiate(b *testing.B, rtCfg *runtimeConfig) {
if err := rt.Compile(testCtx, rtCfg); err != nil {
b.Fatal(err)
}
defer rt.Close()
defer rt.Close(testCtx)
b.ResetTimer()
for i := 0; i < b.N; i++ {
@@ -100,7 +100,7 @@ func benchmarkInstantiate(b *testing.B, rtCfg *runtimeConfig) {
if err != nil {
b.Fatal(err)
}
err = mod.Close()
err = mod.Close(testCtx)
if err != nil {
b.Fatal(err)
}
@@ -127,12 +127,12 @@ func benchmarkFn(rt runtime, rtCfg *runtimeConfig, call func(module) (uint64, er
if err := rt.Compile(testCtx, rtCfg); err != nil {
b.Fatal(err)
}
defer rt.Close()
defer rt.Close(testCtx)
mod, err := rt.Instantiate(testCtx, rtCfg)
if err != nil {
b.Fatal(err)
}
defer mod.Close()
defer mod.Close(testCtx)
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, err := call(mod); err != nil {
@@ -153,7 +153,7 @@ func testCallFn(rt runtime, rtCfg *runtimeConfig, testCall func(*testing.T, modu
return func(t *testing.T) {
err := rt.Compile(testCtx, rtCfg)
require.NoError(t, err)
defer rt.Close()
defer rt.Close(testCtx)
// Ensure the module can be re-instantiated times, even if not all runtimes allow renaming.
for i := 0; i < 10; i++ {
@@ -165,7 +165,7 @@ func testCallFn(rt runtime, rtCfg *runtimeConfig, testCall func(*testing.T, modu
testCall(t, m)
}
require.NoError(t, m.Close())
require.NoError(t, m.Close(testCtx))
}
}
}

View File

@@ -118,12 +118,12 @@ func TestExampleUpToDate(t *testing.T) {
// Add WASI to satisfy import tests
wm, err := wasi.InstantiateSnapshotPreview1(testCtx, r)
require.NoError(t, err)
defer wm.Close()
defer wm.Close(testCtx)
// Decode and instantiate the module
module, err := r.InstantiateModuleFromCode(testCtx, exampleBinary)
require.NoError(t, err)
defer module.Close()
defer module.Close(testCtx)
// Call the swap function as a smoke test
results, err := module.ExportedFunction("swap").Call(testCtx, 1, 2)

View File

@@ -3,7 +3,6 @@ package vs
import (
"context"
"fmt"
"io"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
@@ -16,14 +15,14 @@ type runtimeConfig struct {
}
type runtime interface {
Compile(ctx context.Context, cfg *runtimeConfig) error
Instantiate(ctx context.Context, cfg *runtimeConfig) (module, error)
io.Closer
Compile(context.Context, *runtimeConfig) error
Instantiate(context.Context, *runtimeConfig) (module, error)
Close(context.Context) error
}
type module interface {
CallI64_I64(ctx context.Context, funcName string, param uint64) (uint64, error)
io.Closer
Close(context.Context) error
}
func newWazeroInterpreterRuntime() runtime {
@@ -72,9 +71,9 @@ func (r *wazeroRuntime) Instantiate(ctx context.Context, cfg *runtimeConfig) (mo
return
}
func (r *wazeroRuntime) Close() (err error) {
func (r *wazeroRuntime) Close(ctx context.Context) (err error) {
if compiled := r.compiled; compiled != nil {
err = compiled.Close()
err = compiled.Close(ctx)
}
r.compiled = nil
return
@@ -89,9 +88,9 @@ func (m *wazeroModule) CallI64_I64(ctx context.Context, funcName string, param u
return 0, nil
}
func (m *wazeroModule) Close() (err error) {
func (m *wazeroModule) Close(ctx context.Context) (err error) {
if mod := m.mod; mod != nil {
err = mod.Close()
err = mod.Close(ctx)
}
m.mod = nil
return

View File

@@ -59,7 +59,7 @@ func (r *wasm3Runtime) Instantiate(_ context.Context, cfg *runtimeConfig) (mod m
return
}
func (r *wasm3Runtime) Close() error {
func (r *wasm3Runtime) Close(_ context.Context) error {
if r := r.runtime; r != nil {
r.Destroy()
}
@@ -77,7 +77,7 @@ func (m *wasm3Module) CallI64_I64(_ context.Context, funcName string, param uint
}
}
func (m *wasm3Module) Close() error {
func (m *wasm3Module) Close(_ context.Context) error {
// module can't be destroyed
m.module = nil
m.funcs = nil

View File

@@ -59,7 +59,7 @@ func (r *wasmedgeRuntime) Instantiate(_ context.Context, cfg *runtimeConfig) (mo
return
}
func (r *wasmedgeRuntime) Close() error {
func (r *wasmedgeRuntime) Close(_ context.Context) error {
if conf := r.conf; conf != nil {
conf.Release()
}
@@ -75,7 +75,7 @@ func (m *wasmedgeModule) CallI64_I64(_ context.Context, funcName string, param u
}
}
func (m *wasmedgeModule) Close() error {
func (m *wasmedgeModule) Close(_ context.Context) error {
if vm := m.vm; vm != nil {
vm.Release()
}

View File

@@ -61,7 +61,7 @@ func (r *wasmerRuntime) Instantiate(_ context.Context, cfg *runtimeConfig) (mod
return
}
func (r *wasmerRuntime) Close() error {
func (r *wasmerRuntime) Close(_ context.Context) error {
r.engine = nil
return nil
}
@@ -75,7 +75,7 @@ func (m *wasmerModule) CallI64_I64(_ context.Context, funcName string, param uin
}
}
func (m *wasmerModule) Close() error {
func (m *wasmerModule) Close(_ context.Context) error {
if instance := m.instance; instance != nil {
instance.Close()
}

View File

@@ -65,7 +65,7 @@ func (r *wasmtimeRuntime) Instantiate(_ context.Context, cfg *runtimeConfig) (mo
return
}
func (r *wasmtimeRuntime) Close() error {
func (r *wasmtimeRuntime) Close(_ context.Context) error {
r.engine = nil
return nil // wasmtime only closes via finalizer
}
@@ -79,7 +79,7 @@ func (m *wasmtimeModule) CallI64_I64(_ context.Context, funcName string, param u
}
}
func (m *wasmtimeModule) Close() error {
func (m *wasmtimeModule) Close(_ context.Context) error {
m.store = nil
m.instance = nil
m.funcs = nil

View File

@@ -14,6 +14,9 @@ import (
"github.com/tetratelabs/wazero/internal/wasm/binary"
)
// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
const (
i32 = wasm.ValueTypeI32
i64 = wasm.ValueTypeI64
@@ -46,9 +49,10 @@ func TestModGen(t *testing.T) {
// Encode the generated module (*wasm.Module) as binary.
bin := binary.EncodeModule(m)
// Pass the generated binary into our compilers.
code, err := runtime.CompileModule(context.Background(), bin)
code, err := runtime.CompileModule(testCtx, bin)
require.NoError(t, err)
err = code.Close(testCtx)
require.NoError(t, err)
code.Close()
})
}
}

View File

@@ -74,12 +74,14 @@ func (m *CallContext) String() string {
}
// Close implements the same method as documented on api.Module.
func (m *CallContext) Close() (err error) {
return m.CloseWithExitCode(0)
func (m *CallContext) Close(ctx context.Context) (err error) {
return m.CloseWithExitCode(ctx, 0)
}
// CloseWithExitCode implements the same method as documented on api.Module.
func (m *CallContext) CloseWithExitCode(exitCode uint32) (err error) {
func (m *CallContext) CloseWithExitCode(_ context.Context, exitCode uint32) (err error) {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
closed := uint64(1) + uint64(exitCode)<<32 // Store exitCode as high-order bits.
if !atomic.CompareAndSwapUint64(m.closed, 0, closed) {
return nil
@@ -91,12 +93,12 @@ func (m *CallContext) CloseWithExitCode(exitCode uint32) (err error) {
return
}
// Memory implements api.Module Memory
// Memory implements the same method as documented on api.Module.
func (m *CallContext) Memory() api.Memory {
return m.module.Memory
}
// ExportedMemory implements api.Module ExportedMemory
// ExportedMemory implements the same method as documented on api.Module.
func (m *CallContext) ExportedMemory(name string) api.Memory {
exp, err := m.module.getExport(name, ExternTypeMemory)
if err != nil {
@@ -105,7 +107,7 @@ func (m *CallContext) ExportedMemory(name string) api.Memory {
return exp.Memory
}
// ExportedFunction implements api.Module ExportedFunction
// ExportedFunction implements the same method as documented on api.Module.
func (m *CallContext) ExportedFunction(name string) api.Function {
exp, err := m.module.getExport(name, ExternTypeFunc)
if err != nil {
@@ -124,17 +126,17 @@ type importedFn struct {
importedFn *FunctionInstance
}
// ParamTypes implements the same method as documented on api.Function
// ParamTypes implements the same method as documented on api.Function.
func (f *importedFn) ParamTypes() []api.ValueType {
return f.importedFn.ParamTypes()
}
// ResultTypes implements the same method as documented on api.Function
// ResultTypes implements the same method as documented on api.Function.
func (f *importedFn) ResultTypes() []api.ValueType {
return f.importedFn.ResultTypes()
}
// Call implements the same method as documented on api.Function
// Call implements the same method as documented on api.Function.
func (f *importedFn) Call(ctx context.Context, params ...uint64) (ret []uint64, err error) {
if ctx == nil {
ctx = context.Background()
@@ -143,17 +145,17 @@ func (f *importedFn) Call(ctx context.Context, params ...uint64) (ret []uint64,
return f.importedFn.Module.Engine.Call(ctx, mod, f.importedFn, params...)
}
// ParamTypes implements the same method as documented on api.Function
// ParamTypes implements the same method as documented on api.Function.
func (f *FunctionInstance) ParamTypes() []api.ValueType {
return f.Type.Params
}
// ResultTypes implements the same method as documented on api.Function
// ResultTypes implements the same method as documented on api.Function.
func (f *FunctionInstance) ResultTypes() []api.ValueType {
return f.Type.Results
}
// Call implements the same method as documented on api.Function
// Call implements the same method as documented on api.Function.
func (f *FunctionInstance) Call(ctx context.Context, params ...uint64) (ret []uint64, err error) {
if ctx == nil {
ctx = context.Background()
@@ -162,7 +164,7 @@ func (f *FunctionInstance) Call(ctx context.Context, params ...uint64) (ret []ui
return mod.Engine.Call(ctx, mod.CallCtx, f, params...)
}
// ExportedGlobal implements api.Module ExportedGlobal
// ExportedGlobal implements the same method as documented on api.Module.
func (m *CallContext) ExportedGlobal(name string) api.Global {
exp, err := m.module.getExport(name, ExternTypeGlobal)
if err != nil {

View File

@@ -2,6 +2,7 @@ package wasm
import (
"context"
"fmt"
"path"
"testing"
@@ -80,7 +81,7 @@ func TestCallContext_String(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
// Ensure paths that can create the host module can see the name.
m, err := s.Instantiate(context.Background(), &Module{}, tc.moduleName, nil)
defer m.Close() //nolint
defer m.Close(testCtx) //nolint
require.NoError(t, err)
require.Equal(t, tc.expected, m.String())
@@ -92,24 +93,52 @@ func TestCallContext_String(t *testing.T) {
func TestCallContext_Close(t *testing.T) {
s := newStore()
t.Run("calls store.CloseWithExitCode(module.name)", func(t *testing.T) {
tests := []struct {
name string
closer func(context.Context, *CallContext) error
expectedClosed uint64
}{
{
name: "Close()",
closer: func(ctx context.Context, callContext *CallContext) error {
return callContext.Close(ctx)
},
expectedClosed: uint64(1),
},
{
name: "CloseWithExitCode(255)",
closer: func(ctx context.Context, callContext *CallContext) error {
return callContext.CloseWithExitCode(ctx, 255)
},
expectedClosed: uint64(255)<<32 + 1,
},
}
for _, tt := range tests {
tc := tt
t.Run(fmt.Sprintf("%s calls store.CloseWithExitCode(module.name))", tc.name), func(t *testing.T) {
for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil!
moduleName := t.Name()
m, err := s.Instantiate(context.Background(), &Module{}, moduleName, nil)
m, err := s.Instantiate(ctx, &Module{}, moduleName, nil)
require.NoError(t, err)
// We use side effects to determine if Close in fact called store.CloseWithExitCode (without repeating store_test.go).
// One side effect of store.CloseWithExitCode is that the moduleName can no longer be looked up. Verify our base case.
// We use side effects to see if Close called store.CloseWithExitCode (without repeating store_test.go).
// One side effect of store.CloseWithExitCode is that the moduleName can no longer be looked up.
require.Equal(t, s.Module(moduleName), m)
// Closing should not err.
require.NoError(t, m.Close())
require.NoError(t, tc.closer(ctx, m))
require.Equal(t, tc.expectedClosed, *m.closed)
// Verify our intended side-effect
require.Nil(t, s.Module(moduleName))
// Verify no error closing again.
require.NoError(t, m.Close())
require.NoError(t, tc.closer(ctx, m))
}
})
}
t.Run("calls SysContext.Close()", func(t *testing.T) {
tempDir := t.TempDir()
@@ -139,12 +168,12 @@ func TestCallContext_Close(t *testing.T) {
require.True(t, len(sys.openedFiles) > 0, "sys.openedFiles was empty")
// Closing should not err.
require.NoError(t, m.Close())
require.NoError(t, m.Close(testCtx))
// Verify our intended side-effect
require.Equal(t, 0, len(sys.openedFiles), "expected no opened files")
// Verify no error closing again.
require.NoError(t, m.Close())
require.NoError(t, m.Close(testCtx))
})
}

View File

@@ -1,6 +1,7 @@
package wasm
import (
"context"
"fmt"
"github.com/tetratelabs/wazero/api"
@@ -10,21 +11,25 @@ type mutableGlobal struct {
g *GlobalInstance
}
// compile-time check to ensure mutableGlobal is a api.Global
// compile-time check to ensure mutableGlobal is a api.Global.
var _ api.Global = &mutableGlobal{}
// Type implements api.Global Type
// Type implements the same method as documented on api.Global.
func (g *mutableGlobal) Type() api.ValueType {
return g.g.Type.ValType
}
// Get implements api.Global Get
func (g *mutableGlobal) Get() uint64 {
// Get implements the same method as documented on api.Global.
func (g *mutableGlobal) Get(_ context.Context) uint64 {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
return g.g.Val
}
// Set implements api.MutableGlobal Set
func (g *mutableGlobal) Set(v uint64) {
// Set implements the same method as documented on api.MutableGlobal.
func (g *mutableGlobal) Set(_ context.Context, v uint64) {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
g.g.Val = v
}
@@ -32,11 +37,11 @@ func (g *mutableGlobal) Set(v uint64) {
func (g *mutableGlobal) String() string {
switch g.Type() {
case ValueTypeI32, ValueTypeI64:
return fmt.Sprintf("global(%d)", g.Get())
return fmt.Sprintf("global(%d)", g.Get(context.Background()))
case ValueTypeF32:
return fmt.Sprintf("global(%f)", api.DecodeF32(g.Get()))
return fmt.Sprintf("global(%f)", api.DecodeF32(g.Get(context.Background())))
case ValueTypeF64:
return fmt.Sprintf("global(%f)", api.DecodeF64(g.Get()))
return fmt.Sprintf("global(%f)", api.DecodeF64(g.Get(context.Background())))
default:
panic(fmt.Errorf("BUG: unknown value type %X", g.Type()))
}
@@ -47,13 +52,15 @@ type globalI32 uint64
// compile-time check to ensure globalI32 is a api.Global
var _ api.Global = globalI32(0)
// Type implements api.Global Type
// Type implements the same method as documented on api.Global.
func (g globalI32) Type() api.ValueType {
return ValueTypeI32
}
// Get implements api.Global Get
func (g globalI32) Get() uint64 {
// Get implements the same method as documented on api.Global.
func (g globalI32) Get(_ context.Context) uint64 {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
return uint64(g)
}
@@ -67,13 +74,15 @@ type globalI64 uint64
// compile-time check to ensure globalI64 is a api.Global
var _ api.Global = globalI64(0)
// Type implements api.Global Type
// Type implements the same method as documented on api.Global.
func (g globalI64) Type() api.ValueType {
return ValueTypeI64
}
// Get implements api.Global Get
func (g globalI64) Get() uint64 {
// Get implements the same method as documented on api.Global.
func (g globalI64) Get(_ context.Context) uint64 {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
return uint64(g)
}
@@ -87,19 +96,21 @@ type globalF32 uint64
// compile-time check to ensure globalF32 is a api.Global
var _ api.Global = globalF32(0)
// Type implements api.Global Type
// Type implements the same method as documented on api.Global.
func (g globalF32) Type() api.ValueType {
return ValueTypeF32
}
// Get implements api.Global Get
func (g globalF32) Get() uint64 {
// Get implements the same method as documented on api.Global.
func (g globalF32) Get(_ context.Context) uint64 {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
return uint64(g)
}
// String implements fmt.Stringer
func (g globalF32) String() string {
return fmt.Sprintf("global(%f)", api.DecodeF32(g.Get()))
return fmt.Sprintf("global(%f)", api.DecodeF32(g.Get(context.Background())))
}
type globalF64 uint64
@@ -107,17 +118,19 @@ type globalF64 uint64
// compile-time check to ensure globalF64 is a api.Global
var _ api.Global = globalF64(0)
// Type implements api.Global Type
// Type implements the same method as documented on api.Global.
func (g globalF64) Type() api.ValueType {
return ValueTypeF64
}
// Get implements api.Global Get
func (g globalF64) Get() uint64 {
// Get implements the same method as documented on api.Global.
func (g globalF64) Get(_ context.Context) uint64 {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
return uint64(g)
}
// String implements fmt.Stringer
func (g globalF64) String() string {
return fmt.Sprintf("global(%f)", api.DecodeF64(g.Get()))
return fmt.Sprintf("global(%f)", api.DecodeF64(g.Get(context.Background())))
}

View File

@@ -126,15 +126,20 @@ func TestGlobalTypes(t *testing.T) {
tc := tt
t.Run(tc.name, func(t *testing.T) {
for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil!
require.Equal(t, tc.expectedType, tc.global.Type())
require.Equal(t, tc.expectedVal, tc.global.Get())
require.Equal(t, tc.expectedVal, tc.global.Get(ctx))
require.Equal(t, tc.expectedString, tc.global.String())
mutable, ok := tc.global.(api.MutableGlobal)
require.Equal(t, tc.expectedMutable, ok)
if ok {
mutable.Set(2)
require.Equal(t, uint64(2), tc.global.Get())
mutable.Set(ctx, 2)
require.Equal(t, uint64(2), tc.global.Get(ctx))
mutable.Set(ctx, tc.expectedVal) // Set it back!
require.Equal(t, tc.expectedVal, tc.global.Get(ctx))
}
}
})
}

View File

@@ -2,7 +2,6 @@ package interpreter
import (
"context"
"encoding/binary"
"fmt"
"math"
"math/bits"
@@ -164,8 +163,15 @@ func (c *code) instantiate(f *wasm.FunctionInstance) *function {
}
}
// Non-interface union of all the wazeroir operations.
// interpreterOp is the compilation (engine.lowerIR) result of a wazeroir.Operation.
//
// Not all operations result in an interpreterOp, e.g. wazeroir.OperationI32ReinterpretFromF32, and some operations are
// more complex than others, e.g. wazeroir.OperationBrTable.
//
// Note: This is a form of union type as it can store fields needed for any operation. Hence, most fields are opaque and
// only relevant when in context of its kind.
type interpreterOp struct {
// kind determines how to interpret the other fields in this struct.
kind wazeroir.OperationKind
b1, b2 byte
b3 bool
@@ -181,7 +187,7 @@ func (e *engine) CompileModule(ctx context.Context, module *wasm.Module) error {
funcs := make([]*code, 0, len(module.FunctionSection))
if module.IsHostModule() {
// If this is the host module, there's nothing to do as the runtime reprsentation of
// If this is the host module, there's nothing to do as the runtime representation of
// host function in interpreter is its Go function itself as opposed to Wasm functions,
// which need to be compiled down to wazeroir.
for _, hf := range module.HostFunctionSection {
@@ -679,131 +685,131 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, callCtx *wasm.CallCont
}
case wazeroir.OperationKindGlobalGet:
{
g := globals[op.us[0]]
g := globals[op.us[0]] // TODO: Not yet traceable as it doesn't use the types in global.go
ce.pushValue(g.Val)
frame.pc++
}
case wazeroir.OperationKindGlobalSet:
{
g := globals[op.us[0]]
g := globals[op.us[0]] // TODO: Not yet traceable as it doesn't use the types in global.go
g.Val = ce.popValue()
frame.pc++
}
case wazeroir.OperationKindLoad:
{
base := op.us[1] + ce.popValue()
offset := ce.popMemoryOffset(op)
switch wazeroir.UnsignedType(op.b1) {
case wazeroir.UnsignedTypeI32, wazeroir.UnsignedTypeF32:
if uint64(len(memoryInst.Buffer)) < base+4 {
if val, ok := memoryInst.ReadUint32Le(ctx, offset); !ok {
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
} else {
ce.pushValue(uint64(val))
}
ce.pushValue(uint64(binary.LittleEndian.Uint32(memoryInst.Buffer[base:])))
case wazeroir.UnsignedTypeI64, wazeroir.UnsignedTypeF64:
if uint64(len(memoryInst.Buffer)) < base+8 {
if val, ok := memoryInst.ReadUint64Le(ctx, offset); !ok {
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
} else {
ce.pushValue(val)
}
ce.pushValue(binary.LittleEndian.Uint64(memoryInst.Buffer[base:]))
}
frame.pc++
}
case wazeroir.OperationKindLoad8:
{
base := op.us[1] + ce.popValue()
if uint64(len(memoryInst.Buffer)) < base+1 {
val, ok := memoryInst.ReadByte(ctx, ce.popMemoryOffset(op))
if !ok {
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
}
switch wazeroir.SignedInt(op.b1) {
case wazeroir.SignedInt32, wazeroir.SignedInt64:
ce.pushValue(uint64(int8(memoryInst.Buffer[base])))
ce.pushValue(uint64(int8(val)))
case wazeroir.SignedUint32, wazeroir.SignedUint64:
ce.pushValue(uint64(uint8(memoryInst.Buffer[base])))
ce.pushValue(uint64(val))
}
frame.pc++
}
case wazeroir.OperationKindLoad16:
{
base := op.us[1] + ce.popValue()
if uint64(len(memoryInst.Buffer)) < base+2 {
val, ok := memoryInst.ReadUint16Le(ctx, ce.popMemoryOffset(op))
if !ok {
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
}
switch wazeroir.SignedInt(op.b1) {
case wazeroir.SignedInt32, wazeroir.SignedInt64:
ce.pushValue(uint64(int16(binary.LittleEndian.Uint16(memoryInst.Buffer[base:]))))
ce.pushValue(uint64(int16(val)))
case wazeroir.SignedUint32, wazeroir.SignedUint64:
ce.pushValue(uint64(binary.LittleEndian.Uint16(memoryInst.Buffer[base:])))
ce.pushValue(uint64(val))
}
frame.pc++
}
case wazeroir.OperationKindLoad32:
{
base := op.us[1] + ce.popValue()
if uint64(len(memoryInst.Buffer)) < base+4 {
val, ok := memoryInst.ReadUint32Le(ctx, ce.popMemoryOffset(op))
if !ok {
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
}
if op.b1 == 1 {
ce.pushValue(uint64(int32(binary.LittleEndian.Uint32(memoryInst.Buffer[base:]))))
if op.b1 == 1 { // Signed
ce.pushValue(uint64(int32(val)))
} else {
ce.pushValue(uint64(binary.LittleEndian.Uint32(memoryInst.Buffer[base:])))
ce.pushValue(uint64(val))
}
frame.pc++
}
case wazeroir.OperationKindStore:
{
val := ce.popValue()
base := op.us[1] + ce.popValue()
offset := ce.popMemoryOffset(op)
switch wazeroir.UnsignedType(op.b1) {
case wazeroir.UnsignedTypeI32, wazeroir.UnsignedTypeF32:
if uint64(len(memoryInst.Buffer)) < base+4 {
if !memoryInst.WriteUint32Le(ctx, offset, uint32(val)) {
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
}
binary.LittleEndian.PutUint32(memoryInst.Buffer[base:], uint32(val))
case wazeroir.UnsignedTypeI64, wazeroir.UnsignedTypeF64:
if uint64(len(memoryInst.Buffer)) < base+8 {
if !memoryInst.WriteUint64Le(ctx, offset, val) {
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
}
binary.LittleEndian.PutUint64(memoryInst.Buffer[base:], val)
}
frame.pc++
}
case wazeroir.OperationKindStore8:
{
val := byte(ce.popValue())
base := op.us[1] + ce.popValue()
if uint64(len(memoryInst.Buffer)) < base+1 {
offset := ce.popMemoryOffset(op)
if !memoryInst.WriteByte(ctx, offset, val) {
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
}
memoryInst.Buffer[base] = val
frame.pc++
}
case wazeroir.OperationKindStore16:
{
val := uint16(ce.popValue())
base := op.us[1] + ce.popValue()
if uint64(len(memoryInst.Buffer)) < base+2 {
offset := ce.popMemoryOffset(op)
if !memoryInst.WriteUint16Le(ctx, offset, val) {
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
}
binary.LittleEndian.PutUint16(memoryInst.Buffer[base:], val)
frame.pc++
}
case wazeroir.OperationKindStore32:
{
val := uint32(ce.popValue())
base := op.us[1] + ce.popValue()
if uint64(len(memoryInst.Buffer)) < base+4 {
offset := ce.popMemoryOffset(op)
if !memoryInst.WriteUint32Le(ctx, offset, val) {
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
}
binary.LittleEndian.PutUint32(memoryInst.Buffer[base:], val)
frame.pc++
}
case wazeroir.OperationKindMemorySize:
{
ce.pushValue(uint64(memoryInst.PageSize()))
ce.pushValue(uint64(memoryInst.PageSize(ctx)))
frame.pc++
}
case wazeroir.OperationKindMemoryGrow:
{
n := ce.popValue()
res := memoryInst.Grow(uint32(n))
res := memoryInst.Grow(ctx, uint32(n))
ce.pushValue(uint64(res))
frame.pc++
}
@@ -1670,6 +1676,17 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, callCtx *wasm.CallCont
ce.popFrame()
}
// popMemoryOffset takes a memory offset off the stack for use in load and store instructions.
// As the top of stack value is 64-bit, this ensures it is in range before returning it.
func (ce *callEngine) popMemoryOffset(op *interpreterOp) uint32 {
// TODO: Document what 'us' is and why we expect to look at value 1.
offset := op.us[1] + ce.popValue()
if offset > math.MaxUint32 {
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
}
return uint32(offset)
}
func (ce *callEngine) callGoFuncWithStack(ctx context.Context, callCtx *wasm.CallContext, f *function) {
params := wasm.PopGoFuncParams(f.source, ce.popValue)
results := ce.callGoFunc(ctx, callCtx, f, params)

View File

@@ -690,7 +690,7 @@ jitentry:
switch ce.exitContext.builtinFunctionCallIndex {
case builtinFunctionIndexMemoryGrow:
callercode := ce.callFrameTop().function
ce.builtinFunctionMemoryGrow(callercode.source.Module.Memory)
ce.builtinFunctionMemoryGrow(ctx, callercode.source.Module.Memory)
case builtinFunctionIndexGrowValueStack:
callercode := ce.callFrameTop().function
ce.builtinFunctionGrowValueStack(callercode.stackPointerCeil)
@@ -740,10 +740,10 @@ func (ce *callEngine) builtinFunctionGrowCallFrameStack() {
ce.globalContext.callFrameStackElementZeroAddress = stackSliceHeader.Data
}
func (ce *callEngine) builtinFunctionMemoryGrow(mem *wasm.MemoryInstance) {
func (ce *callEngine) builtinFunctionMemoryGrow(ctx context.Context, mem *wasm.MemoryInstance) {
newPages := ce.popValue()
res := mem.Grow(uint32(newPages))
res := mem.Grow(ctx, uint32(newPages))
ce.pushValue(uint64(res))
// Update the moduleContext fields as they become stale after the update ^^.
@@ -782,7 +782,7 @@ func compileWasmFunction(enabledFeatures wasm.Features, ir *wazeroir.Compilation
var skip bool
for _, op := range ir.Operations {
// Compiler determines whether or not skip the entire label.
// Compiler determines whether skip the entire label.
// For example, if the label doesn't have any caller,
// we don't need to generate native code at all as we never reach the region.
if op.Kind() == wazeroir.OperationKindLabel {

View File

@@ -2,6 +2,7 @@ package wasm
import (
"bytes"
"context"
"encoding/binary"
"fmt"
"math"
@@ -35,18 +36,17 @@ type MemoryInstance struct {
}
// Size implements the same method as documented on api.Memory.
func (m *MemoryInstance) Size() uint32 {
return uint32(len(m.Buffer))
}
func (m *MemoryInstance) Size(_ context.Context) uint32 {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
// hasSize returns true if Len is sufficient for sizeInBytes at the given offset.
func (m *MemoryInstance) hasSize(offset uint32, sizeInBytes uint32) bool {
return uint64(offset)+uint64(sizeInBytes) <= uint64(m.Size()) // uint64 prevents overflow on add
return m.size()
}
// IndexByte implements the same method as documented on api.Memory.
func (m *MemoryInstance) IndexByte(offset uint32, c byte) (uint32, bool) {
if offset >= m.Size() {
func (m *MemoryInstance) IndexByte(_ context.Context, offset uint32, c byte) (uint32, bool) {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
if offset >= uint32(len(m.Buffer)) {
return 0, false
}
b := m.Buffer[offset:]
@@ -58,24 +58,37 @@ func (m *MemoryInstance) IndexByte(offset uint32, c byte) (uint32, bool) {
}
// ReadByte implements the same method as documented on api.Memory.
func (m *MemoryInstance) ReadByte(offset uint32) (byte, bool) {
if offset >= m.Size() {
func (m *MemoryInstance) ReadByte(_ context.Context, offset uint32) (byte, bool) {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
if offset >= m.size() {
return 0, false
}
return m.Buffer[offset], true
}
// ReadUint32Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) ReadUint32Le(offset uint32) (uint32, bool) {
if !m.hasSize(offset, 4) {
// ReadUint16Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) ReadUint16Le(_ context.Context, offset uint32) (uint16, bool) {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
if !m.hasSize(offset, 2) {
return 0, false
}
return binary.LittleEndian.Uint32(m.Buffer[offset : offset+4]), true
return binary.LittleEndian.Uint16(m.Buffer[offset : offset+2]), true
}
// ReadUint32Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) ReadUint32Le(_ context.Context, offset uint32) (uint32, bool) {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
return m.readUint32Le(offset)
}
// ReadFloat32Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) ReadFloat32Le(offset uint32) (float32, bool) {
v, ok := m.ReadUint32Le(offset)
func (m *MemoryInstance) ReadFloat32Le(_ context.Context, offset uint32) (float32, bool) {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
v, ok := m.readUint32Le(offset)
if !ok {
return 0, false
}
@@ -83,16 +96,17 @@ func (m *MemoryInstance) ReadFloat32Le(offset uint32) (float32, bool) {
}
// ReadUint64Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) ReadUint64Le(offset uint32) (uint64, bool) {
if !m.hasSize(offset, 8) {
return 0, false
}
return binary.LittleEndian.Uint64(m.Buffer[offset : offset+8]), true
func (m *MemoryInstance) ReadUint64Le(_ context.Context, offset uint32) (uint64, bool) {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
return m.readUint64Le(offset)
}
// ReadFloat64Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) ReadFloat64Le(offset uint32) (float64, bool) {
v, ok := m.ReadUint64Le(offset)
func (m *MemoryInstance) ReadFloat64Le(_ context.Context, offset uint32) (float64, bool) {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
v, ok := m.readUint64Le(offset)
if !ok {
return 0, false
}
@@ -100,7 +114,9 @@ func (m *MemoryInstance) ReadFloat64Le(offset uint32) (float64, bool) {
}
// Read implements the same method as documented on api.Memory.
func (m *MemoryInstance) Read(offset, byteCount uint32) ([]byte, bool) {
func (m *MemoryInstance) Read(_ context.Context, offset, byteCount uint32) ([]byte, bool) {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
if !m.hasSize(offset, byteCount) {
return nil, false
}
@@ -108,44 +124,58 @@ func (m *MemoryInstance) Read(offset, byteCount uint32) ([]byte, bool) {
}
// WriteByte implements the same method as documented on api.Memory.
func (m *MemoryInstance) WriteByte(offset uint32, v byte) bool {
if offset >= m.Size() {
func (m *MemoryInstance) WriteByte(_ context.Context, offset uint32, v byte) bool {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
if offset >= m.size() {
return false
}
m.Buffer[offset] = v
return true
}
// WriteUint32Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) WriteUint32Le(offset, v uint32) bool {
if !m.hasSize(offset, 4) {
// WriteUint16Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) WriteUint16Le(_ context.Context, offset uint32, v uint16) bool {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
if !m.hasSize(offset, 2) {
return false
}
binary.LittleEndian.PutUint32(m.Buffer[offset:], v)
binary.LittleEndian.PutUint16(m.Buffer[offset:], v)
return true
}
// WriteUint32Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) WriteUint32Le(_ context.Context, offset, v uint32) bool {
return m.writeUint32Le(offset, v)
}
// WriteFloat32Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) WriteFloat32Le(offset uint32, v float32) bool {
return m.WriteUint32Le(offset, math.Float32bits(v))
func (m *MemoryInstance) WriteFloat32Le(_ context.Context, offset uint32, v float32) bool {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
return m.writeUint32Le(offset, math.Float32bits(v))
}
// WriteUint64Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) WriteUint64Le(offset uint32, v uint64) bool {
if !m.hasSize(offset, 8) {
return false
}
binary.LittleEndian.PutUint64(m.Buffer[offset:], v)
return true
func (m *MemoryInstance) WriteUint64Le(_ context.Context, offset uint32, v uint64) bool {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
return m.writeUint64Le(offset, v)
}
// WriteFloat64Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) WriteFloat64Le(offset uint32, v float64) bool {
return m.WriteUint64Le(offset, math.Float64bits(v))
func (m *MemoryInstance) WriteFloat64Le(_ context.Context, offset uint32, v float64) bool {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
return m.writeUint64Le(offset, math.Float64bits(v))
}
// Write implements the same method as documented on api.Memory.
func (m *MemoryInstance) Write(offset uint32, val []byte) bool {
func (m *MemoryInstance) Write(_ context.Context, offset uint32, val []byte) bool {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
if !m.hasSize(offset, uint32(len(val))) {
return false
}
@@ -158,17 +188,14 @@ func MemoryPagesToBytesNum(pages uint32) (bytesNum uint64) {
return uint64(pages) << MemoryPageSizeInBits
}
// memoryBytesNumToPages converts the given number of bytes into the number of pages.
func memoryBytesNumToPages(bytesNum uint64) (pages uint32) {
return uint32(bytesNum >> MemoryPageSizeInBits)
}
// Grow extends the memory buffer by "newPages" * memoryPageSize.
// The logic here is described in https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem.
//
// Returns -1 if the operation resulted in exceeding the maximum memory pages.
// Otherwise, returns the prior memory size after growing the memory buffer.
func (m *MemoryInstance) Grow(newPages uint32) (result uint32) {
func (m *MemoryInstance) Grow(_ context.Context, newPages uint32) (result uint32) {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
currentPages := memoryBytesNumToPages(uint64(len(m.Buffer)))
// If exceeds the max of memory size, we push -1 according to the spec.
@@ -182,7 +209,9 @@ func (m *MemoryInstance) Grow(newPages uint32) (result uint32) {
}
// PageSize returns the current memory buffer size in pages.
func (m *MemoryInstance) PageSize() (result uint32) {
func (m *MemoryInstance) PageSize(_ context.Context) (result uint32) {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
return memoryBytesNumToPages(uint64(len(m.Buffer)))
}
@@ -204,3 +233,58 @@ func PagesToUnitOfBytes(pages uint32) string {
}
return fmt.Sprintf("%d Ti", g/1024)
}
// Below are raw functions used to implement the api.Memory API:
// memoryBytesNumToPages converts the given number of bytes into the number of pages.
func memoryBytesNumToPages(bytesNum uint64) (pages uint32) {
return uint32(bytesNum >> MemoryPageSizeInBits)
}
// size returns the size in bytes of the buffer.
func (m *MemoryInstance) size() uint32 {
return uint32(len(m.Buffer))
}
// hasSize returns true if Len is sufficient for sizeInBytes at the given offset.
func (m *MemoryInstance) hasSize(offset uint32, sizeInBytes uint32) bool {
return uint64(offset)+uint64(sizeInBytes) <= uint64(len(m.Buffer)) // uint64 prevents overflow on add
}
// readUint32Le implements ReadUint32Le without using a context. This is extracted as both ints and floats are stored in
// memory as uint32le.
func (m *MemoryInstance) readUint32Le(offset uint32) (uint32, bool) {
if !m.hasSize(offset, 4) {
return 0, false
}
return binary.LittleEndian.Uint32(m.Buffer[offset : offset+4]), true
}
// readUint64Le implements ReadUint64Le without using a context. This is extracted as both ints and floats are stored in
// memory as uint64le.
func (m *MemoryInstance) readUint64Le(offset uint32) (uint64, bool) {
if !m.hasSize(offset, 8) {
return 0, false
}
return binary.LittleEndian.Uint64(m.Buffer[offset : offset+8]), true
}
// writeUint32Le implements WriteUint32Le without using a context. This is extracted as both ints and floats are stored
// in memory as uint32le.
func (m *MemoryInstance) writeUint32Le(offset uint32, v uint32) bool {
if !m.hasSize(offset, 4) {
return false
}
binary.LittleEndian.PutUint32(m.Buffer[offset:], v)
return true
}
// writeUint64Le implements WriteUint64Le without using a context. This is extracted as both ints and floats are stored
// in memory as uint64le.
func (m *MemoryInstance) writeUint64Le(offset uint32, v uint64) bool {
if !m.hasSize(offset, 8) {
return false
}
binary.LittleEndian.PutUint64(m.Buffer[offset:], v)
return true
}

View File

@@ -1,6 +1,7 @@
package wasm
import (
"context"
"math"
"testing"
@@ -26,70 +27,84 @@ func Test_MemoryBytesNumToPages(t *testing.T) {
}
func TestMemoryInstance_Grow_Size(t *testing.T) {
for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil!
max := uint32(10)
m := &MemoryInstance{Max: max, Buffer: make([]byte, 0)}
require.Equal(t, uint32(0), m.Grow(5))
require.Equal(t, uint32(5), m.PageSize())
require.Equal(t, uint32(0), m.Grow(ctx, 5))
require.Equal(t, uint32(5), m.PageSize(ctx))
// Zero page grow is well-defined, should return the current page correctly.
require.Equal(t, uint32(5), m.Grow(0))
require.Equal(t, uint32(5), m.PageSize())
require.Equal(t, uint32(5), m.Grow(4))
require.Equal(t, uint32(9), m.PageSize())
require.Equal(t, uint32(5), m.Grow(ctx, 0))
require.Equal(t, uint32(5), m.PageSize(ctx))
require.Equal(t, uint32(5), m.Grow(ctx, 4))
require.Equal(t, uint32(9), m.PageSize(ctx))
// At this point, the page size equal 9,
// so trying to grow two pages should result in failure.
require.Equal(t, int32(-1), int32(m.Grow(2)))
require.Equal(t, uint32(9), m.PageSize())
require.Equal(t, int32(-1), int32(m.Grow(ctx, 2)))
require.Equal(t, uint32(9), m.PageSize(ctx))
// But growing one page is still permitted.
require.Equal(t, uint32(9), m.Grow(1))
require.Equal(t, uint32(9), m.Grow(ctx, 1))
// Ensure that the current page size equals the max.
require.Equal(t, max, m.PageSize())
require.Equal(t, max, m.PageSize(ctx))
}
}
func TestIndexByte(t *testing.T) {
for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil!
var mem = &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1}
v, ok := mem.IndexByte(4, 16)
v, ok := mem.IndexByte(ctx, 4, 16)
require.True(t, ok)
require.Equal(t, uint32(4), v)
_, ok = mem.IndexByte(5, 16)
_, ok = mem.IndexByte(ctx, 5, 16)
require.False(t, ok)
_, ok = mem.IndexByte(9, 16)
_, ok = mem.IndexByte(ctx, 9, 16)
require.False(t, ok)
}
}
func TestReadByte(t *testing.T) {
for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil!
var mem = &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 0, 0, 0, 16}, Min: 1}
v, ok := mem.ReadByte(7)
v, ok := mem.ReadByte(ctx, 7)
require.True(t, ok)
require.Equal(t, byte(16), v)
_, ok = mem.ReadByte(8)
_, ok = mem.ReadByte(ctx, 8)
require.False(t, ok)
_, ok = mem.ReadByte(9)
_, ok = mem.ReadByte(ctx, 9)
require.False(t, ok)
}
}
func TestReadUint32Le(t *testing.T) {
for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil!
var mem = &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1}
v, ok := mem.ReadUint32Le(4)
v, ok := mem.ReadUint32Le(ctx, 4)
require.True(t, ok)
require.Equal(t, uint32(16), v)
_, ok = mem.ReadUint32Le(5)
_, ok = mem.ReadUint32Le(ctx, 5)
require.False(t, ok)
_, ok = mem.ReadUint32Le(9)
_, ok = mem.ReadUint32Le(ctx, 9)
require.False(t, ok)
}
}
func TestWriteUint32Le(t *testing.T) {
for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil!
var mem = &MemoryInstance{Buffer: make([]byte, 8), Min: 1}
require.True(t, mem.WriteUint32Le(4, 16))
require.True(t, mem.WriteUint32Le(ctx, 4, 16))
require.Equal(t, []byte{0, 0, 0, 0, 16, 0, 0, 0}, mem.Buffer)
require.False(t, mem.WriteUint32Le(5, 16))
require.False(t, mem.WriteUint32Le(9, 16))
require.False(t, mem.WriteUint32Le(ctx, 5, 16))
require.False(t, mem.WriteUint32Le(ctx, 9, 16))
}
}
func TestPagesToUnitOfBytes(t *testing.T) {
@@ -135,7 +150,7 @@ func TestPagesToUnitOfBytes(t *testing.T) {
}
func TestMemoryInstance_HasSize(t *testing.T) {
memory := &MemoryInstance{Buffer: make([]byte, 100)}
memory := &MemoryInstance{Buffer: make([]byte, MemoryPageSize)}
tests := []struct {
name string
@@ -151,19 +166,19 @@ func TestMemoryInstance_HasSize(t *testing.T) {
},
{
name: "maximum valid sizeInBytes",
offset: memory.Size() - 8,
offset: memory.Size(testCtx) - 8,
sizeInBytes: 8,
expected: true,
},
{
name: "sizeInBytes exceeds the valid size by 1",
offset: 100, // arbitrary valid offset
sizeInBytes: uint64(memory.Size() - 99),
sizeInBytes: uint64(memory.Size(testCtx) - 99),
expected: false,
},
{
name: "offset exceeds the memory size",
offset: memory.Size(),
offset: memory.Size(testCtx),
sizeInBytes: 1, // arbitrary size
expected: false,
},
@@ -173,6 +188,12 @@ func TestMemoryInstance_HasSize(t *testing.T) {
sizeInBytes: 4, // if there's overflow, offset + sizeInBytes is 3, and it may pass the check
expected: false,
},
{
name: "address.wast:200",
offset: 4294967295,
sizeInBytes: 1,
expected: false,
},
}
for _, tt := range tests {
@@ -184,6 +205,57 @@ func TestMemoryInstance_HasSize(t *testing.T) {
}
}
func TestMemoryInstance_ReadUint16Le(t *testing.T) {
tests := []struct {
name string
memory []byte
offset uint32
expected uint16
expectedOk bool
}{
{
name: "valid offset with an endian-insensitive v",
memory: []byte{0xff, 0xff},
offset: 0, // arbitrary valid offset.
expected: math.MaxUint16,
expectedOk: true,
},
{
name: "valid offset with an endian-sensitive v",
memory: []byte{0xfe, 0xff},
offset: 0, // arbitrary valid offset.
expected: math.MaxUint16 - 1,
expectedOk: true,
},
{
name: "maximum boundary valid offset",
offset: 1,
memory: []byte{0x00, 0x1, 0x00},
expected: 1, // arbitrary valid v
expectedOk: true,
},
{
name: "offset exceeds the maximum valid offset by 1",
memory: []byte{0xff, 0xff},
offset: 1,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil!
memory := &MemoryInstance{Buffer: tc.memory}
v, ok := memory.ReadUint16Le(ctx, tc.offset)
require.Equal(t, tc.expectedOk, ok)
require.Equal(t, tc.expected, v)
}
})
}
}
func TestMemoryInstance_ReadUint32Le(t *testing.T) {
tests := []struct {
name string
@@ -224,11 +296,13 @@ func TestMemoryInstance_ReadUint32Le(t *testing.T) {
tc := tt
t.Run(tc.name, func(t *testing.T) {
for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil!
memory := &MemoryInstance{Buffer: tc.memory}
v, ok := memory.ReadUint32Le(tc.offset)
v, ok := memory.ReadUint32Le(ctx, tc.offset)
require.Equal(t, tc.expectedOk, ok)
require.Equal(t, tc.expected, v)
}
})
}
}
@@ -273,11 +347,13 @@ func TestMemoryInstance_ReadUint64Le(t *testing.T) {
tc := tt
t.Run(tc.name, func(t *testing.T) {
for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil!
memory := &MemoryInstance{Buffer: tc.memory}
v, ok := memory.ReadUint64Le(tc.offset)
v, ok := memory.ReadUint64Le(ctx, tc.offset)
require.Equal(t, tc.expectedOk, ok)
require.Equal(t, tc.expected, v)
}
})
}
}
@@ -322,11 +398,13 @@ func TestMemoryInstance_ReadFloat32Le(t *testing.T) {
tc := tt
t.Run(tc.name, func(t *testing.T) {
for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil!
memory := &MemoryInstance{Buffer: tc.memory}
v, ok := memory.ReadFloat32Le(tc.offset)
v, ok := memory.ReadFloat32Le(ctx, tc.offset)
require.Equal(t, tc.expectedOk, ok)
require.Equal(t, tc.expected, v)
}
})
}
}
@@ -371,11 +449,66 @@ func TestMemoryInstance_ReadFloat64Le(t *testing.T) {
tc := tt
t.Run(tc.name, func(t *testing.T) {
for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil!
memory := &MemoryInstance{Buffer: tc.memory}
v, ok := memory.ReadFloat64Le(tc.offset)
v, ok := memory.ReadFloat64Le(ctx, tc.offset)
require.Equal(t, tc.expectedOk, ok)
require.Equal(t, tc.expected, v)
}
})
}
}
func TestMemoryInstance_WriteUint16Le(t *testing.T) {
memory := &MemoryInstance{Buffer: make([]byte, 100)}
tests := []struct {
name string
offset uint32
v uint16
expectedOk bool
expectedBytes []byte
}{
{
name: "valid offset with an endian-insensitive v",
offset: 0, // arbitrary valid offset.
v: math.MaxUint16,
expectedOk: true,
expectedBytes: []byte{0xff, 0xff},
},
{
name: "valid offset with an endian-sensitive v",
offset: 0, // arbitrary valid offset.
v: math.MaxUint16 - 1,
expectedOk: true,
expectedBytes: []byte{0xfe, 0xff},
},
{
name: "maximum boundary valid offset",
offset: memory.Size(testCtx) - 2, // 2 is the size of uint16
v: 1, // arbitrary valid v
expectedOk: true,
expectedBytes: []byte{0x1, 0x00},
},
{
name: "offset exceeds the maximum valid offset by 1",
offset: memory.Size(testCtx) - 2 + 1, // 2 is the size of uint16
v: 1, // arbitrary valid v
expectedBytes: []byte{0xff, 0xff},
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil!
require.Equal(t, tc.expectedOk, memory.WriteUint16Le(ctx, tc.offset, tc.v))
if tc.expectedOk {
require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+2]) // 2 is the size of uint16
}
}
})
}
}
@@ -406,14 +539,14 @@ func TestMemoryInstance_WriteUint32Le(t *testing.T) {
},
{
name: "maximum boundary valid offset",
offset: memory.Size() - 4, // 4 is the size of uint32
offset: memory.Size(testCtx) - 4, // 4 is the size of uint32
v: 1, // arbitrary valid v
expectedOk: true,
expectedBytes: []byte{0x1, 0x00, 0x00, 0x00},
},
{
name: "offset exceeds the maximum valid offset by 1",
offset: memory.Size() - 4 + 1, // 4 is the size of uint32
offset: memory.Size(testCtx) - 4 + 1, // 4 is the size of uint32
v: 1, // arbitrary valid v
expectedBytes: []byte{0xff, 0xff, 0xff, 0xff},
},
@@ -423,10 +556,12 @@ func TestMemoryInstance_WriteUint32Le(t *testing.T) {
tc := tt
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.expectedOk, memory.WriteUint32Le(tc.offset, tc.v))
for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil!
require.Equal(t, tc.expectedOk, memory.WriteUint32Le(ctx, tc.offset, tc.v))
if tc.expectedOk {
require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+4]) // 4 is the size of uint32
}
}
})
}
}
@@ -456,14 +591,14 @@ func TestMemoryInstance_WriteUint64Le(t *testing.T) {
},
{
name: "maximum boundary valid offset",
offset: memory.Size() - 8, // 8 is the size of uint64
offset: memory.Size(testCtx) - 8, // 8 is the size of uint64
v: 1, // arbitrary valid v
expectedOk: true,
expectedBytes: []byte{0x1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
},
{
name: "offset exceeds the maximum valid offset by 1",
offset: memory.Size() - 8 + 1, // 8 is the size of uint64
offset: memory.Size(testCtx) - 8 + 1, // 8 is the size of uint64
v: 1, // arbitrary valid v
expectedOk: false,
},
@@ -473,10 +608,12 @@ func TestMemoryInstance_WriteUint64Le(t *testing.T) {
tc := tt
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.expectedOk, memory.WriteUint64Le(tc.offset, tc.v))
for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil!
require.Equal(t, tc.expectedOk, memory.WriteUint64Le(ctx, tc.offset, tc.v))
if tc.expectedOk {
require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+8]) // 8 is the size of uint64
}
}
})
}
}
@@ -507,14 +644,14 @@ func TestMemoryInstance_WriteFloat32Le(t *testing.T) {
},
{
name: "maximum boundary valid offset",
offset: memory.Size() - 4, // 4 is the size of float32
offset: memory.Size(testCtx) - 4, // 4 is the size of float32
v: 0.1, // arbitrary valid v
expectedOk: true,
expectedBytes: []byte{0xcd, 0xcc, 0xcc, 0x3d},
},
{
name: "offset exceeds the maximum valid offset by 1",
offset: memory.Size() - 4 + 1, // 4 is the size of float32
offset: memory.Size(testCtx) - 4 + 1, // 4 is the size of float32
v: math.MaxFloat32, // arbitrary valid v
expectedBytes: []byte{0xff, 0xff, 0xff, 0xff},
},
@@ -524,10 +661,12 @@ func TestMemoryInstance_WriteFloat32Le(t *testing.T) {
tc := tt
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.expectedOk, memory.WriteFloat32Le(tc.offset, tc.v))
for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil!
require.Equal(t, tc.expectedOk, memory.WriteFloat32Le(ctx, tc.offset, tc.v))
if tc.expectedOk {
require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+4]) // 4 is the size of float32
}
}
})
}
}
@@ -557,14 +696,14 @@ func TestMemoryInstance_WriteFloat64Le(t *testing.T) {
},
{
name: "maximum boundary valid offset",
offset: memory.Size() - 8, // 8 is the size of float64
offset: memory.Size(testCtx) - 8, // 8 is the size of float64
v: math.MaxFloat64, // arbitrary valid v
expectedOk: true,
expectedBytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f},
},
{
name: "offset exceeds the maximum valid offset by 1",
offset: memory.Size() - 8 + 1, // 8 is the size of float64
offset: memory.Size(testCtx) - 8 + 1, // 8 is the size of float64
v: math.MaxFloat64, // arbitrary valid v
expectedOk: false,
},
@@ -574,10 +713,12 @@ func TestMemoryInstance_WriteFloat64Le(t *testing.T) {
tc := tt
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.expectedOk, memory.WriteFloat64Le(tc.offset, tc.v))
for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil!
require.Equal(t, tc.expectedOk, memory.WriteFloat64Le(ctx, tc.offset, tc.v))
if tc.expectedOk {
require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+8]) // 8 is the size of float64
}
}
})
}
}

View File

@@ -75,12 +75,12 @@ func TestModuleInstance_Memory(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
s := newStore()
instance, err := s.Instantiate(context.Background(), tc.input, "test", nil)
instance, err := s.Instantiate(testCtx, tc.input, "test", nil)
require.NoError(t, err)
mem := instance.ExportedMemory("memory")
if tc.expected {
require.Equal(t, tc.expectedLen, mem.Size())
require.Equal(t, tc.expectedLen, mem.Size(testCtx))
} else {
require.Nil(t, mem)
}
@@ -102,7 +102,7 @@ func TestStore_Instantiate(t *testing.T) {
sys := &SysContext{}
mod, err := s.Instantiate(testCtx, m, "", sys)
require.NoError(t, err)
defer mod.Close()
defer mod.Close(testCtx)
t.Run("CallContext defaults", func(t *testing.T) {
require.Equal(t, s.modules[""], mod.module)
@@ -131,14 +131,14 @@ func TestStore_CloseModule(t *testing.T) {
Features20191205,
)
require.NoError(t, err)
_, err = s.Instantiate(context.Background(), m, importedModuleName, nil)
_, err = s.Instantiate(testCtx, m, importedModuleName, nil)
require.NoError(t, err)
},
},
{
name: "Module imports Module",
initializer: func(t *testing.T, s *Store) {
_, err := s.Instantiate(context.Background(), &Module{
_, err := s.Instantiate(testCtx, &Module{
TypeSection: []*FunctionType{{}},
FunctionSection: []uint32{0},
CodeSection: []*Code{{Body: []byte{OpcodeEnd}}},
@@ -153,7 +153,7 @@ func TestStore_CloseModule(t *testing.T) {
s := newStore()
tc.initializer(t, s)
_, err := s.Instantiate(context.Background(), &Module{
_, err := s.Instantiate(testCtx, &Module{
TypeSection: []*FunctionType{{}},
ImportSection: []*Import{{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0}},
MemorySection: &Memory{Min: 1},
@@ -169,14 +169,14 @@ func TestStore_CloseModule(t *testing.T) {
require.True(t, ok)
// Close the importing module
require.NoError(t, importing.CallCtx.CloseWithExitCode(0))
require.NoError(t, importing.CallCtx.CloseWithExitCode(testCtx, 0))
require.Nil(t, s.modules[importingModuleName])
// Can re-close the importing module
require.NoError(t, importing.CallCtx.CloseWithExitCode(0))
require.NoError(t, importing.CallCtx.CloseWithExitCode(testCtx, 0))
// Now we close the imported module.
require.NoError(t, imported.CallCtx.CloseWithExitCode(0))
require.NoError(t, imported.CallCtx.CloseWithExitCode(testCtx, 0))
require.Nil(t, s.modules[importedModuleName])
})
}
@@ -195,7 +195,7 @@ func TestStore_hammer(t *testing.T) {
require.NoError(t, err)
s := newStore()
imported, err := s.Instantiate(context.Background(), m, importedModuleName, nil)
imported, err := s.Instantiate(testCtx, m, importedModuleName, nil)
require.NoError(t, err)
_, ok := s.modules[imported.Name()]
@@ -222,16 +222,16 @@ func TestStore_hammer(t *testing.T) {
N = 100
}
hammer.NewHammer(t, P, N).Run(func(name string) {
mod, instantiateErr := s.Instantiate(context.Background(), importingModule, name, DefaultSysContext())
mod, instantiateErr := s.Instantiate(testCtx, importingModule, name, DefaultSysContext())
require.NoError(t, instantiateErr)
require.NoError(t, mod.CloseWithExitCode(0))
require.NoError(t, mod.Close(testCtx))
}, nil)
if t.Failed() {
return // At least one test failed, so return now.
}
// Close the imported module.
require.NoError(t, imported.CloseWithExitCode(0))
require.NoError(t, imported.Close(testCtx))
// All instances are freed.
require.Zero(t, len(s.modules))
@@ -252,23 +252,23 @@ func TestStore_Instantiate_Errors(t *testing.T) {
t.Run("Fails if module name already in use", func(t *testing.T) {
s := newStore()
_, err = s.Instantiate(context.Background(), m, importedModuleName, nil)
_, err = s.Instantiate(testCtx, m, importedModuleName, nil)
require.NoError(t, err)
// Trying to register it again should fail
_, err = s.Instantiate(context.Background(), m, importedModuleName, nil)
_, err = s.Instantiate(testCtx, m, importedModuleName, nil)
require.EqualError(t, err, "module imported has already been instantiated")
})
t.Run("fail resolve import", func(t *testing.T) {
s := newStore()
_, err = s.Instantiate(context.Background(), m, importedModuleName, nil)
_, err = s.Instantiate(testCtx, m, importedModuleName, nil)
require.NoError(t, err)
hm := s.modules[importedModuleName]
require.NotNil(t, hm)
_, err = s.Instantiate(context.Background(), &Module{
_, err = s.Instantiate(testCtx, &Module{
TypeSection: []*FunctionType{{}},
ImportSection: []*Import{
// The first import resolve succeeds -> increment hm.dependentCount.
@@ -283,7 +283,7 @@ func TestStore_Instantiate_Errors(t *testing.T) {
t.Run("compilation failed", func(t *testing.T) {
s := newStore()
_, err = s.Instantiate(context.Background(), m, importedModuleName, nil)
_, err = s.Instantiate(testCtx, m, importedModuleName, nil)
require.NoError(t, err)
hm := s.modules[importedModuleName]
@@ -292,7 +292,7 @@ func TestStore_Instantiate_Errors(t *testing.T) {
engine := s.Engine.(*mockEngine)
engine.shouldCompileFail = true
_, err = s.Instantiate(context.Background(), &Module{
_, err = s.Instantiate(testCtx, &Module{
TypeSection: []*FunctionType{{}},
FunctionSection: []uint32{0, 0},
CodeSection: []*Code{
@@ -311,14 +311,14 @@ func TestStore_Instantiate_Errors(t *testing.T) {
engine := s.Engine.(*mockEngine)
engine.callFailIndex = 1
_, err = s.Instantiate(context.Background(), m, importedModuleName, nil)
_, err = s.Instantiate(testCtx, m, importedModuleName, nil)
require.NoError(t, err)
hm := s.modules[importedModuleName]
require.NotNil(t, hm)
startFuncIndex := uint32(1)
_, err = s.Instantiate(context.Background(), &Module{
_, err = s.Instantiate(testCtx, &Module{
TypeSection: []*FunctionType{{}},
FunctionSection: []uint32{0},
CodeSection: []*Code{{Body: []byte{OpcodeEnd}}},
@@ -344,19 +344,19 @@ func TestCallContext_ExportedFunction(t *testing.T) {
s := newStore()
// Add the host module
imported, err := s.Instantiate(context.Background(), host, host.NameSection.ModuleName, nil)
imported, err := s.Instantiate(testCtx, host, host.NameSection.ModuleName, nil)
require.NoError(t, err)
defer imported.Close()
defer imported.Close(testCtx)
t.Run("imported function", func(t *testing.T) {
importing, err := s.Instantiate(context.Background(), &Module{
importing, err := s.Instantiate(testCtx, &Module{
TypeSection: []*FunctionType{{}},
ImportSection: []*Import{{Type: ExternTypeFunc, Module: "host", Name: "host_fn", DescFunc: 0}},
MemorySection: &Memory{Min: 1},
ExportSection: []*Export{{Type: ExternTypeFunc, Name: "host.fn", Index: 0}},
}, "test", nil)
require.NoError(t, err)
defer importing.Close()
defer importing.Close(testCtx)
fn := importing.ExportedFunction("host.fn")
require.NotNil(t, fn)
@@ -409,7 +409,7 @@ func (e *mockModuleEngine) Call(ctx context.Context, callCtx *CallContext, f *Fu
}
// Close implements the same method as documented on wasm.ModuleEngine.
func (e *mockModuleEngine) Close() {
func (e *mockModuleEngine) Close(_ context.Context) {
}
func TestStore_getFunctionTypeID(t *testing.T) {

View File

@@ -182,6 +182,8 @@ type CompilationResult struct {
}
func CompileFunctions(_ context.Context, enabledFeatures wasm.Features, module *wasm.Module) ([]*CompilationResult, error) {
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
functions, globals, mem, table, err := module.AllDeclarations()
if err != nil {
return nil, err

View File

@@ -25,7 +25,7 @@ func Example() {
if err != nil {
log.Fatal(err)
}
defer wm.Close()
defer wm.Close(testCtx)
// Override default configuration (which discards stdout).
config := wazero.NewModuleConfig().WithStdout(os.Stdout)

View File

@@ -22,11 +22,11 @@ func TestInstantiateModuleWithConfig(t *testing.T) {
sys := wazero.NewModuleConfig().WithStdout(stdout)
wm, err := InstantiateSnapshotPreview1(testCtx, r)
require.NoError(t, err)
defer wm.Close()
defer wm.Close(testCtx)
compiled, err := r.CompileModule(testCtx, wasiArg)
require.NoError(t, err)
defer compiled.Close()
defer compiled.Close(testCtx)
// Re-use the same module many times.
for _, tc := range []string{"a", "b", "c"} {
@@ -37,6 +37,6 @@ func TestInstantiateModuleWithConfig(t *testing.T) {
require.Equal(t, append([]byte(tc), 0), stdout.Bytes())
stdout.Reset()
require.NoError(t, mod.Close())
require.NoError(t, mod.Close(testCtx))
}
}

View File

@@ -515,9 +515,9 @@ func snapshotPreview1Functions() (a *snapshotPreview1, nameToGoFunc map[string]i
// See ArgsSizesGet
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#args_get
// See https://en.wikipedia.org/wiki/Null-terminated_string
func (a *snapshotPreview1) ArgsGet(m api.Module, argv, argvBuf uint32) Errno {
func (a *snapshotPreview1) ArgsGet(ctx context.Context, m api.Module, argv, argvBuf uint32) Errno {
sys := sysCtx(m)
return writeOffsetsAndNullTerminatedValues(m.Memory(), sys.Args(), argv, argvBuf)
return writeOffsetsAndNullTerminatedValues(ctx, m.Memory(), sys.Args(), argv, argvBuf)
}
// ArgsSizesGet is the WASI function named functionArgsSizesGet that reads command-line argument data (WithArgs)
@@ -546,14 +546,14 @@ func (a *snapshotPreview1) ArgsGet(m api.Module, argv, argvBuf uint32) Errno {
// See ArgsGet
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#args_sizes_get
// See https://en.wikipedia.org/wiki/Null-terminated_string
func (a *snapshotPreview1) ArgsSizesGet(m api.Module, resultArgc, resultArgvBufSize uint32) Errno {
func (a *snapshotPreview1) ArgsSizesGet(ctx context.Context, m api.Module, resultArgc, resultArgvBufSize uint32) Errno {
sys := sysCtx(m)
mem := m.Memory()
if !mem.WriteUint32Le(resultArgc, uint32(len(sys.Args()))) {
if !mem.WriteUint32Le(ctx, resultArgc, uint32(len(sys.Args()))) {
return ErrnoFault
}
if !mem.WriteUint32Le(resultArgvBufSize, sys.ArgsSize()) {
if !mem.WriteUint32Le(ctx, resultArgvBufSize, sys.ArgsSize()) {
return ErrnoFault
}
return ErrnoSuccess
@@ -585,9 +585,9 @@ func (a *snapshotPreview1) ArgsSizesGet(m api.Module, resultArgc, resultArgvBufS
// See EnvironSizesGet
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#environ_get
// See https://en.wikipedia.org/wiki/Null-terminated_string
func (a *snapshotPreview1) EnvironGet(m api.Module, environ uint32, environBuf uint32) Errno {
func (a *snapshotPreview1) EnvironGet(ctx context.Context, m api.Module, environ uint32, environBuf uint32) Errno {
sys := sysCtx(m)
return writeOffsetsAndNullTerminatedValues(m.Memory(), sys.Environ(), environ, environBuf)
return writeOffsetsAndNullTerminatedValues(ctx, m.Memory(), sys.Environ(), environ, environBuf)
}
// EnvironSizesGet is the WASI function named functionEnvironSizesGet that reads environment variable
@@ -617,14 +617,14 @@ func (a *snapshotPreview1) EnvironGet(m api.Module, environ uint32, environBuf u
// See EnvironGet
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#environ_sizes_get
// See https://en.wikipedia.org/wiki/Null-terminated_string
func (a *snapshotPreview1) EnvironSizesGet(m api.Module, resultEnvironc uint32, resultEnvironBufSize uint32) Errno {
func (a *snapshotPreview1) EnvironSizesGet(ctx context.Context, m api.Module, resultEnvironc uint32, resultEnvironBufSize uint32) Errno {
sys := sysCtx(m)
mem := m.Memory()
if !mem.WriteUint32Le(resultEnvironc, uint32(len(sys.Environ()))) {
if !mem.WriteUint32Le(ctx, resultEnvironc, uint32(len(sys.Environ()))) {
return ErrnoFault
}
if !mem.WriteUint32Le(resultEnvironBufSize, sys.EnvironSize()) {
if !mem.WriteUint32Le(ctx, resultEnvironBufSize, sys.EnvironSize()) {
return ErrnoFault
}
@@ -632,7 +632,7 @@ func (a *snapshotPreview1) EnvironSizesGet(m api.Module, resultEnvironc uint32,
}
// ClockResGet is the WASI function named functionClockResGet and is stubbed for GrainLang per #271
func (a *snapshotPreview1) ClockResGet(m api.Module, id uint32, resultResolution uint32) Errno {
func (a *snapshotPreview1) ClockResGet(ctx context.Context, m api.Module, id uint32, resultResolution uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
@@ -656,21 +656,21 @@ func (a *snapshotPreview1) ClockResGet(m api.Module, id uint32, resultResolution
// Note: This is similar to `clock_gettime` in POSIX.
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clock_time_getid-clockid-precision-timestamp---errno-timestamp
// See https://linux.die.net/man/3/clock_gettime
func (a *snapshotPreview1) ClockTimeGet(m api.Module, id uint32, precision uint64, resultTimestamp uint32) Errno {
func (a *snapshotPreview1) ClockTimeGet(ctx context.Context, m api.Module, id uint32, precision uint64, resultTimestamp uint32) Errno {
// TODO: id and precision are currently ignored.
if !m.Memory().WriteUint64Le(resultTimestamp, a.timeNowUnixNano()) {
if !m.Memory().WriteUint64Le(ctx, resultTimestamp, a.timeNowUnixNano()) {
return ErrnoFault
}
return ErrnoSuccess
}
// FdAdvise is the WASI function named functionFdAdvise and is stubbed for GrainLang per #271
func (a *snapshotPreview1) FdAdvise(m api.Module, fd uint32, offset, len uint64, resultAdvice uint32) Errno {
func (a *snapshotPreview1) FdAdvise(ctx context.Context, m api.Module, fd uint32, offset, len uint64, resultAdvice uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
// FdAllocate is the WASI function named functionFdAllocate and is stubbed for GrainLang per #271
func (a *snapshotPreview1) FdAllocate(m api.Module, fd uint32, offset, len uint64) Errno {
func (a *snapshotPreview1) FdAllocate(ctx context.Context, m api.Module, fd uint32, offset, len uint64) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
@@ -682,7 +682,7 @@ func (a *snapshotPreview1) FdAllocate(m api.Module, fd uint32, offset, len uint6
// Note: This is similar to `close` in POSIX.
// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_close
// See https://linux.die.net/man/3/close
func (a *snapshotPreview1) FdClose(m api.Module, fd uint32) Errno {
func (a *snapshotPreview1) FdClose(ctx context.Context, m api.Module, fd uint32) Errno {
sys := sysCtx(m)
if ok, err := sys.CloseFile(fd); err != nil {
@@ -695,7 +695,7 @@ func (a *snapshotPreview1) FdClose(m api.Module, fd uint32) Errno {
}
// FdDatasync is the WASI function named functionFdDatasync and is stubbed for GrainLang per #271
func (a *snapshotPreview1) FdDatasync(m api.Module, fd uint32) Errno {
func (a *snapshotPreview1) FdDatasync(ctx context.Context, m api.Module, fd uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
@@ -731,7 +731,7 @@ func (a *snapshotPreview1) FdDatasync(m api.Module, fd uint32) Errno {
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fdstat
// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_fdstat_get
// See https://linux.die.net/man/3/fsync
func (a *snapshotPreview1) FdFdstatGet(m api.Module, fd uint32, resultStat uint32) Errno {
func (a *snapshotPreview1) FdFdstatGet(ctx context.Context, m api.Module, fd uint32, resultStat uint32) Errno {
sys := sysCtx(m)
if _, ok := sys.OpenedFile(fd); !ok {
@@ -767,7 +767,7 @@ func (a *snapshotPreview1) FdFdstatGet(m api.Module, fd uint32, resultStat uint3
// See FdPrestatDirName
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#prestat
// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_prestat_get
func (a *snapshotPreview1) FdPrestatGet(m api.Module, fd uint32, resultPrestat uint32) Errno {
func (a *snapshotPreview1) FdPrestatGet(ctx context.Context, m api.Module, fd uint32, resultPrestat uint32) Errno {
sys := sysCtx(m)
entry, ok := sys.OpenedFile(fd)
@@ -776,11 +776,11 @@ func (a *snapshotPreview1) FdPrestatGet(m api.Module, fd uint32, resultPrestat u
}
// Zero-value 8-bit tag, and 3-byte zero-value paddings, which is uint32le(0) in short.
if !m.Memory().WriteUint32Le(resultPrestat, uint32(0)) {
if !m.Memory().WriteUint32Le(ctx, resultPrestat, uint32(0)) {
return ErrnoFault
}
// Write the length of the directory name at offset 4.
if !m.Memory().WriteUint32Le(resultPrestat+4, uint32(len(entry.Path))) {
if !m.Memory().WriteUint32Le(ctx, resultPrestat+4, uint32(len(entry.Path))) {
return ErrnoFault
}
@@ -788,33 +788,33 @@ func (a *snapshotPreview1) FdPrestatGet(m api.Module, fd uint32, resultPrestat u
}
// FdFdstatSetFlags is the WASI function named functionFdFdstatSetFlags and is stubbed for GrainLang per #271
func (a *snapshotPreview1) FdFdstatSetFlags(m api.Module, fd uint32, flags uint32) Errno {
func (a *snapshotPreview1) FdFdstatSetFlags(ctx context.Context, m api.Module, fd uint32, flags uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
// FdFdstatSetRights implements snapshotPreview1.FdFdstatSetRights
// Note: This will never be implemented per https://github.com/WebAssembly/WASI/issues/469#issuecomment-1045251844
func (a *snapshotPreview1) FdFdstatSetRights(m api.Module, fd uint32, fsRightsBase, fsRightsInheriting uint64) Errno {
func (a *snapshotPreview1) FdFdstatSetRights(ctx context.Context, m api.Module, fd uint32, fsRightsBase, fsRightsInheriting uint64) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
// FdFilestatGet is the WASI function named functionFdFilestatGet
func (a *snapshotPreview1) FdFilestatGet(m api.Module, fd uint32, resultBuf uint32) Errno {
func (a *snapshotPreview1) FdFilestatGet(ctx context.Context, m api.Module, fd uint32, resultBuf uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
// FdFilestatSetSize is the WASI function named functionFdFilestatSetSize
func (a *snapshotPreview1) FdFilestatSetSize(m api.Module, fd uint32, size uint64) Errno {
func (a *snapshotPreview1) FdFilestatSetSize(ctx context.Context, m api.Module, fd uint32, size uint64) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
// FdFilestatSetTimes is the WASI function named functionFdFilestatSetTimes
func (a *snapshotPreview1) FdFilestatSetTimes(m api.Module, fd uint32, atim, mtim uint64, fstFlags uint32) Errno {
func (a *snapshotPreview1) FdFilestatSetTimes(ctx context.Context, m api.Module, fd uint32, atim, mtim uint64, fstFlags uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
// FdPread is the WASI function named functionFdPread
func (a *snapshotPreview1) FdPread(m api.Module, fd, iovs, iovsCount uint32, offset uint64, resultNread uint32) Errno {
func (a *snapshotPreview1) FdPread(ctx context.Context, m api.Module, fd, iovs, iovsCount uint32, offset uint64, resultNread uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
@@ -842,7 +842,7 @@ func (a *snapshotPreview1) FdPread(m api.Module, fd, iovs, iovsCount uint32, off
// Note: importFdPrestatDirName shows this signature in the WebAssembly 1.0 (20191205) Text Format.
// See FdPrestatGet
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_prestat_dir_name
func (a *snapshotPreview1) FdPrestatDirName(m api.Module, fd uint32, pathPtr uint32, pathLen uint32) Errno {
func (a *snapshotPreview1) FdPrestatDirName(ctx context.Context, m api.Module, fd uint32, pathPtr uint32, pathLen uint32) Errno {
sys := sysCtx(m)
f, ok := sys.OpenedFile(fd)
@@ -856,14 +856,14 @@ func (a *snapshotPreview1) FdPrestatDirName(m api.Module, fd uint32, pathPtr uin
}
// TODO: FdPrestatDirName may have to return ErrnoNotdir if the type of the prestat data of `fd` is not a PrestatDir.
if !m.Memory().Write(pathPtr, []byte(f.Path)[:pathLen]) {
if !m.Memory().Write(ctx, pathPtr, []byte(f.Path)[:pathLen]) {
return ErrnoFault
}
return ErrnoSuccess
}
// FdPwrite is the WASI function named functionFdPwrite
func (a *snapshotPreview1) FdPwrite(m api.Module, fd, iovs, iovsCount uint32, offset uint64, resultNwritten uint32) Errno {
func (a *snapshotPreview1) FdPwrite(ctx context.Context, m api.Module, fd, iovs, iovsCount uint32, offset uint64, resultNwritten uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
@@ -910,7 +910,7 @@ func (a *snapshotPreview1) FdPwrite(m api.Module, fd, iovs, iovsCount uint32, of
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_read
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#iovec
// See https://linux.die.net/man/3/readv
func (a *snapshotPreview1) FdRead(m api.Module, fd, iovs, iovsCount, resultSize uint32) Errno {
func (a *snapshotPreview1) FdRead(ctx context.Context, m api.Module, fd, iovs, iovsCount, resultSize uint32) Errno {
sys := sysCtx(m)
var reader io.Reader
@@ -926,15 +926,15 @@ func (a *snapshotPreview1) FdRead(m api.Module, fd, iovs, iovsCount, resultSize
var nread uint32
for i := uint32(0); i < iovsCount; i++ {
iovPtr := iovs + i*8
offset, ok := m.Memory().ReadUint32Le(iovPtr)
offset, ok := m.Memory().ReadUint32Le(ctx, iovPtr)
if !ok {
return ErrnoFault
}
l, ok := m.Memory().ReadUint32Le(iovPtr + 4)
l, ok := m.Memory().ReadUint32Le(ctx, iovPtr+4)
if !ok {
return ErrnoFault
}
b, ok := m.Memory().Read(offset, l)
b, ok := m.Memory().Read(ctx, offset, l)
if !ok {
return ErrnoFault
}
@@ -946,19 +946,19 @@ func (a *snapshotPreview1) FdRead(m api.Module, fd, iovs, iovsCount, resultSize
return ErrnoIo
}
}
if !m.Memory().WriteUint32Le(resultSize, nread) {
if !m.Memory().WriteUint32Le(ctx, resultSize, nread) {
return ErrnoFault
}
return ErrnoSuccess
}
// FdReaddir is the WASI function named functionFdReaddir
func (a *snapshotPreview1) FdReaddir(m api.Module, fd, buf, bufLen uint32, cookie uint64, resultBufused uint32) Errno {
func (a *snapshotPreview1) FdReaddir(ctx context.Context, m api.Module, fd, buf, bufLen uint32, cookie uint64, resultBufused uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
// FdRenumber is the WASI function named functionFdRenumber
func (a *snapshotPreview1) FdRenumber(m api.Module, fd, to uint32) Errno {
func (a *snapshotPreview1) FdRenumber(ctx context.Context, m api.Module, fd, to uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
@@ -993,7 +993,7 @@ func (a *snapshotPreview1) FdRenumber(m api.Module, fd, to uint32) Errno {
// Note: This is similar to `lseek` in POSIX.
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_seek
// See https://linux.die.net/man/3/lseek
func (a *snapshotPreview1) FdSeek(m api.Module, fd uint32, offset uint64, whence uint32, resultNewoffset uint32) Errno {
func (a *snapshotPreview1) FdSeek(ctx context.Context, m api.Module, fd uint32, offset uint64, whence uint32, resultNewoffset uint32) Errno {
sys := sysCtx(m)
var seeker io.Seeker
@@ -1013,7 +1013,7 @@ func (a *snapshotPreview1) FdSeek(m api.Module, fd uint32, offset uint64, whence
return ErrnoIo
}
if !m.Memory().WriteUint32Le(resultNewoffset, uint32(newOffset)) {
if !m.Memory().WriteUint32Le(ctx, resultNewoffset, uint32(newOffset)) {
return ErrnoFault
}
@@ -1021,12 +1021,12 @@ func (a *snapshotPreview1) FdSeek(m api.Module, fd uint32, offset uint64, whence
}
// FdSync is the WASI function named functionFdSync
func (a *snapshotPreview1) FdSync(m api.Module, fd uint32) Errno {
func (a *snapshotPreview1) FdSync(ctx context.Context, m api.Module, fd uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
// FdTell is the WASI function named functionFdTell
func (a *snapshotPreview1) FdTell(m api.Module, fd, resultOffset uint32) Errno {
func (a *snapshotPreview1) FdTell(ctx context.Context, m api.Module, fd, resultOffset uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
@@ -1079,7 +1079,7 @@ func (a *snapshotPreview1) FdTell(m api.Module, fd, resultOffset uint32) Errno {
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#ciovec
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_write
// See https://linux.die.net/man/3/writev
func (a *snapshotPreview1) FdWrite(m api.Module, fd, iovs, iovsCount, resultSize uint32) Errno {
func (a *snapshotPreview1) FdWrite(ctx context.Context, m api.Module, fd, iovs, iovsCount, resultSize uint32) Errno {
sys := sysCtx(m)
var writer io.Writer
@@ -1102,15 +1102,15 @@ func (a *snapshotPreview1) FdWrite(m api.Module, fd, iovs, iovsCount, resultSize
var nwritten uint32
for i := uint32(0); i < iovsCount; i++ {
iovPtr := iovs + i*8
offset, ok := m.Memory().ReadUint32Le(iovPtr)
offset, ok := m.Memory().ReadUint32Le(ctx, iovPtr)
if !ok {
return ErrnoFault
}
l, ok := m.Memory().ReadUint32Le(iovPtr + 4)
l, ok := m.Memory().ReadUint32Le(ctx, iovPtr+4)
if !ok {
return ErrnoFault
}
b, ok := m.Memory().Read(offset, l)
b, ok := m.Memory().Read(ctx, offset, l)
if !ok {
return ErrnoFault
}
@@ -1120,29 +1120,29 @@ func (a *snapshotPreview1) FdWrite(m api.Module, fd, iovs, iovsCount, resultSize
}
nwritten += uint32(n)
}
if !m.Memory().WriteUint32Le(resultSize, nwritten) {
if !m.Memory().WriteUint32Le(ctx, resultSize, nwritten) {
return ErrnoFault
}
return ErrnoSuccess
}
// PathCreateDirectory is the WASI function named functionPathCreateDirectory
func (a *snapshotPreview1) PathCreateDirectory(m api.Module, fd, path, pathLen uint32) Errno {
func (a *snapshotPreview1) PathCreateDirectory(ctx context.Context, m api.Module, fd, path, pathLen uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
// PathFilestatGet is the WASI function named functionPathFilestatGet
func (a *snapshotPreview1) PathFilestatGet(m api.Module, fd, flags, path, pathLen, resultBuf uint32) Errno {
func (a *snapshotPreview1) PathFilestatGet(ctx context.Context, m api.Module, fd, flags, path, pathLen, resultBuf uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
// PathFilestatSetTimes is the WASI function named functionPathFilestatSetTimes
func (a *snapshotPreview1) PathFilestatSetTimes(m api.Module, fd, flags, path, pathLen uint32, atim, mtime uint64, fstFlags uint32) Errno {
func (a *snapshotPreview1) PathFilestatSetTimes(ctx context.Context, m api.Module, fd, flags, path, pathLen uint32, atim, mtime uint64, fstFlags uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
// PathLink is the WASI function named functionPathLink
func (a *snapshotPreview1) PathLink(m api.Module, oldFd, oldFlags, oldPath, oldPathLen, newFd, newPath, newPathLen uint32) Errno {
func (a *snapshotPreview1) PathLink(ctx context.Context, m api.Module, oldFd, oldFlags, oldPath, oldPathLen, newFd, newPath, newPathLen uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
@@ -1191,7 +1191,7 @@ func (a *snapshotPreview1) PathLink(m api.Module, oldFd, oldFlags, oldPath, oldP
// Note: Rights will never be implemented per https://github.com/WebAssembly/WASI/issues/469#issuecomment-1045251844
// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#path_open
// See https://linux.die.net/man/3/openat
func (a *snapshotPreview1) PathOpen(m api.Module, fd, dirflags, pathPtr, pathLen, oflags uint32, fsRightsBase,
func (a *snapshotPreview1) PathOpen(ctx context.Context, m api.Module, fd, dirflags, pathPtr, pathLen, oflags uint32, fsRightsBase,
fsRightsInheriting uint64, fdflags, resultOpenedFd uint32) (errno Errno) {
sys := sysCtx(m)
@@ -1200,7 +1200,7 @@ func (a *snapshotPreview1) PathOpen(m api.Module, fd, dirflags, pathPtr, pathLen
return ErrnoBadf
}
b, ok := m.Memory().Read(pathPtr, pathLen)
b, ok := m.Memory().Read(ctx, pathPtr, pathLen)
if !ok {
return ErrnoFault
}
@@ -1216,7 +1216,7 @@ func (a *snapshotPreview1) PathOpen(m api.Module, fd, dirflags, pathPtr, pathLen
if newFD, ok := sys.OpenFile(entry); !ok {
_ = entry.File.Close()
return ErrnoIo
} else if !m.Memory().WriteUint32Le(resultOpenedFd, newFD) {
} else if !m.Memory().WriteUint32Le(ctx, resultOpenedFd, newFD) {
_ = entry.File.Close()
return ErrnoFault
}
@@ -1224,32 +1224,32 @@ func (a *snapshotPreview1) PathOpen(m api.Module, fd, dirflags, pathPtr, pathLen
}
// PathReadlink is the WASI function named functionPathReadlink
func (a *snapshotPreview1) PathReadlink(m api.Module, fd, path, pathLen, buf, bufLen, resultBufused uint32) Errno {
func (a *snapshotPreview1) PathReadlink(ctx context.Context, m api.Module, fd, path, pathLen, buf, bufLen, resultBufused uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
// PathRemoveDirectory is the WASI function named functionPathRemoveDirectory
func (a *snapshotPreview1) PathRemoveDirectory(m api.Module, fd, path, pathLen uint32) Errno {
func (a *snapshotPreview1) PathRemoveDirectory(ctx context.Context, m api.Module, fd, path, pathLen uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
// PathRename is the WASI function named functionPathRename
func (a *snapshotPreview1) PathRename(m api.Module, fd, oldPath, oldPathLen, newFd, newPath, newPathLen uint32) Errno {
func (a *snapshotPreview1) PathRename(ctx context.Context, m api.Module, fd, oldPath, oldPathLen, newFd, newPath, newPathLen uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
// PathSymlink is the WASI function named functionPathSymlink
func (a *snapshotPreview1) PathSymlink(m api.Module, oldPath, oldPathLen, fd, newPath, newPathLen uint32) Errno {
func (a *snapshotPreview1) PathSymlink(ctx context.Context, m api.Module, oldPath, oldPathLen, fd, newPath, newPathLen uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
// PathUnlinkFile is the WASI function named functionPathUnlinkFile
func (a *snapshotPreview1) PathUnlinkFile(m api.Module, fd, path, pathLen uint32) Errno {
func (a *snapshotPreview1) PathUnlinkFile(ctx context.Context, m api.Module, fd, path, pathLen uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
// PollOneoff is the WASI function named functionPollOneoff
func (a *snapshotPreview1) PollOneoff(m api.Module, in, out, nsubscriptions, resultNevents uint32) Errno {
func (a *snapshotPreview1) PollOneoff(ctx context.Context, m api.Module, in, out, nsubscriptions, resultNevents uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
@@ -1262,12 +1262,12 @@ func (a *snapshotPreview1) PollOneoff(m api.Module, in, out, nsubscriptions, res
//
// Note: importProcExit shows this signature in the WebAssembly 1.0 (20191205) Text Format.
// See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#proc_exit
func (a *snapshotPreview1) ProcExit(m api.Module, exitCode uint32) {
_ = m.CloseWithExitCode(exitCode)
func (a *snapshotPreview1) ProcExit(ctx context.Context, m api.Module, exitCode uint32) {
_ = m.CloseWithExitCode(ctx, exitCode)
}
// ProcRaise is the WASI function named functionProcRaise
func (a *snapshotPreview1) ProcRaise(m api.Module, sig uint32) Errno {
func (a *snapshotPreview1) ProcRaise(ctx context.Context, m api.Module, sig uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
@@ -1276,7 +1276,7 @@ func (a *snapshotPreview1) SchedYield(m api.Module) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
// RandomGet is the WASI function named functionRandomGet that write random data in buffer (rand.Read()).
// RandomGet is the WASI function named functionRandomGet that write random data in buffer (rand.Read(ctx, )).
//
// * buf - is the m.Memory offset to write random values
// * bufLen - size of random data in bytes
@@ -1291,7 +1291,7 @@ func (a *snapshotPreview1) SchedYield(m api.Module) Errno {
//
// Note: importRandomGet shows this signature in the WebAssembly 1.0 (20191205) Text Format.
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-random_getbuf-pointeru8-bufLen-size---errno
func (a *snapshotPreview1) RandomGet(m api.Module, buf uint32, bufLen uint32) (errno Errno) {
func (a *snapshotPreview1) RandomGet(ctx context.Context, m api.Module, buf uint32, bufLen uint32) (errno Errno) {
randomBytes := make([]byte, bufLen)
err := a.randSource(randomBytes)
if err != nil {
@@ -1299,7 +1299,7 @@ func (a *snapshotPreview1) RandomGet(m api.Module, buf uint32, bufLen uint32) (e
return ErrnoIo
}
if !m.Memory().Write(buf, randomBytes) {
if !m.Memory().Write(ctx, buf, randomBytes) {
return ErrnoFault
}
@@ -1307,17 +1307,17 @@ func (a *snapshotPreview1) RandomGet(m api.Module, buf uint32, bufLen uint32) (e
}
// SockRecv is the WASI function named functionSockRecv
func (a *snapshotPreview1) SockRecv(m api.Module, fd, riData, riDataCount, riFlags, resultRoDataLen, resultRoFlags uint32) Errno {
func (a *snapshotPreview1) SockRecv(ctx context.Context, m api.Module, fd, riData, riDataCount, riFlags, resultRoDataLen, resultRoFlags uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
// SockSend is the WASI function named functionSockSend
func (a *snapshotPreview1) SockSend(m api.Module, fd, siData, siDataCount, siFlags, resultSoDataLen uint32) Errno {
func (a *snapshotPreview1) SockSend(ctx context.Context, m api.Module, fd, siData, siDataCount, siFlags, resultSoDataLen uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
// SockShutdown is the WASI function named functionSockShutdown
func (a *snapshotPreview1) SockShutdown(m api.Module, fd, how uint32) Errno {
func (a *snapshotPreview1) SockShutdown(ctx context.Context, m api.Module, fd, how uint32) Errno {
return ErrnoNosys // stubbed for GrainLang per #271
}
@@ -1366,20 +1366,20 @@ func openFileEntry(rootFS fs.FS, pathName string) (*wasm.FileEntry, Errno) {
return &wasm.FileEntry{Path: pathName, FS: rootFS, File: f}, ErrnoSuccess
}
func writeOffsetsAndNullTerminatedValues(mem api.Memory, values []string, offsets, bytes uint32) Errno {
func writeOffsetsAndNullTerminatedValues(ctx context.Context, mem api.Memory, values []string, offsets, bytes uint32) Errno {
for _, value := range values {
// Write current offset and advance it.
if !mem.WriteUint32Le(offsets, bytes) {
if !mem.WriteUint32Le(ctx, offsets, bytes) {
return ErrnoFault
}
offsets += 4 // size of uint32
// Write the next value to memory with a NUL terminator
if !mem.Write(bytes, []byte(value)) {
if !mem.Write(ctx, bytes, []byte(value)) {
return ErrnoFault
}
bytes += uint32(len(value))
if !mem.WriteByte(bytes, 0) {
if !mem.WriteByte(ctx, bytes, 0) {
return ErrnoFault
}
bytes++

View File

@@ -24,11 +24,11 @@ func Test_EnvironGet(t *testing.T) {
sys, err := newSysContext(nil, []string{"a=b", "b=cd"}, nil)
require.NoError(t, err)
testCtx := newCtx(make([]byte, 20), sys)
m := newModule(make([]byte, 20), sys)
environGet := newSnapshotPreview1().EnvironGet
require.Equal(t, ErrnoSuccess, environGet(testCtx, 11, 1))
require.Equal(t, testCtx.Memory(), testMem)
require.Equal(t, ErrnoSuccess, environGet(testCtx, m, 11, 1))
require.Equal(t, m.Memory(), testMem)
}
func Benchmark_EnvironGet(b *testing.B) {
@@ -37,7 +37,7 @@ func Benchmark_EnvironGet(b *testing.B) {
b.Fatal(err)
}
testCtx := newCtx([]byte{
m := newModule([]byte{
0, // environBuf is after this
'a', '=', 'b', 0, // null terminated "a=b",
'b', '=', 'c', 'd', 0, // null terminated "b=cd"
@@ -50,14 +50,14 @@ func Benchmark_EnvironGet(b *testing.B) {
environGet := newSnapshotPreview1().EnvironGet
b.Run("EnvironGet", func(b *testing.B) {
for i := 0; i < b.N; i++ {
if environGet(testCtx, 0, 4) != ErrnoSuccess {
if environGet(testCtx, m, 0, 4) != ErrnoSuccess {
b.Fatal()
}
}
})
}
func newCtx(buf []byte, sys *wasm.SysContext) *wasm.CallContext {
func newModule(buf []byte, sys *wasm.SysContext) *wasm.CallContext {
return wasm.NewCallContext(nil, &wasm.ModuleInstance{
Memory: &wasm.MemoryInstance{Min: 1, Buffer: buf},
}, sys)

File diff suppressed because it is too large Load Diff

18
wasm.go
View File

@@ -45,7 +45,7 @@ type Runtime interface {
// * Improve performance when the same module is instantiated multiple times under different names
// * Reduce the amount of errors that can occur during InstantiateModule.
//
// Note: when `ctx` is nil, it defaults to context.Background.
// Note: When the context is nil, it defaults to context.Background.
// Note: The resulting module name defaults to what was binary from the custom name section.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0
CompileModule(ctx context.Context, source []byte) (*CompiledCode, error)
@@ -58,7 +58,7 @@ type Runtime interface {
// module, _ := wazero.NewRuntime().InstantiateModuleFromCode(ctx, source)
// defer module.Close()
//
// Note: when `ctx` is nil, it defaults to context.Background.
// Note: When the context is nil, it defaults to context.Background.
// Note: This is a convenience utility that chains CompileModule with InstantiateModule. To instantiate the same
// source multiple times, use CompileModule as InstantiateModule avoids redundant decoding and/or compilation.
InstantiateModuleFromCode(ctx context.Context, source []byte) (api.Module, error)
@@ -74,7 +74,7 @@ type Runtime interface {
// )
// defer wasm.Close()
//
// Note: When `ctx` is nil, it defaults to context.Background.
// Note: When the context is nil, it defaults to context.Background.
InstantiateModuleFromCodeWithConfig(ctx context.Context, source []byte, config *ModuleConfig) (api.Module, error)
// InstantiateModule instantiates the module namespace or errs if the configuration was invalid.
@@ -92,7 +92,7 @@ type Runtime interface {
// * The module has a table element initializer that resolves to an index outside the Table minimum size.
// * The module has a start function, and it failed to execute.
//
// Note: When `ctx` is nil, it defaults to context.Background.
// Note: When the context is nil, it defaults to context.Background.
InstantiateModule(ctx context.Context, compiled *CompiledCode) (api.Module, error)
// InstantiateModuleWithConfig is like InstantiateModule, except you can override configuration such as the module
@@ -111,7 +111,7 @@ type Runtime interface {
// // Assign different configuration on each instantiation
// module, _ := r.InstantiateModuleWithConfig(ctx, compiled, config.WithName("rotate").WithArgs("rotate", "angle=90", "dir=cw"))
//
// Note: when `ctx` is nil, it defaults to context.Background.
// Note: When the context is nil, it defaults to context.Background.
// Note: Config is copied during instantiation: Later changes to config do not affect the instantiated result.
InstantiateModuleWithConfig(ctx context.Context, compiled *CompiledCode, config *ModuleConfig) (mod api.Module, err error)
}
@@ -188,8 +188,8 @@ func (r *runtime) InstantiateModuleFromCode(ctx context.Context, source []byte)
if compiled, err := r.CompileModule(ctx, source); err != nil {
return nil, err
} else {
// *wasm.ModuleInstance for the source cannot be tracked, so we release the cache inside of this function.
defer compiled.Close()
// *wasm.ModuleInstance for the source cannot be tracked, so we release the cache inside this function.
defer compiled.Close(ctx)
return r.InstantiateModule(ctx, compiled)
}
}
@@ -199,8 +199,8 @@ func (r *runtime) InstantiateModuleFromCodeWithConfig(ctx context.Context, sourc
if compiled, err := r.CompileModule(ctx, source); err != nil {
return nil, err
} else {
// *wasm.ModuleInstance for the source cannot be tracked, so we release the cache inside of this function.
defer compiled.Close()
// *wasm.ModuleInstance for the source cannot be tracked, so we release the cache inside this function.
defer compiled.Close(ctx)
return r.InstantiateModuleWithConfig(ctx, compiled, config)
}
}

View File

@@ -59,7 +59,7 @@ func TestRuntime_DecodeModule(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
code, err := r.CompileModule(testCtx, tc.source)
require.NoError(t, err)
defer code.Close()
defer code.Close(testCtx)
if tc.expectedName != "" {
require.Equal(t, tc.expectedName, code.module.NameSection.ModuleName)
}
@@ -156,11 +156,11 @@ func TestModule_Memory(t *testing.T) {
// Instantiate the module and get the export of the above memory
module, err := tc.builder(r).Instantiate(testCtx)
require.NoError(t, err)
defer module.Close()
defer module.Close(testCtx)
mem := module.ExportedMemory("memory")
if tc.expected {
require.Equal(t, tc.expectedLen, mem.Size())
require.Equal(t, tc.expectedLen, mem.Size(testCtx))
} else {
require.Nil(t, mem)
}
@@ -236,20 +236,20 @@ func TestModule_Global(t *testing.T) {
// Instantiate the module and get the export of the above global
module, err := r.InstantiateModule(testCtx, code)
require.NoError(t, err)
defer module.Close()
defer module.Close(testCtx)
global := module.ExportedGlobal("global")
if !tc.expected {
require.Nil(t, global)
return
}
require.Equal(t, uint64(globalVal), global.Get())
require.Equal(t, uint64(globalVal), global.Get(testCtx))
mutable, ok := global.(api.MutableGlobal)
require.Equal(t, tc.expectedMutable, ok)
if ok {
mutable.Set(2)
require.Equal(t, uint64(2), global.Get())
mutable.Set(testCtx, 2)
require.Equal(t, uint64(2), global.Get(testCtx))
}
})
}
@@ -287,12 +287,12 @@ func TestFunction_Context(t *testing.T) {
return expectedResult
}
source, closer := requireImportAndExportFunction(t, r, hostFn, functionName)
defer closer() // nolint
defer closer(testCtx) // nolint
// Instantiate the module and get the export of the above hostFn
module, err := r.InstantiateModuleFromCodeWithConfig(tc.ctx, source, NewModuleConfig().WithName(t.Name()))
require.NoError(t, err)
defer module.Close()
defer module.Close(testCtx)
// This fails if the function wasn't invoked, or had an unexpected context.
results, err := module.ExportedFunction(functionName).Call(tc.ctx)
@@ -316,19 +316,19 @@ func TestRuntime_InstantiateModule_UsesContext(t *testing.T) {
ExportFunction("start", start).
Instantiate(testCtx)
require.NoError(t, err)
defer env.Close()
defer env.Close(testCtx)
code, err := r.CompileModule(testCtx, []byte(`(module $runtime_test.go
(import "env" "start" (func $start))
(start $start)
)`))
require.NoError(t, err)
defer code.Close()
defer code.Close(testCtx)
// Instantiate the module, which calls the start function. This will fail if the context wasn't as intended.
m, err := r.InstantiateModule(testCtx, code)
require.NoError(t, err)
defer m.Close()
defer m.Close(testCtx)
require.True(t, calledStart)
}
@@ -342,7 +342,7 @@ func TestInstantiateModuleFromCode_DoesntEnforce_Start(t *testing.T) {
(export "memory" (memory 0))
)`))
require.NoError(t, err)
require.NoError(t, mod.Close())
require.NoError(t, mod.Close(testCtx))
}
func TestRuntime_InstantiateModuleFromCode_UsesContext(t *testing.T) {
@@ -359,7 +359,7 @@ func TestRuntime_InstantiateModuleFromCode_UsesContext(t *testing.T) {
ExportFunction("start", start).
Instantiate(testCtx)
require.NoError(t, err)
defer host.Close()
defer host.Close(testCtx)
// Start the module as a WASI command. This will fail if the context wasn't as intended.
mod, err := r.InstantiateModuleFromCode(testCtx, []byte(`(module $start
@@ -369,7 +369,7 @@ func TestRuntime_InstantiateModuleFromCode_UsesContext(t *testing.T) {
(export "memory" (memory 0))
)`))
require.NoError(t, err)
defer mod.Close()
defer mod.Close(testCtx)
require.True(t, calledStart)
}
@@ -380,7 +380,7 @@ func TestInstantiateModuleWithConfig_WithName(t *testing.T) {
r := NewRuntime()
base, err := r.CompileModule(testCtx, []byte(`(module $0 (memory 1))`))
require.NoError(t, err)
defer base.Close()
defer base.Close(testCtx)
require.Equal(t, "0", base.module.NameSection.ModuleName)
@@ -388,14 +388,14 @@ func TestInstantiateModuleWithConfig_WithName(t *testing.T) {
internal := r.(*runtime).store
m1, err := r.InstantiateModuleWithConfig(testCtx, base, NewModuleConfig().WithName("1"))
require.NoError(t, err)
defer m1.Close()
defer m1.Close(testCtx)
require.Nil(t, internal.Module("0"))
require.Equal(t, internal.Module("1"), m1)
m2, err := r.InstantiateModuleWithConfig(testCtx, base, NewModuleConfig().WithName("2"))
require.NoError(t, err)
defer m2.Close()
defer m2.Close(testCtx)
require.Nil(t, internal.Module("0"))
require.Equal(t, internal.Module("2"), m2)
@@ -404,8 +404,8 @@ func TestInstantiateModuleWithConfig_WithName(t *testing.T) {
func TestInstantiateModuleWithConfig_ExitError(t *testing.T) {
r := NewRuntime()
start := func(m api.Module) {
require.NoError(t, m.CloseWithExitCode(2))
start := func(ctx context.Context, m api.Module) {
require.NoError(t, m.CloseWithExitCode(ctx, 2))
}
_, err := r.NewModuleBuilder("env").ExportFunction("_start", start).Instantiate(testCtx)
@@ -415,7 +415,7 @@ func TestInstantiateModuleWithConfig_ExitError(t *testing.T) {
}
// requireImportAndExportFunction re-exports a host function because only host functions can see the propagated context.
func requireImportAndExportFunction(t *testing.T, r Runtime, hostFn func(ctx context.Context) uint64, functionName string) ([]byte, func() error) {
func requireImportAndExportFunction(t *testing.T, r Runtime, hostFn func(ctx context.Context) uint64, functionName string) ([]byte, func(context.Context) error) {
mod, err := r.NewModuleBuilder("host").ExportFunction(functionName, hostFn).Instantiate(testCtx)
require.NoError(t, err)
@@ -424,28 +424,6 @@ func requireImportAndExportFunction(t *testing.T, r Runtime, hostFn func(ctx con
)), mod.Close
}
func TestCompiledCode_Close(t *testing.T) {
e := &mockEngine{name: "1", cachedModules: map[*wasm.Module]struct{}{}}
var cs []*CompiledCode
for i := 0; i < 10; i++ {
m := &wasm.Module{}
err := e.CompileModule(testCtx, m)
require.NoError(t, err)
cs = append(cs, &CompiledCode{module: m, compiledEngine: e})
}
// Before Close.
require.Equal(t, 10, len(e.cachedModules))
for _, c := range cs {
c.Close()
}
// After Close.
require.Zero(t, len(e.cachedModules))
}
type mockEngine struct {
name string
cachedModules map[*wasm.Module]struct{}