diff --git a/experimental/emscripten/emscripten.go b/experimental/emscripten/emscripten.go deleted file mode 100644 index f71b2db236..0000000000 --- a/experimental/emscripten/emscripten.go +++ /dev/null @@ -1,60 +0,0 @@ -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 deleted file mode 100644 index 9f88db3fc8..0000000000 --- a/experimental/emscripten/emscripten_test.go +++ /dev/null @@ -1,494 +0,0 @@ -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 deleted file mode 100644 index 64e152a9c3..0000000000 Binary files a/experimental/emscripten/testdata/invoke.wasm and /dev/null differ diff --git a/experimental/emscripten/testdata/invoke.wat b/experimental/emscripten/testdata/invoke.wat deleted file mode 100644 index 7cb0e90cf8..0000000000 --- a/experimental/emscripten/testdata/invoke.wat +++ /dev/null @@ -1,115 +0,0 @@ -(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 d29ee19873..44f5dd92bc 100644 --- a/imports/emscripten/emscripten.go +++ b/imports/emscripten/emscripten.go @@ -14,6 +14,7 @@ package emscripten import ( "context" + "strings" "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" @@ -71,15 +72,52 @@ type functionExporter struct{} func (functionExporter) ExportFunctions(builder wazero.HostModuleBuilder) { exporter := builder.(wasm.HostFuncExporter) exporter.ExportHostFunc(internal.NotifyMemoryGrowth) +} + +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) (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 + } - 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)) + 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/imports/emscripten/emscripten_test.go b/imports/emscripten/emscripten_test.go index cf62e9d0ce..c23fbc45ff 100644 --- a/imports/emscripten/emscripten_test.go +++ b/imports/emscripten/emscripten_test.go @@ -7,10 +7,20 @@ import ( "testing" "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/experimental" "github.com/tetratelabs/wazero/experimental/logging" "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" + 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 ( + i64 = wasm.ValueTypeI64 + f32 = wasm.ValueTypeF32 + f64 = wasm.ValueTypeF64 ) // growWasm was compiled from testdata/grow.cc @@ -52,7 +62,285 @@ func TestGrow(t *testing.T) { require.Contains(t, log.String(), "==> env.emscripten_notify_memory_growth(memory_index=0)") } -func TestInvoke(t *testing.T) { +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) + } + }) + } +} + +func TestInstantiateForModule(t *testing.T) { var log bytes.Buffer // Set context to one that has an experimental listener @@ -61,10 +349,13 @@ func TestInvoke(t *testing.T) { r := wazero.NewRuntime(ctx) defer r.Close(ctx) - _, err := Instantiate(ctx, r) + compiled, err := r.CompileModule(ctx, invokeWasm) + require.NoError(t, err) + + _, err = InstantiateForModule(ctx, r, compiled) require.NoError(t, err) - mod, err := r.Instantiate(ctx, invokeWasm) + mod, err := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig()) require.NoError(t, err) tests := []struct { @@ -221,7 +512,7 @@ func TestInvoke(t *testing.T) { require.Equal(t, tc.expectedResults, results) // We expect to see the dynamic function call target - require.Equal(t, log.String(), tc.expectedLog) + require.Equal(t, tc.expectedLog, log.String()) // We expect an unreachable function to err params[0]++