1 package emscripten
2
3 import (
4 "bytes"
5 "context"
6 _ "embed"
7 "testing"
8
9 "github.com/tetratelabs/wazero"
10 "github.com/tetratelabs/wazero/api"
11 "github.com/tetratelabs/wazero/experimental"
12 "github.com/tetratelabs/wazero/experimental/logging"
13 "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
14 internal "github.com/tetratelabs/wazero/internal/emscripten"
15 "github.com/tetratelabs/wazero/internal/testing/binaryencoding"
16 "github.com/tetratelabs/wazero/internal/testing/require"
17 "github.com/tetratelabs/wazero/internal/wasm"
18 )
19
20 const (
21 i64 = wasm.ValueTypeI64
22 f32 = wasm.ValueTypeF32
23 f64 = wasm.ValueTypeF64
24 )
25
26
27
28
29 var growWasm []byte
30
31
32
33
34
35
36 var invokeWasm []byte
37
38
39 var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
40
41
42 func TestGrow(t *testing.T) {
43 var log bytes.Buffer
44
45
46 ctx := context.WithValue(testCtx, experimental.FunctionListenerFactoryKey{},
47 logging.NewHostLoggingListenerFactory(&log, logging.LogScopeMemory))
48
49 r := wazero.NewRuntime(ctx)
50 defer r.Close(ctx)
51
52 wasi_snapshot_preview1.MustInstantiate(ctx, r)
53
54 _, err := Instantiate(ctx, r)
55 require.NoError(t, err)
56
57
58 _, err = r.Instantiate(ctx, growWasm)
59 require.Nil(t, err)
60
61
62 require.Contains(t, log.String(), "==> env.emscripten_notify_memory_growth(memory_index=0)")
63 }
64
65 func TestNewFunctionExporterForModule(t *testing.T) {
66 tests := []struct {
67 name string
68 input *wasm.Module
69 expected emscriptenFns
70 }{
71 {
72 name: "empty",
73 input: &wasm.Module{},
74 expected: emscriptenFns{},
75 },
76 {
77 name: internal.FunctionNotifyMemoryGrowth,
78 input: &wasm.Module{
79 TypeSection: []wasm.FunctionType{
80 {Params: []wasm.ValueType{i32}},
81 },
82 ImportSection: []wasm.Import{
83 {
84 Module: "env", Name: internal.FunctionNotifyMemoryGrowth,
85 Type: wasm.ExternTypeFunc,
86 DescFunc: 0,
87 },
88 },
89 },
90 expected: []*wasm.HostFunc{internal.NotifyMemoryGrowth},
91 },
92 {
93 name: "all result types",
94 input: &wasm.Module{
95 TypeSection: []wasm.FunctionType{
96 {Params: []wasm.ValueType{i32}},
97 {Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}},
98 {Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i64}},
99 {Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{f32}},
100 {Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{f64}},
101 },
102 ImportSection: []wasm.Import{
103 {
104 Module: "env", Name: "invoke_v",
105 Type: wasm.ExternTypeFunc,
106 DescFunc: 0,
107 },
108 {
109 Module: "env", Name: "invoke_i",
110 Type: wasm.ExternTypeFunc,
111 DescFunc: 1,
112 },
113 {
114 Module: "env", Name: "invoke_p",
115 Type: wasm.ExternTypeFunc,
116 DescFunc: 1,
117 },
118 {
119 Module: "env", Name: "invoke_j",
120 Type: wasm.ExternTypeFunc,
121 DescFunc: 2,
122 },
123 {
124 Module: "env", Name: "invoke_f",
125 Type: wasm.ExternTypeFunc,
126 DescFunc: 3,
127 },
128 {
129 Module: "env", Name: "invoke_d",
130 Type: wasm.ExternTypeFunc,
131 DescFunc: 4,
132 },
133 },
134 },
135 expected: []*wasm.HostFunc{
136 {
137 ExportName: "invoke_v",
138 ParamTypes: []api.ValueType{i32},
139 ParamNames: []string{"index"},
140 Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{}}},
141 },
142 {
143 ExportName: "invoke_i",
144 ParamTypes: []api.ValueType{i32},
145 ParamNames: []string{"index"},
146 ResultTypes: []api.ValueType{i32},
147 Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{Results: []api.ValueType{i32}}}},
148 },
149 {
150 ExportName: "invoke_p",
151 ParamTypes: []api.ValueType{i32},
152 ParamNames: []string{"index"},
153 ResultTypes: []api.ValueType{i32},
154 Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{Results: []api.ValueType{i32}}}},
155 },
156 {
157 ExportName: "invoke_j",
158 ParamTypes: []api.ValueType{i32},
159 ParamNames: []string{"index"},
160 ResultTypes: []api.ValueType{i64},
161 Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{Results: []api.ValueType{i64}}}},
162 },
163 {
164 ExportName: "invoke_f",
165 ParamTypes: []api.ValueType{i32},
166 ParamNames: []string{"index"},
167 ResultTypes: []api.ValueType{f32},
168 Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{Results: []api.ValueType{f32}}}},
169 },
170 {
171 ExportName: "invoke_d",
172 ParamTypes: []api.ValueType{i32},
173 ParamNames: []string{"index"},
174 ResultTypes: []api.ValueType{f64},
175 Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{Results: []api.ValueType{f64}}}},
176 },
177 },
178 },
179 {
180 name: "ignores other imports",
181 input: &wasm.Module{
182 TypeSection: []wasm.FunctionType{
183 {Params: []wasm.ValueType{i32}},
184 },
185 ImportSection: []wasm.Import{
186 {
187 Module: "anv", Name: "invoke_v",
188 Type: wasm.ExternTypeFunc,
189 DescFunc: 0,
190 },
191 {
192 Module: "env", Name: "invoke_v",
193 Type: wasm.ExternTypeFunc,
194 DescFunc: 0,
195 },
196 {
197 Module: "env", Name: "grow",
198 Type: wasm.ExternTypeFunc,
199 DescFunc: 0,
200 },
201 },
202 },
203 expected: []*wasm.HostFunc{
204 {
205 ExportName: "invoke_v",
206 ParamTypes: []api.ValueType{i32},
207 ParamNames: []string{"index"},
208 Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{}}},
209 },
210 },
211 },
212 {
213 name: "invoke_v and " + internal.FunctionNotifyMemoryGrowth,
214 input: &wasm.Module{
215 TypeSection: []wasm.FunctionType{{Params: []wasm.ValueType{i32}}},
216 ImportSection: []wasm.Import{
217 {
218 Module: "env", Name: "invoke_v",
219 Type: wasm.ExternTypeFunc,
220 DescFunc: 0,
221 },
222 {
223 Module: "env", Name: internal.FunctionNotifyMemoryGrowth,
224 Type: wasm.ExternTypeFunc,
225 DescFunc: 0,
226 },
227 },
228 },
229 expected: []*wasm.HostFunc{
230 {
231 ExportName: "invoke_v",
232 ParamTypes: []api.ValueType{i32},
233 ParamNames: []string{"index"},
234 Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{}}},
235 },
236 internal.NotifyMemoryGrowth,
237 },
238 },
239 {
240 name: "invoke_vi",
241 input: &wasm.Module{
242 TypeSection: []wasm.FunctionType{
243 {Params: []wasm.ValueType{i32, i32}},
244 },
245 ImportSection: []wasm.Import{
246 {
247 Module: "env", Name: "invoke_vi",
248 Type: wasm.ExternTypeFunc,
249 DescFunc: 0,
250 },
251 },
252 },
253 expected: []*wasm.HostFunc{
254 {
255 ExportName: "invoke_vi",
256 ParamTypes: []api.ValueType{i32, i32},
257 ParamNames: []string{"index", "a1"},
258 Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{Params: []api.ValueType{i32}}}},
259 },
260 },
261 },
262 {
263 name: "invoke_iiiii",
264 input: &wasm.Module{
265 TypeSection: []wasm.FunctionType{
266 {
267 Params: []wasm.ValueType{i32, i32, i32, i32, i32},
268 Results: []wasm.ValueType{i32},
269 },
270 },
271 ImportSection: []wasm.Import{
272 {
273 Module: "env", Name: "invoke_iiiii",
274 Type: wasm.ExternTypeFunc,
275 DescFunc: 0,
276 },
277 },
278 },
279 expected: []*wasm.HostFunc{
280 {
281 ExportName: "invoke_iiiii",
282 ParamTypes: []api.ValueType{i32, i32, i32, i32, i32},
283 ParamNames: []string{"index", "a1", "a2", "a3", "a4"},
284 ResultTypes: []wasm.ValueType{i32},
285 Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{
286 Params: []api.ValueType{i32, i32, i32, i32},
287 Results: []api.ValueType{i32},
288 }}},
289 },
290 },
291 },
292 {
293 name: "invoke_viiiddiiiiii",
294 input: &wasm.Module{
295 TypeSection: []wasm.FunctionType{
296 {
297 Params: []wasm.ValueType{i32, i32, i32, i32, f64, f64, i32, i32, i32, i32, i32, i32},
298 },
299 },
300 ImportSection: []wasm.Import{
301 {
302 Module: "env", Name: "invoke_viiiddiiiiii",
303 Type: wasm.ExternTypeFunc,
304 DescFunc: 0,
305 },
306 },
307 },
308 expected: []*wasm.HostFunc{
309 {
310 ExportName: "invoke_viiiddiiiiii",
311 ParamTypes: []api.ValueType{i32, i32, i32, i32, f64, f64, i32, i32, i32, i32, i32, i32},
312 ParamNames: []string{"index", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10", "a11"},
313 Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{
314 Params: []api.ValueType{i32, i32, i32, f64, f64, i32, i32, i32, i32, i32, i32},
315 }}},
316 },
317 },
318 },
319 }
320
321 for _, tt := range tests {
322 tc := tt
323
324 t.Run(tc.name, func(t *testing.T) {
325 r := wazero.NewRuntime(testCtx)
326 defer r.Close(testCtx)
327
328 guest, err := r.CompileModule(testCtx, binaryencoding.EncodeModule(tc.input))
329 require.NoError(t, err)
330
331 exporter, err := NewFunctionExporterForModule(guest)
332 require.NoError(t, err)
333 actual := exporter.(emscriptenFns)
334
335 require.Equal(t, len(tc.expected), len(actual))
336 for i, expected := range tc.expected {
337 require.Equal(t, expected, actual[i], actual[i].ExportName)
338 }
339 })
340 }
341 }
342
343 func TestInstantiateForModule(t *testing.T) {
344 var log bytes.Buffer
345
346
347 ctx := context.WithValue(testCtx, experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&log))
348
349 r := wazero.NewRuntime(ctx)
350 defer r.Close(ctx)
351
352 compiled, err := r.CompileModule(ctx, invokeWasm)
353 require.NoError(t, err)
354
355 _, err = InstantiateForModule(ctx, r, compiled)
356 require.NoError(t, err)
357
358 mod, err := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig())
359 require.NoError(t, err)
360
361 tests := []struct {
362 name, funcName string
363 tableOffset int
364 params, expectedResults []uint64
365 expectedLog string
366 }{
367 {
368 name: "invoke_i",
369 funcName: "call_v_i32",
370 expectedResults: []uint64{42},
371 expectedLog: `--> .call_v_i32(0)
372 ==> env.invoke_i(index=0)
373 --> .stackSave()
374 <-- 65536
375 --> .v_i32()
376 <-- 42
377 <== 42
378 <-- 42
379 `,
380 },
381 {
382 name: "invoke_ii",
383 funcName: "call_i32_i32",
384 tableOffset: 2,
385 params: []uint64{42},
386 expectedResults: []uint64{42},
387 expectedLog: `--> .call_i32_i32(2,42)
388 ==> env.invoke_ii(index=2,a1=42)
389 --> .stackSave()
390 <-- 65536
391 --> .i32_i32(42)
392 <-- 42
393 <== 42
394 <-- 42
395 `,
396 },
397 {
398 name: "invoke_iii",
399 funcName: "call_i32i32_i32",
400 tableOffset: 4,
401 params: []uint64{1, 2},
402 expectedResults: []uint64{3},
403 expectedLog: `--> .call_i32i32_i32(4,1,2)
404 ==> env.invoke_iii(index=4,a1=1,a2=2)
405 --> .stackSave()
406 <-- 65536
407 --> .i32i32_i32(1,2)
408 <-- 3
409 <== 3
410 <-- 3
411 `,
412 },
413 {
414 name: "invoke_iiii",
415 funcName: "call_i32i32i32_i32",
416 tableOffset: 6,
417 params: []uint64{1, 2, 4},
418 expectedResults: []uint64{7},
419 expectedLog: `--> .call_i32i32i32_i32(6,1,2,4)
420 ==> env.invoke_iiii(index=6,a1=1,a2=2,a3=4)
421 --> .stackSave()
422 <-- 65536
423 --> .i32i32i32_i32(1,2,4)
424 <-- 7
425 <== 7
426 <-- 7
427 `,
428 },
429 {
430 name: "invoke_iiiii",
431 funcName: "calli32_i32i32i32i32_i32",
432 tableOffset: 8,
433 params: []uint64{1, 2, 4, 8},
434 expectedResults: []uint64{15},
435 expectedLog: `--> .calli32_i32i32i32i32_i32(8,1,2,4,8)
436 ==> env.invoke_iiiii(index=8,a1=1,a2=2,a3=4,a4=8)
437 --> .stackSave()
438 <-- 65536
439 --> .i32i32i32i32_i32(1,2,4,8)
440 <-- 15
441 <== 15
442 <-- 15
443 `,
444 },
445 {
446 name: "invoke_v",
447 funcName: "call_v_v",
448 tableOffset: 10,
449 expectedLog: `--> .call_v_v(10)
450 ==> env.invoke_v(index=10)
451 --> .stackSave()
452 <-- 65536
453 --> .v_v()
454 <--
455 <==
456 <--
457 `,
458 },
459 {
460 name: "invoke_vi",
461 funcName: "call_i32_v",
462 tableOffset: 12,
463 params: []uint64{42},
464 expectedLog: `--> .call_i32_v(12,42)
465 ==> env.invoke_vi(index=12,a1=42)
466 --> .stackSave()
467 <-- 65536
468 --> .i32_v(42)
469 <--
470 <==
471 <--
472 `,
473 },
474 {
475 name: "invoke_vii",
476 funcName: "call_i32i32_v",
477 tableOffset: 14,
478 params: []uint64{1, 2},
479 expectedLog: `--> .call_i32i32_v(14,1,2)
480 ==> env.invoke_vii(index=14,a1=1,a2=2)
481 --> .stackSave()
482 <-- 65536
483 --> .i32i32_v(1,2)
484 <--
485 <==
486 <--
487 `,
488 },
489 {
490 name: "invoke_viii",
491 funcName: "call_i32i32i32_v",
492 tableOffset: 16,
493 params: []uint64{1, 2, 4},
494 expectedLog: `--> .call_i32i32i32_v(16,1,2,4)
495 ==> env.invoke_viii(index=16,a1=1,a2=2,a3=4)
496 --> .stackSave()
497 <-- 65536
498 --> .i32i32i32_v(1,2,4)
499 <--
500 <==
501 <--
502 `,
503 },
504 {
505 name: "invoke_viiii",
506 funcName: "calli32_i32i32i32i32_v",
507 tableOffset: 18,
508 params: []uint64{1, 2, 4, 8},
509 expectedLog: `--> .calli32_i32i32i32i32_v(18,1,2,4,8)
510 ==> env.invoke_viiii(index=18,a1=1,a2=2,a3=4,a4=8)
511 --> .stackSave()
512 <-- 65536
513 --> .i32i32i32i32_v(1,2,4,8)
514 <--
515 <==
516 <--
517 `,
518 },
519 {
520 name: "invoke_v_with_longjmp",
521 funcName: "call_invoke_v_with_longjmp_throw",
522 tableOffset: 20,
523 params: []uint64{},
524 expectedLog: `--> .call_invoke_v_with_longjmp_throw(20)
525 ==> env.invoke_v(index=20)
526 --> .stackSave()
527 <-- 42
528 --> .call_longjmp_throw()
529 ==> env._emscripten_throw_longjmp()
530 --> .stackRestore(42)
531 <--
532 --> .setThrew(1,0)
533 <--
534 <==
535 <--
536 `,
537 },
538 }
539
540 for _, tt := range tests {
541 tc := tt
542
543 t.Run(tc.name, func(t *testing.T) {
544 defer log.Reset()
545
546 params := tc.params
547 params = append([]uint64{uint64(tc.tableOffset)}, params...)
548
549 results, err := mod.ExportedFunction(tc.funcName).Call(testCtx, params...)
550 require.NoError(t, err)
551 require.Equal(t, tc.expectedResults, results)
552
553
554 require.Equal(t, tc.expectedLog, log.String())
555
556
557 params[0]++
558 _, err = mod.ExportedFunction(tc.funcName).Call(testCtx, params...)
559 require.Error(t, err)
560 })
561 }
562 }
563
View as plain text