1 package wazero
2
3 import (
4 "context"
5 "testing"
6
7 "github.com/tetratelabs/wazero/api"
8 "github.com/tetratelabs/wazero/internal/testing/require"
9 "github.com/tetratelabs/wazero/internal/wasm"
10 )
11
12
13 func TestNewHostModuleBuilder_Compile(t *testing.T) {
14 i32, i64 := api.ValueTypeI32, api.ValueTypeI64
15
16 uint32_uint32 := func(context.Context, uint32) uint32 {
17 return 0
18 }
19 uint64_uint32 := func(context.Context, uint64) uint32 {
20 return 0
21 }
22
23 gofunc1 := api.GoFunc(func(ctx context.Context, stack []uint64) {
24 stack[0] = 0
25 })
26 gofunc2 := api.GoFunc(func(ctx context.Context, stack []uint64) {
27 stack[0] = 0
28 })
29
30 tests := []struct {
31 name string
32 input func(Runtime) HostModuleBuilder
33 expected *wasm.Module
34 }{
35 {
36 name: "empty",
37 input: func(r Runtime) HostModuleBuilder {
38 return r.NewHostModuleBuilder("host")
39 },
40 expected: &wasm.Module{NameSection: &wasm.NameSection{ModuleName: "host"}},
41 },
42 {
43 name: "only name",
44 input: func(r Runtime) HostModuleBuilder {
45 return r.NewHostModuleBuilder("env")
46 },
47 expected: &wasm.Module{NameSection: &wasm.NameSection{ModuleName: "env"}},
48 },
49 {
50 name: "WithFunc",
51 input: func(r Runtime) HostModuleBuilder {
52 return r.NewHostModuleBuilder("host").
53 NewFunctionBuilder().WithFunc(uint32_uint32).Export("1")
54 },
55 expected: &wasm.Module{
56 TypeSection: []wasm.FunctionType{
57 {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}},
58 },
59 FunctionSection: []wasm.Index{0},
60 CodeSection: []wasm.Code{wasm.MustParseGoReflectFuncCode(uint32_uint32)},
61 ExportSection: []wasm.Export{
62 {Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
63 },
64 Exports: map[string]*wasm.Export{
65 "1": {Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
66 },
67 NameSection: &wasm.NameSection{
68 FunctionNames: wasm.NameMap{{Index: 0, Name: "1"}},
69 ModuleName: "host",
70 },
71 },
72 },
73 {
74 name: "WithFunc WithName WithParameterNames",
75 input: func(r Runtime) HostModuleBuilder {
76 return r.NewHostModuleBuilder("host").NewFunctionBuilder().
77 WithFunc(uint32_uint32).
78 WithName("get").WithParameterNames("x").
79 Export("1")
80 },
81 expected: &wasm.Module{
82 TypeSection: []wasm.FunctionType{
83 {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}},
84 },
85 FunctionSection: []wasm.Index{0},
86 CodeSection: []wasm.Code{wasm.MustParseGoReflectFuncCode(uint32_uint32)},
87 ExportSection: []wasm.Export{
88 {Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
89 },
90 Exports: map[string]*wasm.Export{
91 "1": {Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
92 },
93 NameSection: &wasm.NameSection{
94 FunctionNames: wasm.NameMap{{Index: 0, Name: "get"}},
95 LocalNames: []wasm.NameMapAssoc{{Index: 0, NameMap: wasm.NameMap{{Index: 0, Name: "x"}}}},
96 ModuleName: "host",
97 },
98 },
99 },
100 {
101 name: "WithFunc WithName WithResultNames",
102 input: func(r Runtime) HostModuleBuilder {
103 return r.NewHostModuleBuilder("host").NewFunctionBuilder().
104 WithFunc(uint32_uint32).
105 WithName("get").WithResultNames("x").
106 Export("1")
107 },
108 expected: &wasm.Module{
109 TypeSection: []wasm.FunctionType{
110 {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}},
111 },
112 FunctionSection: []wasm.Index{0},
113 CodeSection: []wasm.Code{wasm.MustParseGoReflectFuncCode(uint32_uint32)},
114 ExportSection: []wasm.Export{
115 {Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
116 },
117 Exports: map[string]*wasm.Export{
118 "1": {Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
119 },
120 NameSection: &wasm.NameSection{
121 FunctionNames: wasm.NameMap{{Index: 0, Name: "get"}},
122 ResultNames: []wasm.NameMapAssoc{{Index: 0, NameMap: wasm.NameMap{{Index: 0, Name: "x"}}}},
123 ModuleName: "host",
124 },
125 },
126 },
127 {
128 name: "WithFunc overwrites existing",
129 input: func(r Runtime) HostModuleBuilder {
130 return r.NewHostModuleBuilder("host").
131 NewFunctionBuilder().WithFunc(uint32_uint32).Export("1").
132 NewFunctionBuilder().WithFunc(uint64_uint32).Export("1")
133 },
134 expected: &wasm.Module{
135 TypeSection: []wasm.FunctionType{
136 {Params: []api.ValueType{i64}, Results: []api.ValueType{i32}},
137 },
138 FunctionSection: []wasm.Index{0},
139 CodeSection: []wasm.Code{wasm.MustParseGoReflectFuncCode(uint64_uint32)},
140 ExportSection: []wasm.Export{
141 {Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
142 },
143 Exports: map[string]*wasm.Export{
144 "1": {Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
145 },
146 NameSection: &wasm.NameSection{
147 FunctionNames: wasm.NameMap{{Index: 0, Name: "1"}},
148 ModuleName: "host",
149 },
150 },
151 },
152 {
153 name: "WithFunc twice",
154 input: func(r Runtime) HostModuleBuilder {
155
156 return r.NewHostModuleBuilder("host").
157 NewFunctionBuilder().WithFunc(uint64_uint32).Export("2").
158 NewFunctionBuilder().WithFunc(uint32_uint32).Export("1")
159 },
160 expected: &wasm.Module{
161 TypeSection: []wasm.FunctionType{
162 {Params: []api.ValueType{i64}, Results: []api.ValueType{i32}},
163 {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}},
164 },
165 FunctionSection: []wasm.Index{0, 1},
166 CodeSection: []wasm.Code{wasm.MustParseGoReflectFuncCode(uint64_uint32), wasm.MustParseGoReflectFuncCode(uint32_uint32)},
167 ExportSection: []wasm.Export{
168 {Name: "2", Type: wasm.ExternTypeFunc, Index: 0},
169 {Name: "1", Type: wasm.ExternTypeFunc, Index: 1},
170 },
171 Exports: map[string]*wasm.Export{
172 "2": {Name: "2", Type: wasm.ExternTypeFunc, Index: 0},
173 "1": {Name: "1", Type: wasm.ExternTypeFunc, Index: 1},
174 },
175 NameSection: &wasm.NameSection{
176 FunctionNames: wasm.NameMap{{Index: 0, Name: "2"}, {Index: 1, Name: "1"}},
177 ModuleName: "host",
178 },
179 },
180 },
181 {
182 name: "WithGoFunction",
183 input: func(r Runtime) HostModuleBuilder {
184 return r.NewHostModuleBuilder("host").
185 NewFunctionBuilder().
186 WithGoFunction(gofunc1, []api.ValueType{i32}, []api.ValueType{i32}).
187 Export("1")
188 },
189 expected: &wasm.Module{
190 TypeSection: []wasm.FunctionType{
191 {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}},
192 },
193 FunctionSection: []wasm.Index{0},
194 CodeSection: []wasm.Code{
195 {GoFunc: gofunc1},
196 },
197 ExportSection: []wasm.Export{
198 {Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
199 },
200 Exports: map[string]*wasm.Export{
201 "1": {Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
202 },
203 NameSection: &wasm.NameSection{
204 FunctionNames: wasm.NameMap{{Index: 0, Name: "1"}},
205 ModuleName: "host",
206 },
207 },
208 },
209 {
210 name: "WithGoFunction WithName WithParameterNames",
211 input: func(r Runtime) HostModuleBuilder {
212 return r.NewHostModuleBuilder("host").NewFunctionBuilder().
213 WithGoFunction(gofunc1, []api.ValueType{i32}, []api.ValueType{i32}).
214 WithName("get").WithParameterNames("x").
215 Export("1")
216 },
217 expected: &wasm.Module{
218 TypeSection: []wasm.FunctionType{
219 {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}},
220 },
221 FunctionSection: []wasm.Index{0},
222 CodeSection: []wasm.Code{
223 {GoFunc: gofunc1},
224 },
225 ExportSection: []wasm.Export{
226 {Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
227 },
228 Exports: map[string]*wasm.Export{
229 "1": {Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
230 },
231 NameSection: &wasm.NameSection{
232 FunctionNames: wasm.NameMap{{Index: 0, Name: "get"}},
233 LocalNames: []wasm.NameMapAssoc{{Index: 0, NameMap: wasm.NameMap{{Index: 0, Name: "x"}}}},
234 ModuleName: "host",
235 },
236 },
237 },
238 {
239 name: "WithGoFunction overwrites existing",
240 input: func(r Runtime) HostModuleBuilder {
241 return r.NewHostModuleBuilder("host").
242 NewFunctionBuilder().
243 WithGoFunction(gofunc1, []api.ValueType{i32}, []api.ValueType{i32}).
244 Export("1").
245 NewFunctionBuilder().
246 WithGoFunction(gofunc2, []api.ValueType{i64}, []api.ValueType{i32}).
247 Export("1")
248 },
249 expected: &wasm.Module{
250 TypeSection: []wasm.FunctionType{
251 {Params: []api.ValueType{i64}, Results: []api.ValueType{i32}},
252 },
253 FunctionSection: []wasm.Index{0},
254 CodeSection: []wasm.Code{
255 {GoFunc: gofunc2},
256 },
257 ExportSection: []wasm.Export{
258 {Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
259 },
260 Exports: map[string]*wasm.Export{
261 "1": {Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
262 },
263 NameSection: &wasm.NameSection{
264 FunctionNames: wasm.NameMap{{Index: 0, Name: "1"}},
265 ModuleName: "host",
266 },
267 },
268 },
269 {
270 name: "WithGoFunction twice",
271 input: func(r Runtime) HostModuleBuilder {
272
273 return r.NewHostModuleBuilder("host").
274 NewFunctionBuilder().
275 WithGoFunction(gofunc2, []api.ValueType{i64}, []api.ValueType{i32}).
276 Export("2").
277 NewFunctionBuilder().
278 WithGoFunction(gofunc1, []api.ValueType{i32}, []api.ValueType{i32}).
279 Export("1")
280 },
281 expected: &wasm.Module{
282 TypeSection: []wasm.FunctionType{
283 {Params: []api.ValueType{i64}, Results: []api.ValueType{i32}},
284 {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}},
285 },
286 FunctionSection: []wasm.Index{0, 1},
287 CodeSection: []wasm.Code{
288 {GoFunc: gofunc2},
289 {GoFunc: gofunc1},
290 },
291 ExportSection: []wasm.Export{
292 {Name: "2", Type: wasm.ExternTypeFunc, Index: 0},
293 {Name: "1", Type: wasm.ExternTypeFunc, Index: 1},
294 },
295 Exports: map[string]*wasm.Export{
296 "2": {Name: "2", Type: wasm.ExternTypeFunc, Index: 0},
297 "1": {Name: "1", Type: wasm.ExternTypeFunc, Index: 1},
298 },
299 NameSection: &wasm.NameSection{
300 FunctionNames: wasm.NameMap{{Index: 0, Name: "2"}, {Index: 1, Name: "1"}},
301 ModuleName: "host",
302 },
303 },
304 },
305 }
306
307 for _, tt := range tests {
308 tc := tt
309
310 t.Run(tc.name, func(t *testing.T) {
311 b := tc.input(NewRuntime(testCtx)).(*hostModuleBuilder)
312 compiled, err := b.Compile(testCtx)
313 require.NoError(t, err)
314 m := compiled.(*compiledModule)
315
316 requireHostModuleEquals(t, tc.expected, m.module)
317
318 require.Equal(t, b.r.store.Engine, m.compiledEngine)
319
320
321 expTypeIDs, err := b.r.store.GetFunctionTypeIDs(tc.expected.TypeSection)
322 require.NoError(t, err)
323 require.Equal(t, expTypeIDs, m.typeIDs)
324
325
326 mod, err := b.r.InstantiateModule(testCtx, m, NewModuleConfig())
327 require.NoError(t, err)
328
329
330 require.NoError(t, mod.Close(testCtx))
331 require.Equal(t, uint32(1), b.r.store.Engine.CompiledModuleCount())
332 })
333 }
334 }
335
336
337
338 func TestNewHostModuleBuilder_Compile_Errors(t *testing.T) {
339 tests := []struct {
340 name string
341 input func(Runtime) HostModuleBuilder
342 expectedErr string
343 }{
344 {
345 name: "error compiling",
346 input: func(rt Runtime) HostModuleBuilder {
347 return rt.NewHostModuleBuilder("host").NewFunctionBuilder().
348 WithFunc(&wasm.HostFunc{ExportName: "fn", Code: wasm.Code{GoFunc: func(string) {}}}).
349 Export("fn")
350 },
351 expectedErr: `func[host.fn] param[0] is unsupported: string`,
352 },
353 }
354
355 for _, tt := range tests {
356 tc := tt
357
358 t.Run(tc.name, func(t *testing.T) {
359 _, e := tc.input(NewRuntime(testCtx)).Compile(testCtx)
360 require.EqualError(t, e, tc.expectedErr)
361 })
362 }
363 }
364
365
366 func TestNewHostModuleBuilder_Instantiate(t *testing.T) {
367 r := NewRuntime(testCtx)
368 m, err := r.NewHostModuleBuilder("env").Instantiate(testCtx)
369 require.NoError(t, err)
370
371
372 require.Equal(t, r.Module("env"), m)
373
374
375 require.NoError(t, m.Close(testCtx))
376 require.Zero(t, r.(*runtime).store.Engine.CompiledModuleCount())
377 }
378
379
380 func TestNewHostModuleBuilder_Instantiate_Errors(t *testing.T) {
381 r := NewRuntime(testCtx)
382 _, err := r.NewHostModuleBuilder("env").Instantiate(testCtx)
383 require.NoError(t, err)
384
385 _, err = r.NewHostModuleBuilder("env").Instantiate(testCtx)
386 require.EqualError(t, err, "module[env] has already been instantiated")
387 }
388
389
390 func requireHostModuleEquals(t *testing.T, expected, actual *wasm.Module) {
391
392 for i := range expected.TypeSection {
393 tp := &expected.TypeSection[i]
394 tp.CacheNumInUint64()
395
396 _ = tp.String()
397 }
398 require.Equal(t, expected.TypeSection, actual.TypeSection)
399 require.Equal(t, expected.ImportSection, actual.ImportSection)
400 require.Equal(t, expected.FunctionSection, actual.FunctionSection)
401 require.Equal(t, expected.TableSection, actual.TableSection)
402 require.Equal(t, expected.MemorySection, actual.MemorySection)
403 require.Equal(t, expected.GlobalSection, actual.GlobalSection)
404 require.Equal(t, expected.ExportSection, actual.ExportSection)
405 require.Equal(t, expected.Exports, actual.Exports)
406 require.Equal(t, expected.StartSection, actual.StartSection)
407 require.Equal(t, expected.ElementSection, actual.ElementSection)
408 require.Equal(t, expected.DataSection, actual.DataSection)
409 require.Equal(t, expected.NameSection, actual.NameSection)
410
411
412
413 require.Equal(t, len(expected.CodeSection), len(actual.CodeSection))
414 for i, c := range expected.CodeSection {
415 actualCode := actual.CodeSection[i]
416 require.Equal(t, c.GoFunc, actualCode.GoFunc)
417
418
419 require.Nil(t, actualCode.Body)
420 require.Nil(t, actualCode.LocalTypes)
421 }
422 }
423
View as plain text