From df0faa5d1681bcc75abc15a806976db2bdd546c8 Mon Sep 17 00:00:00 2001 From: Crypt Keeper <64215+codefromthecrypt@users.noreply.github.com> Date: Tue, 18 Apr 2023 09:13:50 +0200 Subject: [PATCH] emscripten: adds experimental InstantiateForModule for invoke exports (#1372) This adds emscripten.InstantiateForModule into the experimental package. This builds dynamic invoke exports in the same order and only matching those needed by the importing modules. Finally, this removes special casing of function type IDs by deferring resolution of them only in Emscripten. Fixes #1364 Signed-off-by: Adrian Cole --- experimental/emscripten/emscripten.go | 60 +++ experimental/emscripten/emscripten_test.go | 494 +++++++++++++++++++ experimental/emscripten/testdata/invoke.wasm | Bin 0 -> 1497 bytes experimental/emscripten/testdata/invoke.wat | 115 +++++ imports/emscripten/emscripten.go | 268 +--------- internal/emscripten/emscripten.go | 95 ++++ internal/wasm/store.go | 54 +- internal/wasm/store_test.go | 24 +- runtime.go | 6 + 9 files changed, 795 insertions(+), 321 deletions(-) create mode 100644 experimental/emscripten/emscripten.go create mode 100644 experimental/emscripten/emscripten_test.go create mode 100644 experimental/emscripten/testdata/invoke.wasm create mode 100644 experimental/emscripten/testdata/invoke.wat create mode 100644 internal/emscripten/emscripten.go diff --git a/experimental/emscripten/emscripten.go b/experimental/emscripten/emscripten.go new file mode 100644 index 00000000..f71b2db2 --- /dev/null +++ b/experimental/emscripten/emscripten.go @@ -0,0 +1,60 @@ +package emscripten + +import ( + "context" + "strings" + + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/imports/emscripten" + internal "github.com/tetratelabs/wazero/internal/emscripten" + "github.com/tetratelabs/wazero/internal/wasm" +) + +type emscriptenFns []*wasm.HostFunc + +// InstantiateForModule instantiates a module named "env" populated with any +// known functions used in emscripten. +func InstantiateForModule(ctx context.Context, r wazero.Runtime, guest wazero.CompiledModule) (api.Closer, error) { + // Create the exporter for the supplied wasm + exporter, err := NewFunctionExporterForModule(guest) + if err != nil { + return nil, err + } + + // Instantiate it! + env := r.NewHostModuleBuilder("env") + exporter.ExportFunctions(env) + return env.Instantiate(ctx) +} + +// NewFunctionExporterForModule returns a guest-specific FunctionExporter, +// populated with any known functions used in emscripten. +func NewFunctionExporterForModule(guest wazero.CompiledModule) (emscripten.FunctionExporter, error) { + ret := emscriptenFns{} + for _, fn := range guest.ImportedFunctions() { + importModule, importName, isImport := fn.Import() + if !isImport || importModule != "env" { + continue // not emscripten + } + if importName == internal.FunctionNotifyMemoryGrowth { + ret = append(ret, internal.NotifyMemoryGrowth) + continue + } + if !strings.HasPrefix(importName, internal.InvokePrefix) { + continue // not invoke, and maybe not emscripten + } + + hf := internal.NewInvokeFunc(importName, fn.ParamTypes(), fn.ResultTypes()) + ret = append(ret, hf) + } + return ret, nil +} + +// ExportFunctions implements FunctionExporter.ExportFunctions +func (i emscriptenFns) ExportFunctions(builder wazero.HostModuleBuilder) { + exporter := builder.(wasm.HostFuncExporter) + for _, fn := range i { + exporter.ExportHostFunc(fn) + } +} diff --git a/experimental/emscripten/emscripten_test.go b/experimental/emscripten/emscripten_test.go new file mode 100644 index 00000000..9f88db3f --- /dev/null +++ b/experimental/emscripten/emscripten_test.go @@ -0,0 +1,494 @@ +package emscripten + +import ( + "bytes" + "context" + _ "embed" + "testing" + + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/experimental" + "github.com/tetratelabs/wazero/experimental/logging" + internal "github.com/tetratelabs/wazero/internal/emscripten" + "github.com/tetratelabs/wazero/internal/testing/binaryencoding" + "github.com/tetratelabs/wazero/internal/testing/require" + "github.com/tetratelabs/wazero/internal/wasm" +) + +const ( + i32 = wasm.ValueTypeI32 + i64 = wasm.ValueTypeI64 + f32 = wasm.ValueTypeF32 + f64 = wasm.ValueTypeF64 +) + +// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors. +var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary") + +func TestNewFunctionExporterForModule(t *testing.T) { + tests := []struct { + name string + input *wasm.Module + expected emscriptenFns + }{ + { + name: "empty", + input: &wasm.Module{}, + expected: emscriptenFns{}, + }, + { + name: internal.FunctionNotifyMemoryGrowth, + input: &wasm.Module{ + TypeSection: []wasm.FunctionType{ + {Params: []wasm.ValueType{i32}}, + }, + ImportSection: []wasm.Import{ + { + Module: "env", Name: internal.FunctionNotifyMemoryGrowth, + Type: wasm.ExternTypeFunc, + DescFunc: 0, + }, + }, + }, + expected: []*wasm.HostFunc{internal.NotifyMemoryGrowth}, + }, + { + name: "all result types", + input: &wasm.Module{ + TypeSection: []wasm.FunctionType{ + {Params: []wasm.ValueType{i32}}, + {Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}}, + {Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i64}}, + {Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{f32}}, + {Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{f64}}, + }, + ImportSection: []wasm.Import{ + { + Module: "env", Name: "invoke_v", + Type: wasm.ExternTypeFunc, + DescFunc: 0, + }, + { + Module: "env", Name: "invoke_i", + Type: wasm.ExternTypeFunc, + DescFunc: 1, + }, + { + Module: "env", Name: "invoke_p", + Type: wasm.ExternTypeFunc, + DescFunc: 1, + }, + { + Module: "env", Name: "invoke_j", + Type: wasm.ExternTypeFunc, + DescFunc: 2, + }, + { + Module: "env", Name: "invoke_f", + Type: wasm.ExternTypeFunc, + DescFunc: 3, + }, + { + Module: "env", Name: "invoke_d", + Type: wasm.ExternTypeFunc, + DescFunc: 4, + }, + }, + }, + expected: []*wasm.HostFunc{ + { + ExportName: "invoke_v", + ParamTypes: []api.ValueType{i32}, + ParamNames: []string{"index"}, + Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{}}}, + }, + { + ExportName: "invoke_i", + ParamTypes: []api.ValueType{i32}, + ParamNames: []string{"index"}, + ResultTypes: []api.ValueType{i32}, + Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{Results: []api.ValueType{i32}}}}, + }, + { + ExportName: "invoke_p", + ParamTypes: []api.ValueType{i32}, + ParamNames: []string{"index"}, + ResultTypes: []api.ValueType{i32}, + Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{Results: []api.ValueType{i32}}}}, + }, + { + ExportName: "invoke_j", + ParamTypes: []api.ValueType{i32}, + ParamNames: []string{"index"}, + ResultTypes: []api.ValueType{i64}, + Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{Results: []api.ValueType{i64}}}}, + }, + { + ExportName: "invoke_f", + ParamTypes: []api.ValueType{i32}, + ParamNames: []string{"index"}, + ResultTypes: []api.ValueType{f32}, + Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{Results: []api.ValueType{f32}}}}, + }, + { + ExportName: "invoke_d", + ParamTypes: []api.ValueType{i32}, + ParamNames: []string{"index"}, + ResultTypes: []api.ValueType{f64}, + Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{Results: []api.ValueType{f64}}}}, + }, + }, + }, + { + name: "ignores other imports", + input: &wasm.Module{ + TypeSection: []wasm.FunctionType{ + {Params: []wasm.ValueType{i32}}, + }, + ImportSection: []wasm.Import{ + { + Module: "anv", Name: "invoke_v", + Type: wasm.ExternTypeFunc, + DescFunc: 0, + }, + { + Module: "env", Name: "invoke_v", + Type: wasm.ExternTypeFunc, + DescFunc: 0, + }, + { + Module: "env", Name: "grow", + Type: wasm.ExternTypeFunc, + DescFunc: 0, + }, + }, + }, + expected: []*wasm.HostFunc{ + { + ExportName: "invoke_v", + ParamTypes: []api.ValueType{i32}, + ParamNames: []string{"index"}, + Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{}}}, + }, + }, + }, + { + name: "invoke_v and " + internal.FunctionNotifyMemoryGrowth, + input: &wasm.Module{ + TypeSection: []wasm.FunctionType{{Params: []wasm.ValueType{i32}}}, + ImportSection: []wasm.Import{ + { + Module: "env", Name: "invoke_v", + Type: wasm.ExternTypeFunc, + DescFunc: 0, + }, + { + Module: "env", Name: internal.FunctionNotifyMemoryGrowth, + Type: wasm.ExternTypeFunc, + DescFunc: 0, + }, + }, + }, + expected: []*wasm.HostFunc{ + { + ExportName: "invoke_v", + ParamTypes: []api.ValueType{i32}, + ParamNames: []string{"index"}, + Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{}}}, + }, + internal.NotifyMemoryGrowth, + }, + }, + { + name: "invoke_vi", + input: &wasm.Module{ + TypeSection: []wasm.FunctionType{ + {Params: []wasm.ValueType{i32, i32}}, + }, + ImportSection: []wasm.Import{ + { + Module: "env", Name: "invoke_vi", + Type: wasm.ExternTypeFunc, + DescFunc: 0, + }, + }, + }, + expected: []*wasm.HostFunc{ + { + ExportName: "invoke_vi", + ParamTypes: []api.ValueType{i32, i32}, + ParamNames: []string{"index", "a1"}, + Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{Params: []api.ValueType{i32}}}}, + }, + }, + }, + { + name: "invoke_iiiii", + input: &wasm.Module{ + TypeSection: []wasm.FunctionType{ + { + Params: []wasm.ValueType{i32, i32, i32, i32, i32}, + Results: []wasm.ValueType{i32}, + }, + }, + ImportSection: []wasm.Import{ + { + Module: "env", Name: "invoke_iiiii", + Type: wasm.ExternTypeFunc, + DescFunc: 0, + }, + }, + }, + expected: []*wasm.HostFunc{ + { + ExportName: "invoke_iiiii", + ParamTypes: []api.ValueType{i32, i32, i32, i32, i32}, + ParamNames: []string{"index", "a1", "a2", "a3", "a4"}, + ResultTypes: []wasm.ValueType{i32}, + Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{ + Params: []api.ValueType{i32, i32, i32, i32}, + Results: []api.ValueType{i32}, + }}}, + }, + }, + }, + { + name: "invoke_viiiddiiiiii", + input: &wasm.Module{ + TypeSection: []wasm.FunctionType{ + { + Params: []wasm.ValueType{i32, i32, i32, i32, f64, f64, i32, i32, i32, i32, i32, i32}, + }, + }, + ImportSection: []wasm.Import{ + { + Module: "env", Name: "invoke_viiiddiiiiii", + Type: wasm.ExternTypeFunc, + DescFunc: 0, + }, + }, + }, + expected: []*wasm.HostFunc{ + { + ExportName: "invoke_viiiddiiiiii", + ParamTypes: []api.ValueType{i32, i32, i32, i32, f64, f64, i32, i32, i32, i32, i32, i32}, + ParamNames: []string{"index", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10", "a11"}, + Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{ + Params: []api.ValueType{i32, i32, i32, f64, f64, i32, i32, i32, i32, i32, i32}, + }}}, + }, + }, + }, + } + + for _, tt := range tests { + tc := tt + + t.Run(tc.name, func(t *testing.T) { + r := wazero.NewRuntime(testCtx) + defer r.Close(testCtx) + + guest, err := r.CompileModule(testCtx, binaryencoding.EncodeModule(tc.input)) + require.NoError(t, err) + + exporter, err := NewFunctionExporterForModule(guest) + require.NoError(t, err) + actual := exporter.(emscriptenFns) + + require.Equal(t, len(tc.expected), len(actual)) + for i, expected := range tc.expected { + require.Equal(t, expected, actual[i], actual[i].ExportName) + } + }) + } +} + +// invokeWasm was generated by the following: +// +// cd testdata; wat2wasm --debug-names invoke.wat +// +//go:embed testdata/invoke.wasm +var invokeWasm []byte + +func TestInstantiateForModule(t *testing.T) { + var log bytes.Buffer + + // Set context to one that has an experimental listener + ctx := context.WithValue(testCtx, experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&log)) + + r := wazero.NewRuntime(ctx) + defer r.Close(ctx) + + compiled, err := r.CompileModule(ctx, invokeWasm) + require.NoError(t, err) + + _, err = InstantiateForModule(ctx, r, compiled) + require.NoError(t, err) + + mod, err := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig()) + require.NoError(t, err) + + tests := []struct { + name, funcName string + tableOffset int + params, expectedResults []uint64 + expectedLog string + }{ + { + name: "invoke_i", + funcName: "call_v_i32", + expectedResults: []uint64{42}, + expectedLog: `--> .call_v_i32(0) + ==> env.invoke_i(index=0) + --> .v_i32() + <-- 42 + <== 42 +<-- 42 +`, + }, + { + name: "invoke_ii", + funcName: "call_i32_i32", + tableOffset: 2, + params: []uint64{42}, + expectedResults: []uint64{42}, + expectedLog: `--> .call_i32_i32(2,42) + ==> env.invoke_ii(index=2,a1=42) + --> .i32_i32(42) + <-- 42 + <== 42 +<-- 42 +`, + }, + { + name: "invoke_iii", + funcName: "call_i32i32_i32", + tableOffset: 4, + params: []uint64{1, 2}, + expectedResults: []uint64{3}, + expectedLog: `--> .call_i32i32_i32(4,1,2) + ==> env.invoke_iii(index=4,a1=1,a2=2) + --> .i32i32_i32(1,2) + <-- 3 + <== 3 +<-- 3 +`, + }, + { + name: "invoke_iiii", + funcName: "call_i32i32i32_i32", + tableOffset: 6, + params: []uint64{1, 2, 4}, + expectedResults: []uint64{7}, + expectedLog: `--> .call_i32i32i32_i32(6,1,2,4) + ==> env.invoke_iiii(index=6,a1=1,a2=2,a3=4) + --> .i32i32i32_i32(1,2,4) + <-- 7 + <== 7 +<-- 7 +`, + }, + { + name: "invoke_iiiii", + funcName: "calli32_i32i32i32i32_i32", + tableOffset: 8, + params: []uint64{1, 2, 4, 8}, + expectedResults: []uint64{15}, + expectedLog: `--> .calli32_i32i32i32i32_i32(8,1,2,4,8) + ==> env.invoke_iiiii(index=8,a1=1,a2=2,a3=4,a4=8) + --> .i32i32i32i32_i32(1,2,4,8) + <-- 15 + <== 15 +<-- 15 +`, + }, + { + name: "invoke_v", + funcName: "call_v_v", + tableOffset: 10, + expectedLog: `--> .call_v_v(10) + ==> env.invoke_v(index=10) + --> .v_v() + <-- + <== +<-- +`, + }, + { + name: "invoke_vi", + funcName: "call_i32_v", + tableOffset: 12, + params: []uint64{42}, + expectedLog: `--> .call_i32_v(12,42) + ==> env.invoke_vi(index=12,a1=42) + --> .i32_v(42) + <-- + <== +<-- +`, + }, + { + name: "invoke_vii", + funcName: "call_i32i32_v", + tableOffset: 14, + params: []uint64{1, 2}, + expectedLog: `--> .call_i32i32_v(14,1,2) + ==> env.invoke_vii(index=14,a1=1,a2=2) + --> .i32i32_v(1,2) + <-- + <== +<-- +`, + }, + { + name: "invoke_viii", + funcName: "call_i32i32i32_v", + tableOffset: 16, + params: []uint64{1, 2, 4}, + expectedLog: `--> .call_i32i32i32_v(16,1,2,4) + ==> env.invoke_viii(index=16,a1=1,a2=2,a3=4) + --> .i32i32i32_v(1,2,4) + <-- + <== +<-- +`, + }, + { + name: "invoke_viiii", + funcName: "calli32_i32i32i32i32_v", + tableOffset: 18, + params: []uint64{1, 2, 4, 8}, + expectedLog: `--> .calli32_i32i32i32i32_v(18,1,2,4,8) + ==> env.invoke_viiii(index=18,a1=1,a2=2,a3=4,a4=8) + --> .i32i32i32i32_v(1,2,4,8) + <-- + <== +<-- +`, + }, + } + + for _, tt := range tests { + tc := tt + + t.Run(tc.name, func(t *testing.T) { + defer log.Reset() + + params := tc.params + params = append([]uint64{uint64(tc.tableOffset)}, params...) + + results, err := mod.ExportedFunction(tc.funcName).Call(testCtx, params...) + require.NoError(t, err) + require.Equal(t, tc.expectedResults, results) + + // We expect to see the dynamic function call target + require.Equal(t, tc.expectedLog, log.String()) + + // We expect an unreachable function to err + params[0]++ + _, err = mod.ExportedFunction(tc.funcName).Call(testCtx, params...) + require.Error(t, err) + }) + } +} diff --git a/experimental/emscripten/testdata/invoke.wasm b/experimental/emscripten/testdata/invoke.wasm new file mode 100644 index 0000000000000000000000000000000000000000..64e152a9c37f768bb24869e1c2173d4001b22d82 GIT binary patch literal 1497 zcmZ`($#T;`5Ph>~n~`mKiMKemvZpvv@C7>#86Po)gQ!>FX76caARhq zamFF3vNZi(zwWlI*T(w$0|7wZ=PR;sm zlL)ri>Qrs@m(QD}0>gLOv1-+%kEO*LSlZ>kkyi+xkA6cS7L zriM^RE#sS7Mj>O1xe$a9N-1NEa~?@)evdeglO##gG~*HZN(O`E7ZKuieSg2yOZD~* z^4tLvI@sxW=$T$GU_5y=wq%3MQGZ;Aaq9GXsc~EnvNDZ%E9hhJX%XOBaxr|itHyQq zPN0IMLZYIQw%Vw;q(v81ENOp$DwT9NLM0@fOi|^M&Q4IZC0&%L3Q5n-QS~Ifyb{03 zoTGXr86abnC?L5x98OJ8l7h&@1$T}VE-q<7WgXs?w9e#Brep<^t-8eIN>-=a`*g_* z^KI)HZf4|BGtoRmo!t*hJQEBt&seJtI%g7pBEDTeY{*}J18*!vyr-0+-cH6T%sa{2 zhI)TK3kN2BAa9b;}FWOw~ zeiMYs&VdyRVI+;JJA918ir!PV_FJ{EsT)iVU|4$ejID>oUfwhq&7Lx0kMWCJFk#03 z(~h}?`s4j-%pyBLpLE?3YUaVtEYf=VQkMfCh1%1@l{%BP$n`uQ1t2WC_;2qn+k2@i zy_z24u=@Ii6{R0;%%3kP7&s!tNRT3ffJBZK+US@~>!OD~1~|eHBaAV@6vvq11amCR Tww&S&=eWQnu5b;`3CQbzn%QGi literal 0 HcmV?d00001 diff --git a/experimental/emscripten/testdata/invoke.wat b/experimental/emscripten/testdata/invoke.wat new file mode 100644 index 00000000..7cb0e90c --- /dev/null +++ b/experimental/emscripten/testdata/invoke.wat @@ -0,0 +1,115 @@ +(module + (type $0 (func (param i32))) + (import "env" "invoke_i" (func $invoke_i (param i32) (result i32))) + (import "env" "invoke_ii" (func $invoke_ii (param i32 i32) (result i32))) + (import "env" "invoke_iii" (func $invoke_iii (param i32 i32 i32) (result i32))) + (import "env" "invoke_iiii" (func $invoke_iiii (param i32 i32 i32 i32) (result i32))) + (import "env" "invoke_iiiii" (func $invoke_iiiii (param i32 i32 i32 i32 i32) (result i32))) + (import "env" "invoke_v" (func $invoke_v (param i32))) + (import "env" "invoke_vi" (func $invoke_vi (param i32 i32))) + (import "env" "invoke_vii" (func $invoke_vii (param i32 i32 i32))) + (import "env" "invoke_viii" (func $invoke_viii (param i32 i32 i32 i32))) + (import "env" "invoke_viiii" (func $invoke_viiii (param i32 i32 i32 i32 i32))) + + (table 20 20 funcref) + + (func $v_i32 (result i32) (i32.const 42)) + (func $v_i32_unreachable (result i32) unreachable) + + (elem (i32.const 0) $v_i32 $v_i32_unreachable) + + ;; call_v_i32 should be called with 0 or 1 and expect 42 or unreachable. + (func $call_v_i32 (export "call_v_i32") (param i32) (result i32) + (call $invoke_i (local.get 0))) + + (func $i32_i32 (param i32) (result i32) (local.get 0)) + (func $i32_i32_unreachable (param i32) (result i32) unreachable) + + (elem (i32.const 2) $i32_i32 $i32_i32_unreachable) + + ;; call_i32_i32 should be called with 2 or 3 followed by one number which is + ;; the result on $0 == 2 or unreachable on 3. + (func $call_i32_i32 (export "call_i32_i32") (param i32 i32) (result i32) + (call $invoke_ii (local.get 0) (local.get 1))) + + (func $i32i32_i32 (param i32 i32) (result i32) (i32.add (local.get 0) (local.get 1))) + (func $i32i32_i32_unreachable (param i32 i32) (result i32) unreachable) + + (elem (i32.const 4) $i32i32_i32 $i32i32_i32_unreachable) + + ;; call_i32i32_i32 should be called with 4 or 5 followed by two numbers + ;; whose sum is the result on $0 == 4 or unreachable on 5. + (func $call_i32i32_i32 (export "call_i32i32_i32") (param i32 i32 i32) (result i32) + (call $invoke_iii (local.get 0) (local.get 1) (local.get 2))) + + (func $i32i32i32_i32 (param i32 i32 i32) (result i32) + (i32.add (i32.add (local.get 0) (local.get 1)) (local.get 2))) + (func $i32i32i32_i32_unreachable (param i32 i32 i32) (result i32) unreachable) + + (elem (i32.const 6) $i32i32i32_i32 $i32i32i32_i32_unreachable) + + ;; call_i32i32i32_i32 should be called with 6 or 7 followed by three numbers + ;; whose sum is the result on $0 == 6 or unreachable on 7. + (func $call_i32i32i32_i32 (export "call_i32i32i32_i32") (param i32 i32 i32 i32) (result i32) + (call $invoke_iiii (local.get 0) (local.get 1) (local.get 2) (local.get 3))) + + (func $i32i32i32i32_i32 (param i32 i32 i32 i32) (result i32) + (i32.add (i32.add (i32.add (local.get 0) (local.get 1)) (local.get 2)) (local.get 3))) + (func $i32i32i32i32_i32_unreachable (param i32 i32 i32 i32) (result i32) unreachable) + + (elem (i32.const 8) $i32i32i32i32_i32 $i32i32i32i32_i32_unreachable) + + ;; calli32_i32i32i32i32_i32 should be called with 8 or 9 followed by four numbers + ;; whose sum is the result on $0 == 8 or unreachable on 9. + (func $calli32_i32i32i32i32_i32 (export "calli32_i32i32i32i32_i32") (param i32 i32 i32 i32 i32) (result i32) + (call $invoke_iiiii (local.get 0) (local.get 1) (local.get 2) (local.get 3) (local.get 4))) + + (func $v_v) + (func $v_v_unreachable unreachable) + + (elem (i32.const 10) $v_v $v_v_unreachable) + + ;; call_v_v should be called with 10 or 11 and expect unreachable on 11. + (func $call_v_v (export "call_v_v") (param i32) + (call $invoke_v (local.get 0))) + + (func $i32_v (param i32)) + (func $i32_v_unreachable (param i32) unreachable) + + (elem (i32.const 12) $i32_v $i32_v_unreachable) + + ;; call_i32_v should be called with 12 or 13 followed by one number and + ;; expect unreachable on 2. + (func $call_i32_v (export "call_i32_v") (param i32 i32) + (call $invoke_vi (local.get 0) (local.get 1))) + + (func $i32i32_v (param i32 i32)) + (func $i32i32_v_unreachable (param i32 i32) unreachable) + + (elem (i32.const 14) $i32i32_v $i32i32_v_unreachable) + + ;; call_i32i32_v should be called with 14 or 15 followed by two numbers + ;; and expect unreachable on 15. + (func $call_i32i32_v (export "call_i32i32_v") (param i32 i32 i32) + (call $invoke_vii (local.get 0) (local.get 1) (local.get 2))) + + (func $i32i32i32_v (param i32 i32 i32)) + (func $i32i32i32_v_unreachable (param i32 i32 i32) unreachable) + + (elem (i32.const 16) $i32i32i32_v $i32i32i32_v_unreachable) + + ;; call_i32i32i32_v should be called with 16 or 17 followed by three numbers + ;; and expect unreachable on 17. + (func $call_i32i32i32_v (export "call_i32i32i32_v") (param i32 i32 i32 i32) + (call $invoke_viii (local.get 0) (local.get 1) (local.get 2) (local.get 3))) + + (func $i32i32i32i32_v (param i32 i32 i32 i32)) + (func $i32i32i32i32_v_unreachable (param i32 i32 i32 i32) unreachable) + + (elem (i32.const 18) $i32i32i32i32_v $i32i32i32i32_v_unreachable) + + ;; calli32_i32i32i32i32_v should be called with 18 or 19 followed by four + ;; numbers and expect unreachable on 19. + (func $calli32_i32i32i32i32_v (export "calli32_i32i32i32i32_v") (param i32 i32 i32 i32 i32) + (call $invoke_viiii (local.get 0) (local.get 1) (local.get 2) (local.get 3) (local.get 4))) +) diff --git a/imports/emscripten/emscripten.go b/imports/emscripten/emscripten.go index 2f904ff8..05cc05af 100644 --- a/imports/emscripten/emscripten.go +++ b/imports/emscripten/emscripten.go @@ -17,9 +17,12 @@ import ( "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" + internal "github.com/tetratelabs/wazero/internal/emscripten" "github.com/tetratelabs/wazero/internal/wasm" ) +const i32 = wasm.ValueTypeI32 + // MustInstantiate calls Instantiate or panics on error. // // This is a simpler function for those who know the module "env" is not @@ -62,257 +65,16 @@ type functionExporter struct{} // ExportFunctions implements FunctionExporter.ExportFunctions func (functionExporter) ExportFunctions(builder wazero.HostModuleBuilder) { exporter := builder.(wasm.HostFuncExporter) - exporter.ExportHostFunc(notifyMemoryGrowth) - exporter.ExportHostFunc(invokeI) - exporter.ExportHostFunc(invokeIi) - exporter.ExportHostFunc(invokeIii) - exporter.ExportHostFunc(invokeIiii) - exporter.ExportHostFunc(invokeIiiii) - exporter.ExportHostFunc(invokeV) - exporter.ExportHostFunc(invokeVi) - exporter.ExportHostFunc(invokeVii) - exporter.ExportHostFunc(invokeViii) - exporter.ExportHostFunc(invokeViiii) -} - -// emscriptenNotifyMemoryGrowth is called when wasm is compiled with -// `-s ALLOW_MEMORY_GROWTH` and a "memory.grow" instruction succeeded. -// The memoryIndex parameter will be zero until "multi-memory" is implemented. -// -// Note: This implementation is a no-op and can be overridden by users manually -// by redefining the same function. wazero will likely implement a generic -// memory growth hook obviating this as well. -// -// Here's the import in a user's module that ends up using this, in WebAssembly -// 1.0 (MVP) Text Format: -// -// (import "env" "emscripten_notify_memory_growth" -// (func $emscripten_notify_memory_growth (param $memory_index i32))) -// -// See https://github.com/emscripten-core/emscripten/blob/3.1.16/system/lib/standalone/standalone.c#L118 -// and https://emscripten.org/docs/api_reference/emscripten.h.html#abi-functions -const functionNotifyMemoryGrowth = "emscripten_notify_memory_growth" - -var notifyMemoryGrowth = &wasm.HostFunc{ - ExportName: functionNotifyMemoryGrowth, - Name: functionNotifyMemoryGrowth, - ParamTypes: []wasm.ValueType{wasm.ValueTypeI32}, - ParamNames: []string{"memory_index"}, - Code: wasm.Code{GoFunc: api.GoModuleFunc(func(context.Context, api.Module, []uint64) {})}, -} - -// All `invoke_` functions have an initial "index" parameter of -// api.ValueTypeI32. This is the index of the desired funcref in the only table -// in the module. The type of the funcref is via naming convention. The first -// character after `invoke_` decides the result type: 'v' for no result or 'i' -// for api.ValueTypeI32. Any count of 'i' following that are api.ValueTypeI32 -// parameters. -// -// For example, the function `invoke_iiiii` signature has five parameters, but -// also five i's. The five 'i's mean there are four parameters -// -// (import "env" "invoke_iiiii" (func $invoke_iiiii -// (param i32 i32 i32 i32 i32) (result i32)))) -// -// So, the above function if invoked `invoke_iiiii(1234, 1, 2, 3, 4)` would -// look up the funcref at table index 1234, which has a type i32i32i3232_i32 -// and invoke it with the remaining parameters, -const ( - i32 = wasm.ValueTypeI32 - - functionInvokeI = "invoke_i" - functionInvokeIi = "invoke_ii" - functionInvokeIii = "invoke_iii" - functionInvokeIiii = "invoke_iiii" - functionInvokeIiiii = "invoke_iiiii" - - functionInvokeV = "invoke_v" - functionInvokeVi = "invoke_vi" - functionInvokeVii = "invoke_vii" - functionInvokeViii = "invoke_viii" - functionInvokeViiii = "invoke_viiii" -) - -var invokeI = &wasm.HostFunc{ - ExportName: functionInvokeI, - Name: functionInvokeI, - ParamTypes: []api.ValueType{i32}, - ParamNames: []string{"index"}, - ResultTypes: []api.ValueType{i32}, - Code: wasm.Code{GoFunc: api.GoModuleFunc(invokeIFn)}, -} - -func invokeIFn(ctx context.Context, mod api.Module, stack []uint64) { - ret, err := callDynamic(ctx, mod.(*wasm.ModuleInstance), wasm.PreAllocatedTypeID_v_i32, wasm.Index(stack[0]), nil) - if err != nil { - panic(err) - } - stack[0] = ret[0] -} - -var invokeIi = &wasm.HostFunc{ - ExportName: functionInvokeIi, - Name: functionInvokeIi, - ParamTypes: []api.ValueType{i32, i32}, - ParamNames: []string{"index", "a1"}, - ResultTypes: []api.ValueType{i32}, - Code: wasm.Code{GoFunc: api.GoModuleFunc(invokeIiFn)}, -} - -func invokeIiFn(ctx context.Context, mod api.Module, stack []uint64) { - ret, err := callDynamic(ctx, mod.(*wasm.ModuleInstance), wasm.PreAllocatedTypeID_i32_i32, wasm.Index(stack[0]), stack[1:]) - if err != nil { - panic(err) - } - stack[0] = ret[0] -} - -var invokeIii = &wasm.HostFunc{ - ExportName: functionInvokeIii, - Name: functionInvokeIii, - ParamTypes: []api.ValueType{i32, i32, i32}, - ParamNames: []string{"index", "a1", "a2"}, - ResultTypes: []api.ValueType{i32}, - Code: wasm.Code{GoFunc: api.GoModuleFunc(invokeIiiFn)}, -} - -func invokeIiiFn(ctx context.Context, mod api.Module, stack []uint64) { - ret, err := callDynamic(ctx, mod.(*wasm.ModuleInstance), wasm.PreAllocatedTypeID_i32i32_i32, wasm.Index(stack[0]), stack[1:]) - if err != nil { - panic(err) - } - stack[0] = ret[0] -} - -var invokeIiii = &wasm.HostFunc{ - ExportName: functionInvokeIiii, - Name: functionInvokeIiii, - ParamTypes: []api.ValueType{i32, i32, i32, i32}, - ParamNames: []string{"index", "a1", "a2", "a3"}, - ResultTypes: []api.ValueType{i32}, - Code: wasm.Code{GoFunc: api.GoModuleFunc(invokeIiiiFn)}, -} - -func invokeIiiiFn(ctx context.Context, mod api.Module, stack []uint64) { - ret, err := callDynamic(ctx, mod.(*wasm.ModuleInstance), wasm.PreAllocatedTypeID_i32i32i32_i32, wasm.Index(stack[0]), stack[1:]) - if err != nil { - panic(err) - } - stack[0] = ret[0] -} - -var invokeIiiii = &wasm.HostFunc{ - ExportName: functionInvokeIiiii, - Name: functionInvokeIiiii, - ParamTypes: []api.ValueType{i32, i32, i32, i32, i32}, - ParamNames: []string{"index", "a1", "a2", "a3", "a4"}, - ResultTypes: []api.ValueType{i32}, - Code: wasm.Code{GoFunc: api.GoModuleFunc(invokeIiiiiFn)}, -} - -func invokeIiiiiFn(ctx context.Context, mod api.Module, stack []uint64) { - ret, err := callDynamic(ctx, mod.(*wasm.ModuleInstance), wasm.PreAllocatedTypeID_i32i32i32i32_i32, wasm.Index(stack[0]), stack[1:]) - if err != nil { - panic(err) - } - stack[0] = ret[0] -} - -var invokeV = &wasm.HostFunc{ - ExportName: functionInvokeV, - Name: functionInvokeV, - ParamTypes: []api.ValueType{i32}, - ParamNames: []string{"index"}, - ResultTypes: []api.ValueType{}, - Code: wasm.Code{GoFunc: api.GoModuleFunc(invokeVFn)}, -} - -func invokeVFn(ctx context.Context, mod api.Module, stack []uint64) { - _, err := callDynamic(ctx, mod.(*wasm.ModuleInstance), wasm.PreAllocatedTypeID_v_v, wasm.Index(stack[0]), nil) - if err != nil { - panic(err) - } -} - -var invokeVi = &wasm.HostFunc{ - ExportName: functionInvokeVi, - Name: functionInvokeVi, - ParamTypes: []api.ValueType{i32, i32}, - ParamNames: []string{"index", "a1"}, - ResultTypes: []api.ValueType{}, - Code: wasm.Code{GoFunc: api.GoModuleFunc(invokeViFn)}, -} - -func invokeViFn(ctx context.Context, mod api.Module, stack []uint64) { - _, err := callDynamic(ctx, mod.(*wasm.ModuleInstance), wasm.PreAllocatedTypeID_i32_v, wasm.Index(stack[0]), stack[1:]) - if err != nil { - panic(err) - } -} - -var invokeVii = &wasm.HostFunc{ - ExportName: functionInvokeVii, - Name: functionInvokeVii, - ParamTypes: []api.ValueType{i32, i32, i32}, - ParamNames: []string{"index", "a1", "a2"}, - ResultTypes: []api.ValueType{}, - Code: wasm.Code{GoFunc: api.GoModuleFunc(invokeViiFn)}, -} - -func invokeViiFn(ctx context.Context, mod api.Module, stack []uint64) { - _, err := callDynamic(ctx, mod.(*wasm.ModuleInstance), wasm.PreAllocatedTypeID_i32i32_v, wasm.Index(stack[0]), stack[1:]) - if err != nil { - panic(err) - } -} - -var invokeViii = &wasm.HostFunc{ - ExportName: functionInvokeViii, - Name: functionInvokeViii, - ParamTypes: []api.ValueType{i32, i32, i32, i32}, - ParamNames: []string{"index", "a1", "a2", "a3"}, - ResultTypes: []api.ValueType{}, - Code: wasm.Code{GoFunc: api.GoModuleFunc(invokeViiiFn)}, -} - -func invokeViiiFn(ctx context.Context, mod api.Module, stack []uint64) { - _, err := callDynamic(ctx, mod.(*wasm.ModuleInstance), wasm.PreAllocatedTypeID_i32i32i32_v, wasm.Index(stack[0]), stack[1:]) - if err != nil { - panic(err) - } -} - -var invokeViiii = &wasm.HostFunc{ - ExportName: functionInvokeViiii, - Name: functionInvokeViiii, - ParamTypes: []api.ValueType{i32, i32, i32, i32, i32}, - ParamNames: []string{"index", "a1", "a2", "a3", "a4"}, - ResultTypes: []api.ValueType{}, - Code: wasm.Code{GoFunc: api.GoModuleFunc(invokeViiiiFn)}, -} - -func invokeViiiiFn(ctx context.Context, mod api.Module, stack []uint64) { - _, err := callDynamic(ctx, mod.(*wasm.ModuleInstance), wasm.PreAllocatedTypeID_i32i32i32i32_v, wasm.Index(stack[0]), stack[1:]) - if err != nil { - panic(err) - } -} - -// callDynamic special cases dynamic calls needed for emscripten `invoke_` -// functions such as `invoke_ii` or `invoke_v`. -// -// # Parameters -// -// - ctx: the propagated go context. -// - m: the incoming module instance of the `invoke_` function. -// - typeID: used to type check on indirect calls. -// - tableOffset: position in the module's only table -// - params: parameters to the funcref -func callDynamic(ctx context.Context, m *wasm.ModuleInstance, typeID wasm.FunctionTypeID, tableOffset wasm.Index, params []uint64) (results []uint64, err error) { - t := m.Tables[0] // Emscripten doesn't use multiple tables - idx, err := m.Engine.LookupFunction(t, typeID, tableOffset) - if err != nil { - return nil, err - } - return m.Engine.NewFunction(idx).Call(ctx, params...) + exporter.ExportHostFunc(internal.NotifyMemoryGrowth) + + exporter.ExportHostFunc(internal.NewInvokeFunc("invoke_i", []api.ValueType{i32}, []api.ValueType{i32})) + exporter.ExportHostFunc(internal.NewInvokeFunc("invoke_ii", []api.ValueType{i32, i32}, []api.ValueType{i32})) + exporter.ExportHostFunc(internal.NewInvokeFunc("invoke_iii", []api.ValueType{i32, i32, i32}, []api.ValueType{i32})) + exporter.ExportHostFunc(internal.NewInvokeFunc("invoke_iiii", []api.ValueType{i32, i32, i32, i32}, []api.ValueType{i32})) + exporter.ExportHostFunc(internal.NewInvokeFunc("invoke_iiiii", []api.ValueType{i32, i32, i32, i32, i32}, []api.ValueType{i32})) + exporter.ExportHostFunc(internal.NewInvokeFunc("invoke_v", []api.ValueType{i32}, nil)) + exporter.ExportHostFunc(internal.NewInvokeFunc("invoke_vi", []api.ValueType{i32, i32}, nil)) + exporter.ExportHostFunc(internal.NewInvokeFunc("invoke_vii", []api.ValueType{i32, i32, i32}, nil)) + exporter.ExportHostFunc(internal.NewInvokeFunc("invoke_viii", []api.ValueType{i32, i32, i32, i32}, nil)) + exporter.ExportHostFunc(internal.NewInvokeFunc("invoke_viiii", []api.ValueType{i32, i32, i32, i32, i32}, nil)) } diff --git a/internal/emscripten/emscripten.go b/internal/emscripten/emscripten.go new file mode 100644 index 00000000..b014d09c --- /dev/null +++ b/internal/emscripten/emscripten.go @@ -0,0 +1,95 @@ +package emscripten + +import ( + "context" + "strconv" + + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/internal/wasm" +) + +const FunctionNotifyMemoryGrowth = "emscripten_notify_memory_growth" + +var NotifyMemoryGrowth = &wasm.HostFunc{ + ExportName: FunctionNotifyMemoryGrowth, + Name: FunctionNotifyMemoryGrowth, + ParamTypes: []wasm.ValueType{wasm.ValueTypeI32}, + ParamNames: []string{"memory_index"}, + Code: wasm.Code{GoFunc: api.GoModuleFunc(func(context.Context, api.Module, []uint64) {})}, +} + +// InvokePrefix is the naming convention of Emscripten dynamic functions. +// +// All `invoke_` functions have an initial "index" parameter of +// api.ValueTypeI32. This is the index of the desired funcref in the only table +// in the module. The type of the funcref is via naming convention. The first +// character after `invoke_` decides the result type: 'v' for no result or 'i' +// for api.ValueTypeI32. Any count of 'i' following that are api.ValueTypeI32 +// parameters. +// +// For example, the function `invoke_iiiii` signature has five parameters, but +// also five i's. The five 'i's mean there are four parameters +// +// (import "env" "invoke_iiiii" (func $invoke_iiiii +// (param i32 i32 i32 i32 i32) (result i32)))) +// +// So, the above function if invoked `invoke_iiiii(1234, 1, 2, 3, 4)` would +// look up the funcref at table index 1234, which has a type i32i32i3232_i32 +// and invoke it with the remaining parameters. +const InvokePrefix = "invoke_" + +func NewInvokeFunc(importName string, params, results []api.ValueType) *wasm.HostFunc { + // The type we invoke is the same type as the import except without the + // index parameter. + fn := &InvokeFunc{&wasm.FunctionType{Results: results}} + if len(params) > 1 { + fn.FunctionType.Params = params[1:] + } + + // Now, make friendly parameter names. + paramNames := make([]string, len(params)) + paramNames[0] = "index" + for i := 1; i < len(paramNames); i++ { + paramNames[i] = "a" + strconv.Itoa(i) + } + return &wasm.HostFunc{ + ExportName: importName, + ParamTypes: params, + ParamNames: paramNames, + ResultTypes: results, + Code: wasm.Code{GoFunc: fn}, + } +} + +type InvokeFunc struct { + *wasm.FunctionType +} + +// Call implements api.GoModuleFunction by special casing dynamic calls needed +// for emscripten `invoke_` functions such as `invoke_ii` or `invoke_v`. +func (v *InvokeFunc) Call(ctx context.Context, mod api.Module, stack []uint64) { + m := mod.(*wasm.ModuleInstance) + + // Lookup the type of the function we are calling indirectly. + typeID, err := m.GetFunctionTypeID(v.FunctionType) + if err != nil { + panic(err) + } + + tableOffset := wasm.Index(stack[0]) // position in the module's only table. + params := stack[1:] // parameters to the dynamic function being called + + // Lookup the table index we will call. + t := m.Tables[0] // Note: Emscripten doesn't use multiple tables + idx, err := m.Engine.LookupFunction(t, typeID, tableOffset) + if err != nil { + panic(err) + } + + ret, err := m.Engine.NewFunction(idx).Call(ctx, params...) + if err != nil { + panic(err) + } + // if there are any results, copy them back to the stack + copy(stack, ret) +} diff --git a/internal/wasm/store.go b/internal/wasm/store.go index bc6bc627..9aac46fa 100644 --- a/internal/wasm/store.go +++ b/internal/wasm/store.go @@ -153,6 +153,11 @@ type ( // The wazero specific limitations described at RATIONALE.md. const maximumFunctionTypes = 1 << 27 +// GetFunctionTypeID is used by emscripten. +func (m *ModuleInstance) GetFunctionTypeID(t *FunctionType) (FunctionTypeID, error) { + return m.s.GetFunctionTypeID(t) +} + func (m *ModuleInstance) buildElementInstances(elements []ElementSegment) { m.ElementInstances = make([]ElementInstance, len(elements)) for i, elm := range elements { @@ -267,16 +272,12 @@ func (m *ModuleInstance) getExport(name string, et ExternType) (*Export, error) } func NewStore(enabledFeatures api.CoreFeatures, engine Engine) *Store { - typeIDs := make(map[string]FunctionTypeID, len(preAllocatedTypeIDs)) - for k, v := range preAllocatedTypeIDs { - typeIDs[k] = v - } return &Store{ nameToModule: map[string]*ModuleInstance{}, nameToModuleCap: nameToModuleShrinkThreshold, EnabledFeatures: enabledFeatures, Engine: engine, - typeIDs: typeIDs, + typeIDs: map[string]FunctionTypeID{}, functionMaxTypes: maximumFunctionTypes, } } @@ -544,7 +545,7 @@ func (s *Store) GetFunctionTypeIDs(ts []FunctionType) ([]FunctionTypeID, error) ret := make([]FunctionTypeID, len(ts)) for i := range ts { t := &ts[i] - inst, err := s.getFunctionTypeID(t) + inst, err := s.GetFunctionTypeID(t) if err != nil { return nil, err } @@ -553,46 +554,7 @@ func (s *Store) GetFunctionTypeIDs(ts []FunctionType) ([]FunctionTypeID, error) return ret, nil } -// preAllocatedTypeIDs maps several "well-known" FunctionType strings to the pre allocated FunctionID. -// This is used by emscripten integration, but it is harmless to have this all the time as it's only -// used during Store creation. -var preAllocatedTypeIDs = map[string]FunctionTypeID{ - "i32i32i32i32_v": PreAllocatedTypeID_i32i32i32i32_v, - "i32i32i32_v": PreAllocatedTypeID_i32i32i32_v, - "i32i32_v": PreAllocatedTypeID_i32i32_v, - "i32_v": PreAllocatedTypeID_i32_v, - "v_v": PreAllocatedTypeID_v_v, - "i32i32i32i32_i32": PreAllocatedTypeID_i32i32i32i32_i32, - "i32i32i32_i32": PreAllocatedTypeID_i32i32i32_i32, - "i32i32_i32": PreAllocatedTypeID_i32i32_i32, - "i32_i32": PreAllocatedTypeID_i32_i32, - "v_i32": PreAllocatedTypeID_v_i32, -} - -const ( - // PreAllocatedTypeID_i32i32i32i32_v is FunctionTypeID for i32i32i32i32_v. - PreAllocatedTypeID_i32i32i32i32_v FunctionTypeID = iota - // PreAllocatedTypeID_i32i32i32_v is FunctionTypeID for i32i32i32_v - PreAllocatedTypeID_i32i32i32_v - // PreAllocatedTypeID_i32i32_v is FunctionTypeID for i32i32_v - PreAllocatedTypeID_i32i32_v - // PreAllocatedTypeID_i32_v is FunctionTypeID for i32_v - PreAllocatedTypeID_i32_v - // PreAllocatedTypeID_v_v is FunctionTypeID for v_v - PreAllocatedTypeID_v_v - // PreAllocatedTypeID_i32i32i32i32_i32 is FunctionTypeID for i32i32i32i32_i32 - PreAllocatedTypeID_i32i32i32i32_i32 - // PreAllocatedTypeID_i32i32i32_i32 is FunctionTypeID for i32i32i32_i32 - PreAllocatedTypeID_i32i32i32_i32 - // PreAllocatedTypeID_i32i32_i32 is FunctionTypeID for i32i32_i32 - PreAllocatedTypeID_i32i32_i32 - // PreAllocatedTypeID_i32_i32 is FunctionTypeID for i32_i32 - PreAllocatedTypeID_i32_i32 - // PreAllocatedTypeID_v_i32 is FunctionTypeID for v_i32 - PreAllocatedTypeID_v_i32 -) - -func (s *Store) getFunctionTypeID(t *FunctionType) (FunctionTypeID, error) { +func (s *Store) GetFunctionTypeID(t *FunctionType) (FunctionTypeID, error) { s.mux.RLock() key := t.key() id, ok := s.typeIDs[key] diff --git a/internal/wasm/store_test.go b/internal/wasm/store_test.go index 2bb1af1c..0c9fe047 100644 --- a/internal/wasm/store_test.go +++ b/internal/wasm/store_test.go @@ -93,16 +93,6 @@ func TestModuleInstance_Memory(t *testing.T) { } } -func TestNewStore(t *testing.T) { - s := NewStore(api.CoreFeaturesV1, &mockEngine{shouldCompileFail: false, callFailIndex: -1}) - // Ensures that a newly created store has the pre allocated type IDs. - for k, v := range preAllocatedTypeIDs { - actual, ok := s.typeIDs[k] - require.True(t, ok) - require.Equal(t, v, actual) - } -} - func TestStore_Instantiate(t *testing.T) { s := newStore() m, err := NewHostModule( @@ -513,7 +503,7 @@ func TestStore_getFunctionTypeID(t *testing.T) { for i := 0; i < max; i++ { s.typeIDs[strconv.Itoa(i)] = 0 } - _, err := s.getFunctionTypeID(&FunctionType{}) + _, err := s.GetFunctionTypeID(&FunctionType{}) require.Error(t, err) }) t.Run("ok", func(t *testing.T) { @@ -528,7 +518,7 @@ func TestStore_getFunctionTypeID(t *testing.T) { tc := tt t.Run(tc.String(), func(t *testing.T) { s := newStore() - actual, err := s.getFunctionTypeID(&tc) + actual, err := s.GetFunctionTypeID(&tc) require.NoError(t, err) expectedTypeID, ok := s.typeIDs[tc.String()] @@ -986,13 +976,3 @@ func TestModuleInstance_applyElementsapplyElements(t *testing.T) { m.Tables[0].References) }) } - -// TestPreAllocatedTypeIDs ensures that PreAllocatedTypeIDs has no duplication on the values (FunctionTypeID). -func TestPreAllocatedTypeIDs(t *testing.T) { - exists := make(map[FunctionTypeID]struct{}, len(preAllocatedTypeIDs)) - for _, v := range preAllocatedTypeIDs { - _, ok := exists[v] - require.False(t, ok) - exists[v] = struct{}{} - } -} diff --git a/runtime.go b/runtime.go index 34bc51f7..475938f0 100644 --- a/runtime.go +++ b/runtime.go @@ -23,6 +23,12 @@ import ( // defer r.Close(ctx) // This closes everything this Runtime created. // // mod, _ := r.Instantiate(ctx, wasm) +// +// # Notes +// +// - Closing this closes any CompiledModule or Module it instantiated. +// - This is an interface for decoupling, not third-party implementations. +// All implementations are in wazero. type Runtime interface { // Instantiate instantiates a module from the WebAssembly binary (%.wasm) // with default configuration.