Makes wazero.CompiledCode an interface instead of a struct (#519)
This makes wazero.CompiledCode an interface instead of a struct to prevent it from being used incorrectly. For example, even though the fields are not exported, someone can mistakenly instantiate this when it is a struct, and in doing so violate internal assumptions. Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
14
builder.go
14
builder.go
@@ -153,7 +153,7 @@ type ModuleBuilder interface {
|
|||||||
ExportGlobalF64(name string, v float64) ModuleBuilder
|
ExportGlobalF64(name string, v float64) ModuleBuilder
|
||||||
|
|
||||||
// Build returns a module to instantiate, or returns an error if any of the configuration is invalid.
|
// Build returns a module to instantiate, or returns an error if any of the configuration is invalid.
|
||||||
Build(context.Context) (*CompiledCode, error)
|
Build(context.Context) (CompiledCode, error)
|
||||||
|
|
||||||
// Instantiate is a convenience that calls Build, then Runtime.InstantiateModule
|
// Instantiate is a convenience that calls Build, then Runtime.InstantiateModule
|
||||||
//
|
//
|
||||||
@@ -250,7 +250,7 @@ func (b *moduleBuilder) ExportGlobalF64(name string, v float64) ModuleBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build implements ModuleBuilder.Build
|
// Build implements ModuleBuilder.Build
|
||||||
func (b *moduleBuilder) Build(ctx context.Context) (*CompiledCode, error) {
|
func (b *moduleBuilder) Build(ctx context.Context) (CompiledCode, error) {
|
||||||
// Verify the maximum limit here, so we don't have to pass it to wasm.NewHostModule
|
// Verify the maximum limit here, so we don't have to pass it to wasm.NewHostModule
|
||||||
memoryLimitPages := b.r.memoryLimitPages
|
memoryLimitPages := b.r.memoryLimitPages
|
||||||
for name, mem := range b.nameToMemory {
|
for name, mem := range b.nameToMemory {
|
||||||
@@ -271,19 +271,19 @@ func (b *moduleBuilder) Build(ctx context.Context) (*CompiledCode, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CompiledCode{module: module, compiledEngine: b.r.store.Engine}, nil
|
return &compiledCode{module: module, compiledEngine: b.r.store.Engine}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instantiate implements ModuleBuilder.Instantiate
|
// Instantiate implements ModuleBuilder.Instantiate
|
||||||
func (b *moduleBuilder) Instantiate(ctx context.Context) (api.Module, error) {
|
func (b *moduleBuilder) Instantiate(ctx context.Context) (api.Module, error) {
|
||||||
if module, err := b.Build(ctx); err != nil {
|
if compiled, err := b.Build(ctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
if err = b.r.store.Engine.CompileModule(ctx, module.module); err != nil {
|
if err = b.r.store.Engine.CompileModule(ctx, compiled.(*compiledCode).module); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// *wasm.ModuleInstance cannot be tracked, so we release the cache inside this function.
|
// *wasm.ModuleInstance cannot be tracked, so we release the cache inside this function.
|
||||||
defer module.Close(ctx)
|
defer compiled.Close(ctx)
|
||||||
return b.r.InstantiateModuleWithConfig(ctx, module, NewModuleConfig().WithName(b.moduleName))
|
return b.r.InstantiateModuleWithConfig(ctx, compiled, NewModuleConfig().WithName(b.moduleName))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -344,8 +344,9 @@ func TestNewModuleBuilder_Build(t *testing.T) {
|
|||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
b := tc.input(NewRuntime()).(*moduleBuilder)
|
b := tc.input(NewRuntime()).(*moduleBuilder)
|
||||||
m, err := b.Build(testCtx)
|
compiled, err := b.Build(testCtx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
m := compiled.(*compiledCode)
|
||||||
|
|
||||||
requireHostModuleEquals(t, tc.expected, m.module)
|
requireHostModuleEquals(t, tc.expected, m.module)
|
||||||
|
|
||||||
|
|||||||
17
config.go
17
config.go
@@ -238,22 +238,27 @@ func (c *runtimeConfig) WithWasmCore2() RuntimeConfig {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// CompiledCode is a WebAssembly 1.0 (20191205) module ready to be instantiated (Runtime.InstantiateModule) as an\
|
// CompiledCode is a WebAssembly 1.0 (20191205) module ready to be instantiated (Runtime.InstantiateModule) as an
|
||||||
// api.Module.
|
// api.Module.
|
||||||
//
|
//
|
||||||
// Note: In WebAssembly language, this is a decoded, validated, and possibly also compiled module. wazero avoids using
|
// Note: In WebAssembly language, this is a decoded, validated, and possibly also compiled module. wazero avoids using
|
||||||
// the name "Module" for both before and after instantiation as the name conflation has caused confusion.
|
// the name "Module" for both before and after instantiation as the name conflation has caused confusion.
|
||||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#semantic-phases%E2%91%A0
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#semantic-phases%E2%91%A0
|
||||||
type CompiledCode struct {
|
type CompiledCode interface {
|
||||||
|
// 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.
|
||||||
|
Close(context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type compiledCode struct {
|
||||||
module *wasm.Module
|
module *wasm.Module
|
||||||
// compiledEngine holds an engine on which `module` is compiled.
|
// compiledEngine holds an engine on which `module` is compiled.
|
||||||
compiledEngine wasm.Engine
|
compiledEngine wasm.Engine
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close releases all the allocated resources for this CompiledCode.
|
// Close implements CompiledCode.Close
|
||||||
//
|
func (c *compiledCode) Close(_ context.Context) error {
|
||||||
// Note: It is safe to call Close while having outstanding calls from Modules instantiated from this *CompiledCode.
|
|
||||||
func (c *CompiledCode) Close(_ context.Context) error {
|
|
||||||
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
|
// Note: If you use the context.Context param, don't forget to coerce nil to context.Background()!
|
||||||
|
|
||||||
c.compiledEngine.DeleteCompiledModule(c.module)
|
c.compiledEngine.DeleteCompiledModule(c.module)
|
||||||
|
|||||||
@@ -853,12 +853,12 @@ func TestCompiledCode_Close(t *testing.T) {
|
|||||||
for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil!
|
for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil!
|
||||||
e := &mockEngine{name: "1", cachedModules: map[*wasm.Module]struct{}{}}
|
e := &mockEngine{name: "1", cachedModules: map[*wasm.Module]struct{}{}}
|
||||||
|
|
||||||
var cs []*CompiledCode
|
var cs []*compiledCode
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
m := &wasm.Module{}
|
m := &wasm.Module{}
|
||||||
err := e.CompileModule(ctx, m)
|
err := e.CompileModule(ctx, m)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
cs = append(cs, &CompiledCode{module: m, compiledEngine: e})
|
cs = append(cs, &compiledCode{module: m, compiledEngine: e})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Before Close.
|
// Before Close.
|
||||||
|
|||||||
@@ -364,7 +364,7 @@ func testCloseInFlight(t *testing.T, r wazero.Runtime) {
|
|||||||
tc := tt
|
tc := tt
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
var importingCode, importedCode *wazero.CompiledCode
|
var importingCode, importedCode wazero.CompiledCode
|
||||||
var imported, importing api.Module
|
var imported, importing api.Module
|
||||||
var err error
|
var err error
|
||||||
closeAndReturn := func(ctx context.Context, x uint32) uint32 {
|
closeAndReturn := func(ctx context.Context, x uint32) uint32 {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ type wazeroRuntime struct {
|
|||||||
config wazero.RuntimeConfig
|
config wazero.RuntimeConfig
|
||||||
runtime wazero.Runtime
|
runtime wazero.Runtime
|
||||||
logFn func([]byte) error
|
logFn func([]byte) error
|
||||||
env, compiled *wazero.CompiledCode
|
env, compiled wazero.CompiledCode
|
||||||
}
|
}
|
||||||
|
|
||||||
type wazeroModule struct {
|
type wazeroModule struct {
|
||||||
|
|||||||
27
wasm.go
27
wasm.go
@@ -49,7 +49,7 @@ type Runtime interface {
|
|||||||
// Note: When the context 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.
|
// 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
|
// 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)
|
CompileModule(ctx context.Context, source []byte) (CompiledCode, error)
|
||||||
|
|
||||||
// InstantiateModuleFromCode instantiates a module from the WebAssembly 1.0 (20191205) text or binary source or
|
// InstantiateModuleFromCode instantiates a module from the WebAssembly 1.0 (20191205) text or binary source or
|
||||||
// errs if invalid.
|
// errs if invalid.
|
||||||
@@ -94,7 +94,7 @@ type Runtime interface {
|
|||||||
// * The module has a start function, and it failed to execute.
|
// * The module has a start function, and it failed to execute.
|
||||||
//
|
//
|
||||||
// Note: When the context 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)
|
InstantiateModule(ctx context.Context, compiled CompiledCode) (api.Module, error)
|
||||||
|
|
||||||
// InstantiateModuleWithConfig is like InstantiateModule, except you can override configuration such as the module
|
// InstantiateModuleWithConfig is like InstantiateModule, except you can override configuration such as the module
|
||||||
// name or ENV variables.
|
// name or ENV variables.
|
||||||
@@ -114,7 +114,7 @@ type Runtime interface {
|
|||||||
//
|
//
|
||||||
// Note: When the context 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.
|
// 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)
|
InstantiateModuleWithConfig(ctx context.Context, compiled CompiledCode, config *ModuleConfig) (mod api.Module, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRuntime() Runtime {
|
func NewRuntime() Runtime {
|
||||||
@@ -149,7 +149,7 @@ func (r *runtime) Module(moduleName string) api.Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CompileModule implements Runtime.CompileModule
|
// CompileModule implements Runtime.CompileModule
|
||||||
func (r *runtime) CompileModule(ctx context.Context, source []byte) (*CompiledCode, error) {
|
func (r *runtime) CompileModule(ctx context.Context, source []byte) (CompiledCode, error) {
|
||||||
if source == nil {
|
if source == nil {
|
||||||
return nil, errors.New("source == nil")
|
return nil, errors.New("source == nil")
|
||||||
}
|
}
|
||||||
@@ -202,7 +202,7 @@ func (r *runtime) CompileModule(ctx context.Context, source []byte) (*CompiledCo
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CompiledCode{module: internal, compiledEngine: r.store.Engine}, nil
|
return &compiledCode{module: internal, compiledEngine: r.store.Engine}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstantiateModuleFromCode implements Runtime.InstantiateModuleFromCode
|
// InstantiateModuleFromCode implements Runtime.InstantiateModuleFromCode
|
||||||
@@ -228,23 +228,28 @@ func (r *runtime) InstantiateModuleFromCodeWithConfig(ctx context.Context, sourc
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InstantiateModule implements Runtime.InstantiateModule
|
// InstantiateModule implements Runtime.InstantiateModule
|
||||||
func (r *runtime) InstantiateModule(ctx context.Context, compiled *CompiledCode) (mod api.Module, err error) {
|
func (r *runtime) InstantiateModule(ctx context.Context, compiled CompiledCode) (mod api.Module, err error) {
|
||||||
return r.InstantiateModuleWithConfig(ctx, compiled, NewModuleConfig())
|
return r.InstantiateModuleWithConfig(ctx, compiled, NewModuleConfig())
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstantiateModuleWithConfig implements Runtime.InstantiateModuleWithConfig
|
// InstantiateModuleWithConfig implements Runtime.InstantiateModuleWithConfig
|
||||||
func (r *runtime) InstantiateModuleWithConfig(ctx context.Context, compiled *CompiledCode, config *ModuleConfig) (mod api.Module, err error) {
|
func (r *runtime) InstantiateModuleWithConfig(ctx context.Context, compiled CompiledCode, config *ModuleConfig) (mod api.Module, err error) {
|
||||||
var sysCtx *wasm.SysContext
|
var sysCtx *wasm.SysContext
|
||||||
if sysCtx, err = config.toSysContext(); err != nil {
|
if sysCtx, err = config.toSysContext(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
name := config.name
|
code, ok := compiled.(*compiledCode)
|
||||||
if name == "" && compiled.module.NameSection != nil && compiled.module.NameSection.ModuleName != "" {
|
if !ok {
|
||||||
name = compiled.module.NameSection.ModuleName
|
panic(fmt.Errorf("unsupported wazero.CompiledCode implementation: %#v", compiled))
|
||||||
}
|
}
|
||||||
|
|
||||||
module := config.replaceImports(compiled.module)
|
name := config.name
|
||||||
|
if name == "" && code.module.NameSection != nil && code.module.NameSection.ModuleName != "" {
|
||||||
|
name = code.module.NameSection.ModuleName
|
||||||
|
}
|
||||||
|
|
||||||
|
module := config.replaceImports(code.module)
|
||||||
|
|
||||||
var functionListenerFactory experimentalapi.FunctionListenerFactory
|
var functionListenerFactory experimentalapi.FunctionListenerFactory
|
||||||
if ctx != nil { // Test to see if internal code are using an experimental feature.
|
if ctx != nil { // Test to see if internal code are using an experimental feature.
|
||||||
|
|||||||
28
wasm_test.go
28
wasm_test.go
@@ -19,7 +19,7 @@ import (
|
|||||||
var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
|
var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
|
||||||
|
|
||||||
func TestNewRuntimeWithConfig_PanicsOnWrongImpl(t *testing.T) {
|
func TestNewRuntimeWithConfig_PanicsOnWrongImpl(t *testing.T) {
|
||||||
// It is too burdensome to define an impl of RuntimeConfig in tests just to verify the error when it is wrong.
|
// It causes maintenance to define an impl of RuntimeConfig in tests just to verify the error when it is wrong.
|
||||||
// Instead, we pass nil which is implicitly the wrong type, as that's less work!
|
// Instead, we pass nil which is implicitly the wrong type, as that's less work!
|
||||||
err := require.CapturePanic(func() {
|
err := require.CapturePanic(func() {
|
||||||
NewRuntimeWithConfig(nil)
|
NewRuntimeWithConfig(nil)
|
||||||
@@ -68,8 +68,9 @@ func TestRuntime_CompileModule(t *testing.T) {
|
|||||||
tc := tt
|
tc := tt
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
code, err := r.CompileModule(testCtx, tc.source)
|
m, err := r.CompileModule(testCtx, tc.source)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
code := m.(*compiledCode)
|
||||||
defer code.Close(testCtx)
|
defer code.Close(testCtx)
|
||||||
if tc.expectedName != "" {
|
if tc.expectedName != "" {
|
||||||
require.Equal(t, tc.expectedName, code.module.NameSection.ModuleName)
|
require.Equal(t, tc.expectedName, code.module.NameSection.ModuleName)
|
||||||
@@ -84,8 +85,9 @@ func TestRuntime_CompileModule(t *testing.T) {
|
|||||||
|
|
||||||
source := []byte(`(module (memory 1 3))`)
|
source := []byte(`(module (memory 1 3))`)
|
||||||
|
|
||||||
code, err := r.CompileModule(testCtx, source)
|
m, err := r.CompileModule(testCtx, source)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
code := m.(*compiledCode)
|
||||||
defer code.Close(testCtx)
|
defer code.Close(testCtx)
|
||||||
|
|
||||||
require.Equal(t, &wasm.Memory{
|
require.Equal(t, &wasm.Memory{
|
||||||
@@ -312,12 +314,13 @@ func TestModule_Global(t *testing.T) {
|
|||||||
|
|
||||||
r := NewRuntime().(*runtime)
|
r := NewRuntime().(*runtime)
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
var code *CompiledCode
|
var m CompiledCode
|
||||||
if tc.module != nil {
|
if tc.module != nil {
|
||||||
code = &CompiledCode{module: tc.module}
|
m = &compiledCode{module: tc.module}
|
||||||
} else {
|
} else {
|
||||||
code, _ = tc.builder(r).Build(testCtx)
|
m, _ = tc.builder(r).Build(testCtx)
|
||||||
}
|
}
|
||||||
|
code := m.(*compiledCode)
|
||||||
|
|
||||||
err := r.store.Engine.CompileModule(testCtx, code.module)
|
err := r.store.Engine.CompileModule(testCtx, code.module)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -463,6 +466,17 @@ func TestRuntime_InstantiateModuleFromCode_UsesContext(t *testing.T) {
|
|||||||
require.True(t, calledStart)
|
require.True(t, calledStart)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInstantiateModuleWithConfig_PanicsOnWrongCompiledCodeImpl(t *testing.T) {
|
||||||
|
// It causes maintenance to define an impl of CompiledCode in tests just to verify the error when it is wrong.
|
||||||
|
// Instead, we pass nil which is implicitly the wrong type, as that's less work!
|
||||||
|
r := NewRuntime()
|
||||||
|
err := require.CapturePanic(func() {
|
||||||
|
_, _ = r.InstantiateModuleWithConfig(testCtx, nil, NewModuleConfig())
|
||||||
|
})
|
||||||
|
|
||||||
|
require.EqualError(t, err, "unsupported wazero.CompiledCode implementation: <nil>")
|
||||||
|
}
|
||||||
|
|
||||||
// TestInstantiateModuleWithConfig_WithName tests that we can pre-validate (cache) a module and instantiate it under
|
// TestInstantiateModuleWithConfig_WithName tests that we can pre-validate (cache) a module and instantiate it under
|
||||||
// different names. This pattern is used in wapc-go.
|
// different names. This pattern is used in wapc-go.
|
||||||
func TestInstantiateModuleWithConfig_WithName(t *testing.T) {
|
func TestInstantiateModuleWithConfig_WithName(t *testing.T) {
|
||||||
@@ -471,7 +485,7 @@ func TestInstantiateModuleWithConfig_WithName(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer base.Close(testCtx)
|
defer base.Close(testCtx)
|
||||||
|
|
||||||
require.Equal(t, "0", base.module.NameSection.ModuleName)
|
require.Equal(t, "0", base.(*compiledCode).module.NameSection.ModuleName)
|
||||||
|
|
||||||
// Use the same runtime to instantiate multiple modules
|
// Use the same runtime to instantiate multiple modules
|
||||||
internal := r.(*runtime).store
|
internal := r.(*runtime).store
|
||||||
|
|||||||
Reference in New Issue
Block a user