1 package bench
2
3 import (
4 "context"
5 _ "embed"
6 "encoding/binary"
7 "math"
8 "testing"
9
10 "github.com/tetratelabs/wazero/api"
11 "github.com/tetratelabs/wazero/internal/engine/compiler"
12 "github.com/tetratelabs/wazero/internal/platform"
13 "github.com/tetratelabs/wazero/internal/testing/require"
14 "github.com/tetratelabs/wazero/internal/wasm"
15 )
16
17 const (
18
19
20 callGoHostName = "call_go_host"
21
22
23 callGoReflectHostName = "call_go_reflect_host"
24 )
25
26
27
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
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
187 importingModule := &wasm.Module{
188 ImportFunctionCount: 2,
189 TypeSection: []wasm.FunctionType{ft},
190 ImportSection: []wasm.Import{
191
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}},
206 {Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeCall, 1, wasm.OpcodeEnd}},
207 },
208
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 }
232
View as plain text