wa-lang.org/wazero@v1.0.2/internal/integration_test/bench/hostfunc_bench_test.go (about) 1 package bench 2 3 import ( 4 "context" 5 _ "embed" 6 "encoding/binary" 7 "fmt" 8 "math" 9 "testing" 10 11 "wa-lang.org/wazero/api" 12 "wa-lang.org/wazero/internal/engine/compiler" 13 "wa-lang.org/wazero/internal/platform" 14 "wa-lang.org/wazero/internal/testing/require" 15 "wa-lang.org/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 // callWasmHostName is the name of exported function which calls the 26 // Wasm-implemented host function. 27 callWasmHostName = "call_wasm_host" 28 ) 29 30 // BenchmarkHostFunctionCall measures the cost of host function calls whose target functions are either 31 // Go-implemented or Wasm-implemented, and compare the results between them. 32 func BenchmarkHostFunctionCall(b *testing.B) { 33 if !platform.CompilerSupported() { 34 b.Skip() 35 } 36 37 m := setupHostCallBench(func(err error) { 38 if err != nil { 39 b.Fatal(err) 40 } 41 }) 42 43 const offset = uint64(100) 44 const val = float32(1.1234) 45 46 binary.LittleEndian.PutUint32(m.Memory.Buffer[offset:], math.Float32bits(val)) 47 48 for _, fn := range []string{callGoReflectHostName, callGoHostName, callWasmHostName} { 49 fn := fn 50 51 b.Run(fn, func(b *testing.B) { 52 ce, err := getCallEngine(m, fn) 53 if err != nil { 54 b.Fatal(err) 55 } 56 57 b.ResetTimer() 58 for i := 0; i < b.N; i++ { 59 res, err := ce.Call(testCtx, m.CallCtx, []uint64{offset}) 60 if err != nil { 61 b.Fatal(err) 62 } 63 if uint32(res[0]) != math.Float32bits(val) { 64 b.Fail() 65 } 66 } 67 }) 68 } 69 } 70 71 func TestBenchmarkFunctionCall(t *testing.T) { 72 if !platform.CompilerSupported() { 73 t.Skip() 74 } 75 76 m := setupHostCallBench(func(err error) { 77 require.NoError(t, err) 78 }) 79 80 callWasmHost, err := getCallEngine(m, callWasmHostName) 81 require.NoError(t, err) 82 83 callGoHost, err := getCallEngine(m, callGoHostName) 84 require.NoError(t, err) 85 86 callGoReflectHost, err := getCallEngine(m, callGoReflectHostName) 87 require.NoError(t, err) 88 89 require.NotNil(t, callWasmHost) 90 require.NotNil(t, callGoHost) 91 require.NotNil(t, callGoReflectHost) 92 93 tests := []struct { 94 offset uint32 95 val float32 96 }{ 97 {offset: 0, val: math.Float32frombits(0xffffffff)}, 98 {offset: 100, val: 1.12314}, 99 {offset: wasm.MemoryPageSize - 4, val: 1.12314}, 100 } 101 102 mem := m.Memory.Buffer 103 104 for _, f := range []struct { 105 name string 106 ce wasm.CallEngine 107 }{ 108 {name: "go", ce: callGoHost}, 109 {name: "go-reflect", ce: callGoReflectHost}, 110 {name: "wasm", ce: callWasmHost}, 111 } { 112 f := f 113 t.Run(f.name, func(t *testing.T) { 114 for _, tc := range tests { 115 binary.LittleEndian.PutUint32(mem[tc.offset:], math.Float32bits(tc.val)) 116 res, err := f.ce.Call(context.Background(), m.CallCtx, []uint64{uint64(tc.offset)}) 117 require.NoError(t, err) 118 require.Equal(t, math.Float32bits(tc.val), uint32(res[0])) 119 } 120 }) 121 } 122 } 123 124 func getCallEngine(m *wasm.ModuleInstance, name string) (ce wasm.CallEngine, err error) { 125 f := m.Exports[name].Function 126 if f == nil { 127 err = fmt.Errorf("%s not found", name) 128 return 129 } 130 131 ce, err = m.Engine.NewCallEngine(m.CallCtx, f) 132 return 133 } 134 135 func setupHostCallBench(requireNoError func(error)) *wasm.ModuleInstance { 136 eng := compiler.NewEngine(context.Background(), api.CoreFeaturesV2) 137 138 ft := &wasm.FunctionType{ 139 Params: []wasm.ValueType{wasm.ValueTypeI32}, 140 Results: []wasm.ValueType{wasm.ValueTypeF32}, 141 ParamNumInUint64: 1, ResultNumInUint64: 1, 142 } 143 144 // Build the host module. 145 hostModule := &wasm.Module{ 146 TypeSection: []*wasm.FunctionType{ft}, 147 FunctionSection: []wasm.Index{0, 0, 0}, 148 CodeSection: []*wasm.Code{ 149 { 150 IsHostFunction: true, 151 GoFunc: api.GoModuleFunc(func(ctx context.Context, mod api.Module, stack []uint64) { 152 ret, ok := mod.Memory().ReadUint32Le(ctx, uint32(stack[0])) 153 if !ok { 154 panic("couldn't read memory") 155 } 156 stack[0] = uint64(ret) 157 }), 158 }, 159 wasm.MustParseGoReflectFuncCode( 160 func(ctx context.Context, m api.Module, pos uint32) float32 { 161 ret, ok := m.Memory().ReadUint32Le(ctx, pos) 162 if !ok { 163 panic("couldn't read memory") 164 } 165 return math.Float32frombits(ret) 166 }, 167 ), 168 { 169 IsHostFunction: true, 170 Body: []byte{ 171 wasm.OpcodeLocalGet, 0, 172 wasm.OpcodeI32Load, 0x2, 0x0, // offset = 0 173 wasm.OpcodeF32ReinterpretI32, 174 wasm.OpcodeEnd, 175 }, 176 }, 177 }, 178 ExportSection: []*wasm.Export{ 179 {Name: "go", Type: wasm.ExternTypeFunc, Index: 0}, 180 {Name: "go-reflect", Type: wasm.ExternTypeFunc, Index: 1}, 181 {Name: "wasm", Type: wasm.ExternTypeFunc, Index: 2}, 182 }, 183 ID: wasm.ModuleID{1, 2, 3, 4, 5}, 184 } 185 hostModule.BuildFunctionDefinitions() 186 187 host := &wasm.ModuleInstance{Name: "host", TypeIDs: []wasm.FunctionTypeID{0}} 188 host.Functions = host.BuildFunctions(hostModule) 189 host.BuildExports(hostModule.ExportSection) 190 goFn := host.Exports["go"].Function 191 goReflectFn := host.Exports["go-reflect"].Function 192 wasnFn := host.Exports["wasm"].Function 193 194 err := eng.CompileModule(testCtx, hostModule, nil) 195 requireNoError(err) 196 197 hostME, err := eng.NewModuleEngine(host.Name, hostModule, nil, host.Functions) 198 requireNoError(err) 199 linkModuleToEngine(host, hostME) 200 201 // Build the importing module. 202 importingModule := &wasm.Module{ 203 TypeSection: []*wasm.FunctionType{ft}, 204 ImportSection: []*wasm.Import{ 205 // Placeholders for imports from hostModule. 206 {Type: wasm.ExternTypeFunc}, 207 {Type: wasm.ExternTypeFunc}, 208 {Type: wasm.ExternTypeFunc}, 209 }, 210 FunctionSection: []wasm.Index{0, 0, 0}, 211 ExportSection: []*wasm.Export{ 212 {Name: callGoHostName, Type: wasm.ExternTypeFunc, Index: 2}, 213 {Name: callGoReflectHostName, Type: wasm.ExternTypeFunc, Index: 3}, 214 {Name: callWasmHostName, Type: wasm.ExternTypeFunc, Index: 4}, 215 }, 216 CodeSection: []*wasm.Code{ 217 {Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, // Calling the index 0 = host.go. 218 {Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, 1, wasm.OpcodeEnd}}, // Calling the index 1 = host.go-reflect. 219 {Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, 2, wasm.OpcodeEnd}}, // Calling the index 2 = host.wasm. 220 }, 221 // Indicates that this module has a memory so that compilers are able to assemble memory-related initialization. 222 MemorySection: &wasm.Memory{Min: 1}, 223 ID: wasm.ModuleID{1}, 224 } 225 226 importingModule.BuildFunctionDefinitions() 227 err = eng.CompileModule(testCtx, importingModule, nil) 228 requireNoError(err) 229 230 importing := &wasm.ModuleInstance{TypeIDs: []wasm.FunctionTypeID{0}} 231 importingFunctions := importing.BuildFunctions(importingModule) 232 importing.Functions = append([]*wasm.FunctionInstance{goFn, wasnFn}, importingFunctions...) 233 importing.BuildExports(importingModule.ExportSection) 234 235 importingMe, err := eng.NewModuleEngine(importing.Name, importingModule, []*wasm.FunctionInstance{goFn, goReflectFn, wasnFn}, importingFunctions) 236 requireNoError(err) 237 linkModuleToEngine(importing, importingMe) 238 239 importing.Memory = &wasm.MemoryInstance{Buffer: make([]byte, wasm.MemoryPageSize), Min: 1, Cap: 1, Max: 1} 240 return importing 241 } 242 243 func linkModuleToEngine(module *wasm.ModuleInstance, me wasm.ModuleEngine) { 244 module.Engine = me 245 module.CallCtx = wasm.NewCallContext(nil, module, nil) 246 }