From 5cea25ed01e96a3f4bc94b44bbf4d2d7106727fd Mon Sep 17 00:00:00 2001 From: Clark McCauley Date: Sat, 5 Oct 2024 17:41:55 -0600 Subject: [PATCH 1/3] Exposed extism module and added methods to get exported functions --- extism.go | 37 ++++++++++++++++--------------------- extism_test.go | 6 +++--- module.go | 30 ++++++++++++++++++++++++++++++ runtime.go | 8 ++++---- 4 files changed, 53 insertions(+), 28 deletions(-) create mode 100644 module.go diff --git a/extism.go b/extism.go index 6245355..8c18cb5 100644 --- a/extism.go +++ b/extism.go @@ -24,11 +24,6 @@ import ( "github.com/tetratelabs/wazero/sys" ) -type module struct { - module api.Module - wasm []byte -} - type PluginCtxKey string type InputOffsetKey string @@ -119,8 +114,8 @@ func (l LogLevel) String() string { // Plugin is used to call WASM functions type Plugin struct { Runtime *Runtime - Modules map[string]module - Main module + Modules map[string]Module + Main Module Timeout time.Duration Config map[string]string // NOTE: maybe we can have some nice methods for getting/setting vars @@ -424,7 +419,7 @@ func NewPlugin( return nil, fmt.Errorf("manifest can't be empty") } - modules := map[string]module{} + modules := map[string]Module{} // NOTE: this is only necessary for guest modules because // host modules have the same access privileges as the host itself @@ -454,11 +449,11 @@ func NewPlugin( moduleConfig = moduleConfig.WithStderr(os.Stderr).WithStdout(os.Stdout) } - // Try to find the main module: - // - There is always one main module - // - If a Wasm value has the Name field set to "main" then use that module - // - If there is only one module in the manifest then that is the main module by default - // - Otherwise the last module listed is the main module + // Try to find the main Module: + // - There is always one main Module + // - If a Wasm value has the Name field set to "main" then use that Module + // - If there is only one Module in the manifest then that is the main Module by default + // - Otherwise the last Module listed is the main Module var trace *observe.TraceCtx for i, wasm := range manifest.Wasm { @@ -485,13 +480,13 @@ func NewPlugin( _, okm := modules[data.Name] if data.Name == "extism:host/env" || okh || okm { - return nil, fmt.Errorf("module name collision: '%s'", data.Name) + return nil, fmt.Errorf("Module name collision: '%s'", data.Name) } if data.Hash != "" { calculatedHash := calculateHash(data.Data) if data.Hash != calculatedHash { - return nil, fmt.Errorf("hash mismatch for module '%s'", data.Name) + return nil, fmt.Errorf("hash mismatch for Module '%s'", data.Name) } } @@ -500,7 +495,7 @@ func NewPlugin( return nil, err } - modules[data.Name] = module{module: m, wasm: data.Data} + modules[data.Name] = Module{inner: m} } i := 0 @@ -514,7 +509,7 @@ func NewPlugin( varMax = int64(manifest.Memory.MaxVarBytes) } for _, m := range modules { - if m.module.Name() == "main" { + if m.inner.Name() == "main" { p := &Plugin{ Runtime: &c, Modules: modules, @@ -539,7 +534,7 @@ func NewPlugin( i++ } - return nil, errors.New("no main module found") + return nil, errors.New("no main Module found") } // SetInput sets the input data for the plugin to be used in the next WebAssembly function call. @@ -619,9 +614,9 @@ func (plugin *Plugin) GetErrorWithContext(ctx context.Context) string { return string(mem) } -// FunctionExists returns true when the named function is present in the plugin's main module +// FunctionExists returns true when the named function is present in the plugin's main Module func (plugin *Plugin) FunctionExists(name string) bool { - return plugin.Main.module.ExportedFunction(name) != nil + return plugin.Main.inner.ExportedFunction(name) != nil } // Call a function by name with the given input, returning the output @@ -646,7 +641,7 @@ func (plugin *Plugin) CallWithContext(ctx context.Context, name string, data []b ctx = context.WithValue(ctx, InputOffsetKey("inputOffset"), intputOffset) - var f = plugin.Main.module.ExportedFunction(name) + var f = plugin.Main.inner.ExportedFunction(name) if f == nil { return 1, []byte{}, fmt.Errorf("unknown function: %s", name) diff --git a/extism_test.go b/extism_test.go index 8476a57..8b41b6c 100644 --- a/extism_test.go +++ b/extism_test.go @@ -550,7 +550,7 @@ func TestTimeout(t *testing.T) { exit, _, err := plugin.Call("run_test", []byte{}) assert.Equal(t, sys.ExitCodeDeadlineExceeded, exit, "Exit code must be `sys.ExitCodeDeadlineExceeded`") - assert.Equal(t, "module closed with context deadline exceeded", err.Error()) + assert.Equal(t, "Module closed with context deadline exceeded", err.Error()) } func TestCancel(t *testing.T) { @@ -581,7 +581,7 @@ func TestCancel(t *testing.T) { exit, _, err := plugin.CallWithContext(ctx, "run_test", []byte{}) assert.Equal(t, sys.ExitCodeContextCanceled, exit, "Exit code must be `sys.ExitCodeContextCanceled`") - assert.Equal(t, "module closed with context canceled", err.Error()) + assert.Equal(t, "Module closed with context canceled", err.Error()) } func TestVar(t *testing.T) { @@ -781,7 +781,7 @@ func TestJsonManifest(t *testing.T) { exit, _, err := plugin.Call("run_test", []byte{}) assert.Equal(t, sys.ExitCodeDeadlineExceeded, exit, "Exit code must be `sys.ExitCodeDeadlineExceeded`") - assert.Equal(t, "module closed with context deadline exceeded", err.Error()) + assert.Equal(t, "Module closed with context deadline exceeded", err.Error()) } } diff --git a/module.go b/module.go new file mode 100644 index 0000000..c5d928d --- /dev/null +++ b/module.go @@ -0,0 +1,30 @@ +package extism + +import "github.com/tetratelabs/wazero/api" + +// Module is a wrapper around a wazero module. It allows us to provide +// our own API and stability guarantees despite any changes that wazero +// may choose to make. +type Module struct { + inner api.Module +} + +// ExportedFunctions returns a map of functions exported from the module +// keyed by the function name. +func (m *Module) ExportedFunctions() map[string]FunctionDefinition { + v := make(map[string]FunctionDefinition) + for name, def := range m.inner.ExportedFunctionDefinitions() { + v[name] = FunctionDefinition{inner: def} + } + return v +} + +// FunctionDefinition represents a function defined in a module. It provides +// a wrapper around the underlying wazero function definition. +type FunctionDefinition struct { + inner api.FunctionDefinition +} + +func (f *FunctionDefinition) Name() string { + return f.inner.Name() +} diff --git a/runtime.go b/runtime.go index 9fa8c00..ef6847f 100644 --- a/runtime.go +++ b/runtime.go @@ -25,12 +25,12 @@ type guestRuntime struct { func detectGuestRuntime(ctx context.Context, p *Plugin) guestRuntime { m := p.Main - runtime, ok := haskellRuntime(ctx, p, m.module) + runtime, ok := haskellRuntime(ctx, p, m.inner) if ok { return runtime } - runtime, ok = wasiRuntime(ctx, p, m.module) + runtime, ok = wasiRuntime(ctx, p, m.inner) if ok { return runtime } @@ -99,7 +99,7 @@ func reactorModule(ctx context.Context, m api.Module, p *Plugin) (guestRuntime, } p.Logf(LogLevelTrace, "WASI runtime detected") - p.Logf(LogLevelTrace, "Reactor module detected") + p.Logf(LogLevelTrace, "Reactor Module detected") return guestRuntime{runtimeType: Wasi, init: init}, true } @@ -113,7 +113,7 @@ func commandModule(ctx context.Context, m api.Module, p *Plugin) (guestRuntime, } p.Logf(LogLevelTrace, "WASI runtime detected") - p.Logf(LogLevelTrace, "Command module detected") + p.Logf(LogLevelTrace, "Command Module detected") return guestRuntime{runtimeType: Wasi, init: init}, true } From e7685181d6801b22508ce4f152064d3a1c40ad33 Mon Sep 17 00:00:00 2001 From: Clark McCauley Date: Sat, 5 Oct 2024 17:47:19 -0600 Subject: [PATCH 2/3] Oops --- extism.go | 12 ++++++------ extism_test.go | 6 +++--- runtime.go | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/extism.go b/extism.go index 8c18cb5..59f0f95 100644 --- a/extism.go +++ b/extism.go @@ -449,11 +449,11 @@ func NewPlugin( moduleConfig = moduleConfig.WithStderr(os.Stderr).WithStdout(os.Stdout) } - // Try to find the main Module: - // - There is always one main Module - // - If a Wasm value has the Name field set to "main" then use that Module - // - If there is only one Module in the manifest then that is the main Module by default - // - Otherwise the last Module listed is the main Module + // Try to find the main module: + // - There is always one main module + // - If a Wasm value has the Name field set to "main" then use that module + // - If there is only one module in the manifest then that is the main module by default + // - Otherwise the last module listed is the main module var trace *observe.TraceCtx for i, wasm := range manifest.Wasm { @@ -480,7 +480,7 @@ func NewPlugin( _, okm := modules[data.Name] if data.Name == "extism:host/env" || okh || okm { - return nil, fmt.Errorf("Module name collision: '%s'", data.Name) + return nil, fmt.Errorf("module name collision: '%s'", data.Name) } if data.Hash != "" { diff --git a/extism_test.go b/extism_test.go index 8b41b6c..8476a57 100644 --- a/extism_test.go +++ b/extism_test.go @@ -550,7 +550,7 @@ func TestTimeout(t *testing.T) { exit, _, err := plugin.Call("run_test", []byte{}) assert.Equal(t, sys.ExitCodeDeadlineExceeded, exit, "Exit code must be `sys.ExitCodeDeadlineExceeded`") - assert.Equal(t, "Module closed with context deadline exceeded", err.Error()) + assert.Equal(t, "module closed with context deadline exceeded", err.Error()) } func TestCancel(t *testing.T) { @@ -581,7 +581,7 @@ func TestCancel(t *testing.T) { exit, _, err := plugin.CallWithContext(ctx, "run_test", []byte{}) assert.Equal(t, sys.ExitCodeContextCanceled, exit, "Exit code must be `sys.ExitCodeContextCanceled`") - assert.Equal(t, "Module closed with context canceled", err.Error()) + assert.Equal(t, "module closed with context canceled", err.Error()) } func TestVar(t *testing.T) { @@ -781,7 +781,7 @@ func TestJsonManifest(t *testing.T) { exit, _, err := plugin.Call("run_test", []byte{}) assert.Equal(t, sys.ExitCodeDeadlineExceeded, exit, "Exit code must be `sys.ExitCodeDeadlineExceeded`") - assert.Equal(t, "Module closed with context deadline exceeded", err.Error()) + assert.Equal(t, "module closed with context deadline exceeded", err.Error()) } } diff --git a/runtime.go b/runtime.go index ef6847f..d079003 100644 --- a/runtime.go +++ b/runtime.go @@ -99,7 +99,7 @@ func reactorModule(ctx context.Context, m api.Module, p *Plugin) (guestRuntime, } p.Logf(LogLevelTrace, "WASI runtime detected") - p.Logf(LogLevelTrace, "Reactor Module detected") + p.Logf(LogLevelTrace, "Reactor module detected") return guestRuntime{runtimeType: Wasi, init: init}, true } @@ -113,7 +113,7 @@ func commandModule(ctx context.Context, m api.Module, p *Plugin) (guestRuntime, } p.Logf(LogLevelTrace, "WASI runtime detected") - p.Logf(LogLevelTrace, "Command Module detected") + p.Logf(LogLevelTrace, "Command module detected") return guestRuntime{runtimeType: Wasi, init: init}, true } From 14c4c4c3ecaa454bc3a91e15ba1c712dd93c9c40 Mon Sep 17 00:00:00 2001 From: Clark McCauley Date: Sat, 5 Oct 2024 17:48:10 -0600 Subject: [PATCH 3/3] Oops --- extism.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extism.go b/extism.go index 59f0f95..5648076 100644 --- a/extism.go +++ b/extism.go @@ -486,7 +486,7 @@ func NewPlugin( if data.Hash != "" { calculatedHash := calculateHash(data.Data) if data.Hash != calculatedHash { - return nil, fmt.Errorf("hash mismatch for Module '%s'", data.Name) + return nil, fmt.Errorf("hash mismatch for module '%s'", data.Name) } } @@ -534,7 +534,7 @@ func NewPlugin( i++ } - return nil, errors.New("no main Module found") + return nil, errors.New("no main module found") } // SetInput sets the input data for the plugin to be used in the next WebAssembly function call.