github.com/tetratelabs/wazero@v1.2.1/imports/emscripten/emscripten_test.go (about) 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 // growWasm was compiled from testdata/grow.cc 27 // 28 //go:embed testdata/grow.wasm 29 var growWasm []byte 30 31 // invokeWasm was generated by the following: 32 // 33 // cd testdata; wat2wasm --debug-names invoke.wat 34 // 35 //go:embed testdata/invoke.wasm 36 var invokeWasm []byte 37 38 // testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors. 39 var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary") 40 41 // TestGrow is an integration test until we have an Emscripten example. 42 func TestGrow(t *testing.T) { 43 var log bytes.Buffer 44 45 // Set context to one that has an experimental listener 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 // Emscripten exits main with zero by default, which coerces to nul. 58 _, err = r.Instantiate(ctx, growWasm) 59 require.Nil(t, err) 60 61 // We expect the memory no-op memory growth hook to be invoked as wasm. 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 // Set context to one that has an experimental listener 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 --> .v_i32() 374 <-- 42 375 <== 42 376 <-- 42 377 `, 378 }, 379 { 380 name: "invoke_ii", 381 funcName: "call_i32_i32", 382 tableOffset: 2, 383 params: []uint64{42}, 384 expectedResults: []uint64{42}, 385 expectedLog: `--> .call_i32_i32(2,42) 386 ==> env.invoke_ii(index=2,a1=42) 387 --> .i32_i32(42) 388 <-- 42 389 <== 42 390 <-- 42 391 `, 392 }, 393 { 394 name: "invoke_iii", 395 funcName: "call_i32i32_i32", 396 tableOffset: 4, 397 params: []uint64{1, 2}, 398 expectedResults: []uint64{3}, 399 expectedLog: `--> .call_i32i32_i32(4,1,2) 400 ==> env.invoke_iii(index=4,a1=1,a2=2) 401 --> .i32i32_i32(1,2) 402 <-- 3 403 <== 3 404 <-- 3 405 `, 406 }, 407 { 408 name: "invoke_iiii", 409 funcName: "call_i32i32i32_i32", 410 tableOffset: 6, 411 params: []uint64{1, 2, 4}, 412 expectedResults: []uint64{7}, 413 expectedLog: `--> .call_i32i32i32_i32(6,1,2,4) 414 ==> env.invoke_iiii(index=6,a1=1,a2=2,a3=4) 415 --> .i32i32i32_i32(1,2,4) 416 <-- 7 417 <== 7 418 <-- 7 419 `, 420 }, 421 { 422 name: "invoke_iiiii", 423 funcName: "calli32_i32i32i32i32_i32", 424 tableOffset: 8, 425 params: []uint64{1, 2, 4, 8}, 426 expectedResults: []uint64{15}, 427 expectedLog: `--> .calli32_i32i32i32i32_i32(8,1,2,4,8) 428 ==> env.invoke_iiiii(index=8,a1=1,a2=2,a3=4,a4=8) 429 --> .i32i32i32i32_i32(1,2,4,8) 430 <-- 15 431 <== 15 432 <-- 15 433 `, 434 }, 435 { 436 name: "invoke_v", 437 funcName: "call_v_v", 438 tableOffset: 10, 439 expectedLog: `--> .call_v_v(10) 440 ==> env.invoke_v(index=10) 441 --> .v_v() 442 <-- 443 <== 444 <-- 445 `, 446 }, 447 { 448 name: "invoke_vi", 449 funcName: "call_i32_v", 450 tableOffset: 12, 451 params: []uint64{42}, 452 expectedLog: `--> .call_i32_v(12,42) 453 ==> env.invoke_vi(index=12,a1=42) 454 --> .i32_v(42) 455 <-- 456 <== 457 <-- 458 `, 459 }, 460 { 461 name: "invoke_vii", 462 funcName: "call_i32i32_v", 463 tableOffset: 14, 464 params: []uint64{1, 2}, 465 expectedLog: `--> .call_i32i32_v(14,1,2) 466 ==> env.invoke_vii(index=14,a1=1,a2=2) 467 --> .i32i32_v(1,2) 468 <-- 469 <== 470 <-- 471 `, 472 }, 473 { 474 name: "invoke_viii", 475 funcName: "call_i32i32i32_v", 476 tableOffset: 16, 477 params: []uint64{1, 2, 4}, 478 expectedLog: `--> .call_i32i32i32_v(16,1,2,4) 479 ==> env.invoke_viii(index=16,a1=1,a2=2,a3=4) 480 --> .i32i32i32_v(1,2,4) 481 <-- 482 <== 483 <-- 484 `, 485 }, 486 { 487 name: "invoke_viiii", 488 funcName: "calli32_i32i32i32i32_v", 489 tableOffset: 18, 490 params: []uint64{1, 2, 4, 8}, 491 expectedLog: `--> .calli32_i32i32i32i32_v(18,1,2,4,8) 492 ==> env.invoke_viiii(index=18,a1=1,a2=2,a3=4,a4=8) 493 --> .i32i32i32i32_v(1,2,4,8) 494 <-- 495 <== 496 <-- 497 `, 498 }, 499 } 500 501 for _, tt := range tests { 502 tc := tt 503 504 t.Run(tc.name, func(t *testing.T) { 505 defer log.Reset() 506 507 params := tc.params 508 params = append([]uint64{uint64(tc.tableOffset)}, params...) 509 510 results, err := mod.ExportedFunction(tc.funcName).Call(testCtx, params...) 511 require.NoError(t, err) 512 require.Equal(t, tc.expectedResults, results) 513 514 // We expect to see the dynamic function call target 515 require.Equal(t, tc.expectedLog, log.String()) 516 517 // We expect an unreachable function to err 518 params[0]++ 519 _, err = mod.ExportedFunction(tc.funcName).Call(testCtx, params...) 520 require.Error(t, err) 521 }) 522 } 523 }