github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/integration_test/bench/hostfunc_bench_test.go (about) 1 package bench 2 3 import ( 4 "context" 5 _ "embed" 6 "encoding/binary" 7 "math" 8 "testing" 9 10 "github.com/tetratelabs/wazero" 11 "github.com/tetratelabs/wazero/api" 12 "github.com/tetratelabs/wazero/internal/platform" 13 "github.com/tetratelabs/wazero/internal/testing/binaryencoding" 14 "github.com/tetratelabs/wazero/internal/testing/require" 15 "github.com/tetratelabs/wazero/internal/wasm" 16 ) 17 18 const ( 19 // callGoHostName is the name of exported function which calls the 20 // Go-implemented host function. 21 callGoHostName = "call_go_host" 22 // callGoReflectHostName is the name of exported function which calls the 23 // Go-implemented host function defined in reflection. 24 callGoReflectHostName = "call_go_reflect_host" 25 ) 26 27 // BenchmarkHostFunctionCall measures the cost of host function calls whose target functions are either 28 // Go-implemented or Wasm-implemented, and compare the results between them. 29 func BenchmarkHostFunctionCall(b *testing.B) { 30 if !platform.CompilerSupported() { 31 b.Skip() 32 } 33 34 m := setupHostCallBench(func(err error) { 35 if err != nil { 36 b.Fatal(err) 37 } 38 }) 39 40 const offset = uint64(100) 41 const val = float32(1.1234) 42 43 binary.LittleEndian.PutUint32(m.MemoryInstance.Buffer[offset:], math.Float32bits(val)) 44 45 for _, fn := range []string{callGoReflectHostName, callGoHostName} { 46 fn := fn 47 48 b.Run(fn, func(b *testing.B) { 49 ce := getCallEngine(m, fn) 50 51 b.ResetTimer() 52 for i := 0; i < b.N; i++ { 53 res, err := ce.Call(testCtx, offset) 54 if err != nil { 55 b.Fatal(err) 56 } 57 if uint32(res[0]) != math.Float32bits(val) { 58 b.Fail() 59 } 60 } 61 }) 62 63 b.Run(fn+"_with_stack", func(b *testing.B) { 64 ce := getCallEngine(m, fn) 65 66 b.ResetTimer() 67 stack := make([]uint64, 1) 68 for i := 0; i < b.N; i++ { 69 stack[0] = offset 70 err := ce.CallWithStack(testCtx, stack) 71 if err != nil { 72 b.Fatal(err) 73 } 74 if uint32(stack[0]) != math.Float32bits(val) { 75 b.Fail() 76 } 77 } 78 }) 79 } 80 } 81 82 func TestBenchmarkFunctionCall(t *testing.T) { 83 if !platform.CompilerSupported() { 84 t.Skip() 85 } 86 87 m := setupHostCallBench(func(err error) { 88 require.NoError(t, err) 89 }) 90 91 callGoHost := getCallEngine(m, callGoHostName) 92 callGoReflectHost := getCallEngine(m, callGoReflectHostName) 93 94 require.NotNil(t, callGoHost) 95 require.NotNil(t, callGoReflectHost) 96 97 tests := []struct { 98 offset uint32 99 val float32 100 }{ 101 {offset: 0, val: math.Float32frombits(0xffffffff)}, 102 {offset: 100, val: 1.12314}, 103 {offset: wasm.MemoryPageSize - 4, val: 1.12314}, 104 } 105 106 mem := m.MemoryInstance.Buffer 107 108 for _, f := range []struct { 109 name string 110 ce api.Function 111 }{ 112 {name: "go", ce: callGoHost}, 113 {name: "go-reflect", ce: callGoReflectHost}, 114 } { 115 f := f 116 t.Run(f.name, func(t *testing.T) { 117 for _, tc := range tests { 118 binary.LittleEndian.PutUint32(mem[tc.offset:], math.Float32bits(tc.val)) 119 res, err := f.ce.Call(context.Background(), uint64(tc.offset)) 120 require.NoError(t, err) 121 require.Equal(t, math.Float32bits(tc.val), uint32(res[0])) 122 } 123 }) 124 } 125 } 126 127 func getCallEngine(m *wasm.ModuleInstance, name string) (ce api.Function) { 128 exp := m.Exports[name] 129 ce = m.Engine.NewFunction(exp.Index) 130 return 131 } 132 133 func setupHostCallBench(requireNoError func(error)) *wasm.ModuleInstance { 134 ctx := context.Background() 135 r := wazero.NewRuntime(ctx) 136 137 const i32, f32 = api.ValueTypeI32, api.ValueTypeF32 138 _, err := r.NewHostModuleBuilder("host"). 139 NewFunctionBuilder().WithGoModuleFunction(api.GoModuleFunc(func(ctx context.Context, mod api.Module, stack []uint64) { 140 ret, ok := mod.Memory().ReadUint32Le(uint32(stack[0])) 141 if !ok { 142 panic("couldn't read memory") 143 } 144 stack[0] = uint64(ret) 145 }), []api.ValueType{i32}, []api.ValueType{f32}).Export("go"). 146 NewFunctionBuilder().WithFunc(func(ctx context.Context, m api.Module, pos uint32) float32 { 147 ret, ok := m.Memory().ReadUint32Le(pos) 148 if !ok { 149 panic("couldn't read memory") 150 } 151 return math.Float32frombits(ret) 152 }).Export("go-reflect").Instantiate(ctx) 153 requireNoError(err) 154 155 // Build the importing module. 156 importingModuleBin := binaryencoding.EncodeModule(&wasm.Module{ 157 TypeSection: []wasm.FunctionType{{ 158 Params: []wasm.ValueType{i32}, 159 Results: []wasm.ValueType{f32}, 160 }}, 161 ImportSection: []wasm.Import{ 162 // Placeholders for imports from hostModule. 163 {Type: wasm.ExternTypeFunc, Module: "host", Name: "go"}, 164 {Type: wasm.ExternTypeFunc, Module: "host", Name: "go-reflect"}, 165 }, 166 FunctionSection: []wasm.Index{0, 0}, 167 ExportSection: []wasm.Export{ 168 {Name: callGoHostName, Type: wasm.ExternTypeFunc, Index: 2}, 169 {Name: callGoReflectHostName, Type: wasm.ExternTypeFunc, Index: 3}, 170 }, 171 Exports: map[string]*wasm.Export{ 172 callGoHostName: {Name: callGoHostName, Type: wasm.ExternTypeFunc, Index: 2}, 173 callGoReflectHostName: {Name: callGoReflectHostName, Type: wasm.ExternTypeFunc, Index: 3}, 174 }, 175 CodeSection: []wasm.Code{ 176 {Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, // Calling the index 0 = host.go. 177 {Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, 1, wasm.OpcodeEnd}}, // Calling the index 1 = host.go-reflect. 178 }, 179 MemorySection: &wasm.Memory{Min: 1}, 180 }) 181 182 importing, err := r.Instantiate(ctx, importingModuleBin) 183 requireNoError(err) 184 return importing.(*wasm.ModuleInstance) 185 }