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