wa-lang.org/wazero@v1.0.2/internal/engine/compiler/compiler_post1_0_test.go (about) 1 package compiler 2 3 import ( 4 "fmt" 5 "strconv" 6 "testing" 7 "unsafe" 8 9 "wa-lang.org/wazero/internal/testing/require" 10 "wa-lang.org/wazero/internal/wasm" 11 "wa-lang.org/wazero/internal/wazeroir" 12 ) 13 14 func TestCompiler_compileSignExtend(t *testing.T) { 15 type fromKind byte 16 from8, from16, from32 := fromKind(0), fromKind(1), fromKind(2) 17 18 t.Run("32bit", func(t *testing.T) { 19 tests := []struct { 20 in int32 21 expected int32 22 fromKind fromKind 23 }{ 24 // https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i32.wast#L270-L276 25 {in: 0, expected: 0, fromKind: from8}, 26 {in: 0x7f, expected: 127, fromKind: from8}, 27 {in: 0x80, expected: -128, fromKind: from8}, 28 {in: 0xff, expected: -1, fromKind: from8}, 29 {in: 0x012345_00, expected: 0, fromKind: from8}, 30 {in: -19088768 /* = 0xfedcba_80 bit pattern */, expected: -0x80, fromKind: from8}, 31 {in: -1, expected: -1, fromKind: from8}, 32 33 // https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i32.wast#L278-L284 34 {in: 0, expected: 0, fromKind: from16}, 35 {in: 0x7fff, expected: 32767, fromKind: from16}, 36 {in: 0x8000, expected: -32768, fromKind: from16}, 37 {in: 0xffff, expected: -1, fromKind: from16}, 38 {in: 0x0123_0000, expected: 0, fromKind: from16}, 39 {in: -19103744 /* = 0xfedc_8000 bit pattern */, expected: -0x8000, fromKind: from16}, 40 {in: -1, expected: -1, fromKind: from16}, 41 } 42 43 for _, tt := range tests { 44 tc := tt 45 t.Run(fmt.Sprintf("0x%x", tc.in), func(t *testing.T) { 46 env := newCompilerEnvironment() 47 compiler := env.requireNewCompiler(t, newCompiler, nil) 48 err := compiler.compilePreamble() 49 require.NoError(t, err) 50 51 // Setup the promote target. 52 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(tc.in)}) 53 require.NoError(t, err) 54 55 if tc.fromKind == from8 { 56 err = compiler.compileSignExtend32From8() 57 } else { 58 err = compiler.compileSignExtend32From16() 59 } 60 require.NoError(t, err) 61 62 // To verify the behavior, we release the value 63 // to the stack. 64 err = compiler.compileReturnFunction() 65 require.NoError(t, err) 66 67 // Generate and run the code under test. 68 code, _, err := compiler.compile() 69 require.NoError(t, err) 70 env.exec(code) 71 72 require.Equal(t, uint64(1), env.stackPointer()) 73 require.Equal(t, tc.expected, env.stackTopAsInt32()) 74 }) 75 } 76 }) 77 t.Run("64bit", func(t *testing.T) { 78 tests := []struct { 79 in int64 80 expected int64 81 fromKind fromKind 82 }{ 83 // https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i64.wast#L271-L277 84 {in: 0, expected: 0, fromKind: from8}, 85 {in: 0x7f, expected: 127, fromKind: from8}, 86 {in: 0x80, expected: -128, fromKind: from8}, 87 {in: 0xff, expected: -1, fromKind: from8}, 88 {in: 0x01234567_89abcd_00, expected: 0, fromKind: from8}, 89 {in: 81985529216486784 /* = 0xfedcba98_765432_80 bit pattern */, expected: -0x80, fromKind: from8}, 90 {in: -1, expected: -1, fromKind: from8}, 91 92 // https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i64.wast#L279-L285 93 {in: 0, expected: 0, fromKind: from16}, 94 {in: 0x7fff, expected: 32767, fromKind: from16}, 95 {in: 0x8000, expected: -32768, fromKind: from16}, 96 {in: 0xffff, expected: -1, fromKind: from16}, 97 {in: 0x12345678_9abc_0000, expected: 0, fromKind: from16}, 98 {in: 81985529216466944 /* = 0xfedcba98_7654_8000 bit pattern */, expected: -0x8000, fromKind: from16}, 99 {in: -1, expected: -1, fromKind: from16}, 100 101 // https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i64.wast#L287-L296 102 {in: 0, expected: 0, fromKind: from32}, 103 {in: 0x7fff, expected: 32767, fromKind: from32}, 104 {in: 0x8000, expected: 32768, fromKind: from32}, 105 {in: 0xffff, expected: 65535, fromKind: from32}, 106 {in: 0x7fffffff, expected: 0x7fffffff, fromKind: from32}, 107 {in: 0x80000000, expected: -0x80000000, fromKind: from32}, 108 {in: 0xffffffff, expected: -1, fromKind: from32}, 109 {in: 0x01234567_00000000, expected: 0, fromKind: from32}, 110 {in: -81985529054232576 /* = 0xfedcba98_80000000 bit pattern */, expected: -0x80000000, fromKind: from32}, 111 {in: -1, expected: -1, fromKind: from32}, 112 } 113 114 for _, tt := range tests { 115 tc := tt 116 t.Run(fmt.Sprintf("0x%x", tc.in), func(t *testing.T) { 117 env := newCompilerEnvironment() 118 compiler := env.requireNewCompiler(t, newCompiler, nil) 119 err := compiler.compilePreamble() 120 require.NoError(t, err) 121 122 // Setup the promote target. 123 err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: uint64(tc.in)}) 124 require.NoError(t, err) 125 126 if tc.fromKind == from8 { 127 err = compiler.compileSignExtend64From8() 128 } else if tc.fromKind == from16 { 129 err = compiler.compileSignExtend64From16() 130 } else { 131 err = compiler.compileSignExtend64From32() 132 } 133 require.NoError(t, err) 134 135 // To verify the behavior, we release the value 136 // to the stack. 137 err = compiler.compileReturnFunction() 138 require.NoError(t, err) 139 140 // Generate and run the code under test. 141 code, _, err := compiler.compile() 142 require.NoError(t, err) 143 env.exec(code) 144 145 require.Equal(t, uint64(1), env.stackPointer()) 146 require.Equal(t, tc.expected, env.stackTopAsInt64()) 147 }) 148 } 149 }) 150 } 151 152 func TestCompiler_compileMemoryCopy(t *testing.T) { 153 const checkCeil = 100 154 tests := []struct { 155 sourceOffset, destOffset, size uint32 156 requireOutOfBoundsError bool 157 }{ 158 {sourceOffset: 0, destOffset: 0, size: 0}, 159 {sourceOffset: 10, destOffset: 5, size: 10}, 160 {sourceOffset: 10, destOffset: 9, size: 1}, 161 {sourceOffset: 10, destOffset: 9, size: 2}, 162 {sourceOffset: 0, destOffset: 10, size: 10}, 163 {sourceOffset: 0, destOffset: 5, size: 10}, 164 {sourceOffset: 9, destOffset: 10, size: 10}, 165 {sourceOffset: 11, destOffset: 13, size: 4}, 166 {sourceOffset: 0, destOffset: 10, size: 5}, 167 {sourceOffset: 1, destOffset: 10, size: 5}, 168 {sourceOffset: 0, destOffset: 10, size: 1}, 169 {sourceOffset: 0, destOffset: 10, size: 0}, 170 {sourceOffset: 5, destOffset: 10, size: 10}, 171 {sourceOffset: 5, destOffset: 10, size: 5}, 172 {sourceOffset: 5, destOffset: 10, size: 1}, 173 {sourceOffset: 5, destOffset: 10, size: 0}, 174 {sourceOffset: 10, destOffset: 0, size: 10}, 175 {sourceOffset: 1, destOffset: 0, size: 2}, 176 {sourceOffset: 1, destOffset: 0, size: 20}, 177 {sourceOffset: 10, destOffset: 0, size: 5}, 178 {sourceOffset: 10, destOffset: 0, size: 1}, 179 {sourceOffset: 10, destOffset: 0, size: 0}, 180 {sourceOffset: 0, destOffset: 50, size: 48}, 181 {sourceOffset: 0, destOffset: 50, size: 49}, 182 {sourceOffset: 10, destOffset: 20, size: 72}, 183 {sourceOffset: 20, destOffset: 10, size: 72}, 184 {sourceOffset: 19, destOffset: 18, size: 79}, 185 {sourceOffset: 20, destOffset: 19, size: 79}, 186 {sourceOffset: defaultMemoryPageNumInTest * wasm.MemoryPageSize, destOffset: 0, size: 1, requireOutOfBoundsError: true}, 187 {sourceOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize + 1, destOffset: 0, size: 0, requireOutOfBoundsError: true}, 188 {sourceOffset: 0, destOffset: defaultMemoryPageNumInTest * wasm.MemoryPageSize, size: 1, requireOutOfBoundsError: true}, 189 {sourceOffset: 0, destOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize + 1, size: 0, requireOutOfBoundsError: true}, 190 {sourceOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize - 99, destOffset: 0, size: 100, requireOutOfBoundsError: true}, 191 {sourceOffset: 0, destOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize - 99, size: 100, requireOutOfBoundsError: true}, 192 } 193 194 for i, tt := range tests { 195 tc := tt 196 t.Run(strconv.Itoa(i), func(t *testing.T) { 197 env := newCompilerEnvironment() 198 compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{HasMemory: true, Signature: &wasm.FunctionType{}}) 199 200 err := compiler.compilePreamble() 201 require.NoError(t, err) 202 203 // Compile operands. 204 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.destOffset}) 205 require.NoError(t, err) 206 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.sourceOffset}) 207 require.NoError(t, err) 208 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.size}) 209 require.NoError(t, err) 210 211 err = compiler.compileMemoryCopy() 212 require.NoError(t, err) 213 214 // Generate the code under test. 215 err = compiler.compileReturnFunction() 216 require.NoError(t, err) 217 code, _, err := compiler.compile() 218 require.NoError(t, err) 219 220 // Setup the source memory region. 221 mem := env.memory() 222 for i := 0; i < checkCeil; i++ { 223 mem[i] = byte(i) 224 } 225 226 // Run code. 227 env.exec(code) 228 229 if !tc.requireOutOfBoundsError { 230 exp := make([]byte, checkCeil) 231 for i := 0; i < checkCeil; i++ { 232 exp[i] = byte(i) 233 } 234 copy(exp[tc.destOffset:], 235 exp[tc.sourceOffset:tc.sourceOffset+tc.size]) 236 237 // Check the status code and the destination memory region. 238 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 239 require.Equal(t, exp, mem[:checkCeil]) 240 } else { 241 require.Equal(t, nativeCallStatusCodeMemoryOutOfBounds, env.compilerStatus()) 242 } 243 }) 244 } 245 } 246 247 func TestCompiler_compileMemoryFill(t *testing.T) { 248 const checkCeil = 50 249 250 tests := []struct { 251 v, destOffset uint32 252 size uint32 253 requireOutOfBoundsError bool 254 }{ 255 {v: 0, destOffset: 0, size: 25}, 256 {v: 0, destOffset: 10, size: 17}, 257 {v: 0, destOffset: 10, size: 15}, 258 {v: 0, destOffset: 10, size: 5}, 259 {v: 0, destOffset: 10, size: 1}, 260 {v: 0, destOffset: 10, size: 0}, 261 {v: 5, destOffset: 10, size: 27}, 262 {v: 5, destOffset: 10, size: 25}, 263 {v: 5, destOffset: 10, size: 21}, 264 {v: 5, destOffset: 10, size: 10}, 265 {v: 5, destOffset: 10, size: 5}, 266 {v: 5, destOffset: 10, size: 1}, 267 {v: 5, destOffset: 10, size: 0}, 268 {v: 10, destOffset: 0, size: 10}, 269 {v: 10, destOffset: 0, size: 5}, 270 {v: 10, destOffset: 0, size: 1}, 271 {v: 10, destOffset: 0, size: 0}, 272 {v: 10, destOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize - 99, size: 100, requireOutOfBoundsError: true}, 273 {v: 10, destOffset: defaultMemoryPageNumInTest * wasm.MemoryPageSize, size: 5, requireOutOfBoundsError: true}, 274 {v: 10, destOffset: defaultMemoryPageNumInTest * wasm.MemoryPageSize, size: 1, requireOutOfBoundsError: true}, 275 {v: 10, destOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize + 1, size: 0, requireOutOfBoundsError: true}, 276 } 277 278 for i, tt := range tests { 279 tc := tt 280 t.Run(strconv.Itoa(i), func(t *testing.T) { 281 env := newCompilerEnvironment() 282 compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{HasMemory: true, Signature: &wasm.FunctionType{}}) 283 284 err := compiler.compilePreamble() 285 require.NoError(t, err) 286 287 // Compile operands. 288 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.destOffset}) 289 require.NoError(t, err) 290 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.v}) 291 require.NoError(t, err) 292 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.size}) 293 require.NoError(t, err) 294 295 err = compiler.compileMemoryFill() 296 require.NoError(t, err) 297 298 // Generate the code under test. 299 err = compiler.compileReturnFunction() 300 require.NoError(t, err) 301 code, _, err := compiler.compile() 302 require.NoError(t, err) 303 304 // Setup the memory region. 305 mem := env.memory() 306 for i := 0; i < checkCeil; i++ { 307 mem[i] = byte(i) 308 } 309 310 // Run code. 311 env.exec(code) 312 313 if !tc.requireOutOfBoundsError { 314 exp := make([]byte, checkCeil) 315 for i := 0; i < checkCeil; i++ { 316 if i >= int(tc.destOffset) && i < int(tc.destOffset+tc.size) { 317 exp[i] = byte(tc.v) 318 } else { 319 exp[i] = byte(i) 320 } 321 } 322 323 // Check the status code and the destination memory region. 324 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 325 require.Equal(t, exp, mem[:checkCeil]) 326 } else { 327 require.Equal(t, nativeCallStatusCodeMemoryOutOfBounds, env.compilerStatus()) 328 } 329 }) 330 } 331 } 332 333 func TestCompiler_compileDataDrop(t *testing.T) { 334 origins := [][]byte{ 335 {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, 336 } 337 338 for i := 0; i < len(origins); i++ { 339 t.Run(strconv.Itoa(i), func(t *testing.T) { 340 env := newCompilerEnvironment() 341 342 env.module().DataInstances = make([][]byte, len(origins)) 343 copy(env.module().DataInstances, origins) 344 345 compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{ 346 HasDataInstances: true, Signature: &wasm.FunctionType{}, 347 }) 348 349 err := compiler.compilePreamble() 350 require.NoError(t, err) 351 352 err = compiler.compileDataDrop(&wazeroir.OperationDataDrop{ 353 DataIndex: uint32(i), 354 }) 355 require.NoError(t, err) 356 357 // Generate the code under test. 358 err = compiler.compileReturnFunction() 359 require.NoError(t, err) 360 code, _, err := compiler.compile() 361 require.NoError(t, err) 362 363 // Run code. 364 env.exec(code) 365 366 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 367 368 // Check if the target data instance is dropped from the dataInstances slice. 369 for j := 0; j < len(origins); j++ { 370 if i == j { 371 require.Nil(t, env.module().DataInstances[j]) 372 } else { 373 require.NotNil(t, env.module().DataInstances[j]) 374 } 375 } 376 }) 377 } 378 } 379 380 func TestCompiler_compileMemoryInit(t *testing.T) { 381 dataInstances := []wasm.DataInstance{ 382 nil, {1, 2, 3, 4, 5}, 383 } 384 385 tests := []struct { 386 sourceOffset, destOffset uint32 387 dataIndex uint32 388 copySize uint32 389 expOutOfBounds bool 390 }{ 391 {sourceOffset: 0, destOffset: 0, copySize: 0, dataIndex: 0}, 392 {sourceOffset: 0, destOffset: 0, copySize: 1, dataIndex: 0, expOutOfBounds: true}, 393 {sourceOffset: 1, destOffset: 0, copySize: 0, dataIndex: 0, expOutOfBounds: true}, 394 {sourceOffset: 0, destOffset: 0, copySize: 0, dataIndex: 1}, 395 {sourceOffset: 0, destOffset: 0, copySize: 5, dataIndex: 1}, 396 {sourceOffset: 0, destOffset: 0, copySize: 1, dataIndex: 1}, 397 {sourceOffset: 0, destOffset: 0, copySize: 3, dataIndex: 1}, 398 {sourceOffset: 0, destOffset: 1, copySize: 3, dataIndex: 1}, 399 {sourceOffset: 0, destOffset: 7, copySize: 4, dataIndex: 1}, 400 {sourceOffset: 1, destOffset: 7, copySize: 4, dataIndex: 1}, 401 {sourceOffset: 4, destOffset: 7, copySize: 1, dataIndex: 1}, 402 {sourceOffset: 5, destOffset: 7, copySize: 0, dataIndex: 1}, 403 {sourceOffset: 0, destOffset: 7, copySize: 5, dataIndex: 1}, 404 {sourceOffset: 1, destOffset: 0, copySize: 3, dataIndex: 1}, 405 {sourceOffset: 0, destOffset: 1, copySize: 4, dataIndex: 1}, 406 {sourceOffset: 1, destOffset: 1, copySize: 3, dataIndex: 1}, 407 {sourceOffset: 0, destOffset: 10, copySize: 5, dataIndex: 1}, 408 {sourceOffset: 0, destOffset: 0, copySize: 6, dataIndex: 1, expOutOfBounds: true}, 409 {sourceOffset: 0, destOffset: defaultMemoryPageNumInTest * wasm.MemoryPageSize, copySize: 5, dataIndex: 1, expOutOfBounds: true}, 410 {sourceOffset: 0, destOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize - 3, copySize: 5, dataIndex: 1, expOutOfBounds: true}, 411 {sourceOffset: 6, destOffset: 0, copySize: 0, dataIndex: 1, expOutOfBounds: true}, 412 } 413 414 for i, tt := range tests { 415 tc := tt 416 t.Run(strconv.Itoa(i), func(t *testing.T) { 417 env := newCompilerEnvironment() 418 env.module().DataInstances = dataInstances 419 420 compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{ 421 HasDataInstances: true, HasMemory: true, 422 Signature: &wasm.FunctionType{}, 423 }) 424 425 err := compiler.compilePreamble() 426 require.NoError(t, err) 427 428 // Compile operands. 429 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.destOffset}) 430 require.NoError(t, err) 431 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.sourceOffset}) 432 require.NoError(t, err) 433 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.copySize}) 434 require.NoError(t, err) 435 436 err = compiler.compileMemoryInit(&wazeroir.OperationMemoryInit{ 437 DataIndex: tc.dataIndex, 438 }) 439 require.NoError(t, err) 440 441 // Generate the code under test. 442 err = compiler.compileReturnFunction() 443 require.NoError(t, err) 444 code, _, err := compiler.compile() 445 require.NoError(t, err) 446 447 // Run code. 448 env.exec(code) 449 450 if !tc.expOutOfBounds { 451 mem := env.memory() 452 exp := make([]byte, defaultMemoryPageNumInTest*wasm.MemoryPageSize) 453 if dataInst := dataInstances[tc.dataIndex]; dataInst != nil { 454 copy(exp[tc.destOffset:], dataInst[tc.sourceOffset:tc.sourceOffset+tc.copySize]) 455 } 456 require.Equal(t, exp[:20], mem[:20]) 457 } else { 458 require.Equal(t, nativeCallStatusCodeMemoryOutOfBounds, env.compilerStatus()) 459 } 460 }) 461 } 462 } 463 464 func TestCompiler_compileElemDrop(t *testing.T) { 465 origins := []wasm.ElementInstance{ 466 {References: []wasm.Reference{1}}, 467 {References: []wasm.Reference{2}}, 468 {References: []wasm.Reference{3}}, 469 {References: []wasm.Reference{4}}, 470 {References: []wasm.Reference{5}}, 471 } 472 473 for i := 0; i < len(origins); i++ { 474 t.Run(strconv.Itoa(i), func(t *testing.T) { 475 env := newCompilerEnvironment() 476 477 insts := make([]wasm.ElementInstance, len(origins)) 478 copy(insts, origins) 479 env.module().ElementInstances = insts 480 481 // Verify assumption that before Drop instruction, all the element instances are not empty. 482 for _, inst := range insts { 483 require.NotEqual(t, 0, len(inst.References)) 484 } 485 486 compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{ 487 HasElementInstances: true, Signature: &wasm.FunctionType{}, 488 }) 489 490 err := compiler.compilePreamble() 491 require.NoError(t, err) 492 493 err = compiler.compileElemDrop(&wazeroir.OperationElemDrop{ 494 ElemIndex: uint32(i), 495 }) 496 require.NoError(t, err) 497 498 // Generate the code under test. 499 err = compiler.compileReturnFunction() 500 require.NoError(t, err) 501 code, _, err := compiler.compile() 502 require.NoError(t, err) 503 504 // Run code. 505 env.exec(code) 506 507 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 508 509 for j := 0; j < len(insts); j++ { 510 if i == j { 511 require.Zero(t, len(env.module().ElementInstances[j].References)) 512 } else { 513 require.NotEqual(t, 0, len(env.module().ElementInstances[j].References)) 514 } 515 } 516 }) 517 } 518 } 519 520 func TestCompiler_compileTableCopy(t *testing.T) { 521 const tableSize = 100 522 tests := []struct { 523 sourceOffset, destOffset, size uint32 524 requireOutOfBoundsError bool 525 }{ 526 {sourceOffset: 0, destOffset: 0, size: 0}, 527 {sourceOffset: 10, destOffset: 5, size: 10}, 528 {sourceOffset: 10, destOffset: 9, size: 1}, 529 {sourceOffset: 10, destOffset: 9, size: 2}, 530 {sourceOffset: 0, destOffset: 10, size: 10}, 531 {sourceOffset: 0, destOffset: 5, size: 10}, 532 {sourceOffset: 9, destOffset: 10, size: 10}, 533 {sourceOffset: 11, destOffset: 13, size: 4}, 534 {sourceOffset: 0, destOffset: 10, size: 5}, 535 {sourceOffset: 1, destOffset: 10, size: 5}, 536 {sourceOffset: 0, destOffset: 10, size: 1}, 537 {sourceOffset: 0, destOffset: 10, size: 0}, 538 {sourceOffset: 5, destOffset: 10, size: 10}, 539 {sourceOffset: 5, destOffset: 10, size: 5}, 540 {sourceOffset: 5, destOffset: 10, size: 1}, 541 {sourceOffset: 5, destOffset: 10, size: 0}, 542 {sourceOffset: 10, destOffset: 0, size: 10}, 543 {sourceOffset: 1, destOffset: 0, size: 2}, 544 {sourceOffset: 1, destOffset: 0, size: 20}, 545 {sourceOffset: 10, destOffset: 0, size: 5}, 546 {sourceOffset: 10, destOffset: 0, size: 1}, 547 {sourceOffset: 10, destOffset: 0, size: 0}, 548 {sourceOffset: tableSize, destOffset: 0, size: 1, requireOutOfBoundsError: true}, 549 {sourceOffset: tableSize + 1, destOffset: 0, size: 0, requireOutOfBoundsError: true}, 550 {sourceOffset: 0, destOffset: tableSize, size: 1, requireOutOfBoundsError: true}, 551 {sourceOffset: 0, destOffset: tableSize + 1, size: 0, requireOutOfBoundsError: true}, 552 {sourceOffset: tableSize - 99, destOffset: 0, size: 100, requireOutOfBoundsError: true}, 553 {sourceOffset: 0, destOffset: tableSize - 99, size: 100, requireOutOfBoundsError: true}, 554 } 555 556 for i, tt := range tests { 557 tc := tt 558 t.Run(strconv.Itoa(i), func(t *testing.T) { 559 env := newCompilerEnvironment() 560 compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{HasTable: true, Signature: &wasm.FunctionType{}}) 561 562 err := compiler.compilePreamble() 563 require.NoError(t, err) 564 565 // Compile operands. 566 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.destOffset}) 567 require.NoError(t, err) 568 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.sourceOffset}) 569 require.NoError(t, err) 570 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.size}) 571 require.NoError(t, err) 572 573 err = compiler.compileTableCopy(&wazeroir.OperationTableCopy{}) 574 require.NoError(t, err) 575 576 // Generate the code under test. 577 err = compiler.compileReturnFunction() 578 require.NoError(t, err) 579 code, _, err := compiler.compile() 580 require.NoError(t, err) 581 582 // Setup the table. 583 table := make([]wasm.Reference, tableSize) 584 env.addTable(&wasm.TableInstance{References: table}) 585 for i := 0; i < tableSize; i++ { 586 table[i] = uintptr(i) 587 } 588 589 // Run code. 590 env.exec(code) 591 592 if !tc.requireOutOfBoundsError { 593 exp := make([]wasm.Reference, tableSize) 594 for i := 0; i < tableSize; i++ { 595 exp[i] = uintptr(i) 596 } 597 copy(exp[tc.destOffset:], 598 exp[tc.sourceOffset:tc.sourceOffset+tc.size]) 599 600 // Check the status code and the destination memory region. 601 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 602 require.Equal(t, exp, table) 603 } else { 604 require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus()) 605 } 606 }) 607 } 608 } 609 610 func TestCompiler_compileTableInit(t *testing.T) { 611 elementInstances := []wasm.ElementInstance{ 612 {}, {References: []wasm.Reference{1, 2, 3, 4, 5}}, 613 } 614 615 const tableSize = 100 616 tests := []struct { 617 sourceOffset, destOffset uint32 618 elemIndex uint32 619 copySize uint32 620 expOutOfBounds bool 621 }{ 622 {sourceOffset: 0, destOffset: 0, copySize: 0, elemIndex: 0}, 623 {sourceOffset: 0, destOffset: 0, copySize: 1, elemIndex: 0, expOutOfBounds: true}, 624 {sourceOffset: 1, destOffset: 0, copySize: 0, elemIndex: 0, expOutOfBounds: true}, 625 {sourceOffset: 0, destOffset: 0, copySize: 0, elemIndex: 1}, 626 {sourceOffset: 0, destOffset: 0, copySize: 5, elemIndex: 1}, 627 {sourceOffset: 0, destOffset: 0, copySize: 1, elemIndex: 1}, 628 {sourceOffset: 0, destOffset: 0, copySize: 3, elemIndex: 1}, 629 {sourceOffset: 0, destOffset: 1, copySize: 3, elemIndex: 1}, 630 {sourceOffset: 0, destOffset: 7, copySize: 4, elemIndex: 1}, 631 {sourceOffset: 1, destOffset: 7, copySize: 4, elemIndex: 1}, 632 {sourceOffset: 4, destOffset: 7, copySize: 1, elemIndex: 1}, 633 {sourceOffset: 5, destOffset: 7, copySize: 0, elemIndex: 1}, 634 {sourceOffset: 0, destOffset: 7, copySize: 5, elemIndex: 1}, 635 {sourceOffset: 1, destOffset: 0, copySize: 3, elemIndex: 1}, 636 {sourceOffset: 0, destOffset: 1, copySize: 4, elemIndex: 1}, 637 {sourceOffset: 1, destOffset: 1, copySize: 3, elemIndex: 1}, 638 {sourceOffset: 0, destOffset: 10, copySize: 5, elemIndex: 1}, 639 {sourceOffset: 0, destOffset: 0, copySize: 6, elemIndex: 1, expOutOfBounds: true}, 640 {sourceOffset: 6, destOffset: 0, copySize: 0, elemIndex: 1, expOutOfBounds: true}, 641 } 642 643 for i, tt := range tests { 644 tc := tt 645 t.Run(strconv.Itoa(i), func(t *testing.T) { 646 env := newCompilerEnvironment() 647 env.module().ElementInstances = elementInstances 648 649 compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{ 650 HasElementInstances: true, HasTable: true, 651 Signature: &wasm.FunctionType{}, 652 }) 653 654 err := compiler.compilePreamble() 655 require.NoError(t, err) 656 657 // Compile operands. 658 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.destOffset}) 659 require.NoError(t, err) 660 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.sourceOffset}) 661 require.NoError(t, err) 662 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.copySize}) 663 require.NoError(t, err) 664 665 err = compiler.compileTableInit(&wazeroir.OperationTableInit{ 666 ElemIndex: tc.elemIndex, 667 }) 668 require.NoError(t, err) 669 670 // Setup the table. 671 table := make([]wasm.Reference, tableSize) 672 env.addTable(&wasm.TableInstance{References: table}) 673 for i := 0; i < tableSize; i++ { 674 table[i] = uintptr(i) 675 } 676 677 // Generate the code under test. 678 err = compiler.compileReturnFunction() 679 require.NoError(t, err) 680 code, _, err := compiler.compile() 681 require.NoError(t, err) 682 683 // Run code. 684 env.exec(code) 685 686 if !tc.expOutOfBounds { 687 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 688 exp := make([]wasm.Reference, tableSize) 689 for i := 0; i < tableSize; i++ { 690 exp[i] = uintptr(i) 691 } 692 if inst := elementInstances[tc.elemIndex]; inst.References != nil { 693 copy(exp[tc.destOffset:], inst.References[tc.sourceOffset:tc.sourceOffset+tc.copySize]) 694 } 695 require.Equal(t, exp, table) 696 } else { 697 require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus()) 698 } 699 }) 700 } 701 } 702 703 type dog struct{ name string } 704 705 func TestCompiler_compileTableSet(t *testing.T) { 706 externDog := &dog{name: "sushi"} 707 externrefOpaque := uintptr(unsafe.Pointer(externDog)) 708 funcref := &function{source: &wasm.FunctionInstance{}} 709 funcrefOpaque := uintptr(unsafe.Pointer(funcref)) 710 711 externTable := &wasm.TableInstance{Type: wasm.RefTypeExternref, References: []wasm.Reference{0, 0, externrefOpaque, 0, 0}} 712 funcrefTable := &wasm.TableInstance{Type: wasm.RefTypeFuncref, References: []wasm.Reference{0, 0, 0, 0, funcrefOpaque}} 713 tables := []*wasm.TableInstance{externTable, funcrefTable} 714 715 tests := []struct { 716 name string 717 tableIndex uint32 718 offset uint32 719 in uintptr 720 expExtern bool 721 expError bool 722 }{ 723 { 724 name: "externref - non nil", 725 tableIndex: 0, 726 offset: 2, 727 in: externrefOpaque, 728 expExtern: true, 729 }, 730 { 731 name: "externref - nil", 732 tableIndex: 0, 733 offset: 1, 734 in: 0, 735 expExtern: true, 736 }, 737 { 738 name: "externref - out of bounds", 739 tableIndex: 0, 740 offset: 10, 741 in: 0, 742 expError: true, 743 }, 744 { 745 name: "funcref - non nil", 746 tableIndex: 1, 747 offset: 4, 748 in: funcrefOpaque, 749 expExtern: false, 750 }, 751 { 752 name: "funcref - nil", 753 tableIndex: 1, 754 offset: 3, 755 in: 0, 756 expExtern: false, 757 }, 758 { 759 name: "funcref - out of bounds", 760 tableIndex: 1, 761 offset: 100000, 762 in: 0, 763 expError: true, 764 }, 765 } 766 767 for _, tt := range tests { 768 tc := tt 769 t.Run(tc.name, func(t *testing.T) { 770 env := newCompilerEnvironment() 771 772 for _, table := range tables { 773 env.addTable(table) 774 } 775 776 compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{ 777 HasTable: true, 778 Signature: &wasm.FunctionType{}, 779 }) 780 781 err := compiler.compilePreamble() 782 require.NoError(t, err) 783 784 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.offset}) 785 require.NoError(t, err) 786 787 err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: uint64(tc.in)}) 788 require.NoError(t, err) 789 790 err = compiler.compileTableSet(&wazeroir.OperationTableSet{TableIndex: tc.tableIndex}) 791 require.NoError(t, err) 792 793 // Generate the code under test. 794 err = compiler.compileReturnFunction() 795 require.NoError(t, err) 796 code, _, err := compiler.compile() 797 require.NoError(t, err) 798 799 // Run code. 800 env.exec(code) 801 802 if tc.expError { 803 require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus()) 804 } else { 805 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 806 require.Equal(t, uint64(0), env.stackPointer()) 807 808 if tc.expExtern { 809 actual := dogFromPtr(externTable.References[tc.offset]) 810 exp := externDog 811 if tc.in == 0 { 812 exp = nil 813 } 814 require.Equal(t, exp, actual) 815 } else { 816 actual := functionFromPtr(funcrefTable.References[tc.offset]) 817 exp := funcref 818 if tc.in == 0 { 819 exp = nil 820 } 821 require.Equal(t, exp, actual) 822 } 823 } 824 }) 825 } 826 } 827 828 //go:nocheckptr ignore "pointer arithmetic result points to invalid allocation" 829 func dogFromPtr(ptr uintptr) *dog { 830 if ptr == 0 { 831 return nil 832 } 833 return (*dog)(unsafe.Pointer(ptr)) 834 } 835 836 //go:nocheckptr ignore "pointer arithmetic result points to invalid allocation" 837 func functionFromPtr(ptr uintptr) *function { 838 if ptr == 0 { 839 return nil 840 } 841 return (*function)(unsafe.Pointer(ptr)) 842 } 843 844 func TestCompiler_compileTableGet(t *testing.T) { 845 externDog := &dog{name: "sushi"} 846 externrefOpaque := uintptr(unsafe.Pointer(externDog)) 847 funcref := &function{source: &wasm.FunctionInstance{}} 848 funcrefOpaque := uintptr(unsafe.Pointer(funcref)) 849 tables := []*wasm.TableInstance{ 850 {Type: wasm.RefTypeExternref, References: []wasm.Reference{0, 0, externrefOpaque, 0, 0}}, 851 {Type: wasm.RefTypeFuncref, References: []wasm.Reference{0, 0, 0, 0, funcrefOpaque}}, 852 } 853 854 tests := []struct { 855 name string 856 tableIndex uint32 857 offset uint32 858 exp uintptr 859 expError bool 860 }{ 861 { 862 name: "externref - non nil", 863 tableIndex: 0, 864 offset: 2, 865 exp: externrefOpaque, 866 }, 867 { 868 name: "externref - nil", 869 tableIndex: 0, 870 offset: 4, 871 exp: 0, 872 }, 873 { 874 name: "externref - out of bounds", 875 tableIndex: 0, 876 offset: 5, 877 expError: true, 878 }, 879 { 880 name: "funcref - non nil", 881 tableIndex: 1, 882 offset: 4, 883 exp: funcrefOpaque, 884 }, 885 { 886 name: "funcref - nil", 887 tableIndex: 1, 888 offset: 1, 889 exp: 0, 890 }, 891 { 892 name: "funcref - out of bounds", 893 tableIndex: 1, 894 offset: 1000, 895 expError: true, 896 }, 897 } 898 899 for _, tt := range tests { 900 tc := tt 901 t.Run(tc.name, func(t *testing.T) { 902 env := newCompilerEnvironment() 903 904 for _, table := range tables { 905 env.addTable(table) 906 } 907 908 compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{ 909 HasTable: true, 910 Signature: &wasm.FunctionType{}, 911 }) 912 913 err := compiler.compilePreamble() 914 require.NoError(t, err) 915 916 err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.offset}) 917 require.NoError(t, err) 918 919 err = compiler.compileTableGet(&wazeroir.OperationTableGet{TableIndex: tc.tableIndex}) 920 require.NoError(t, err) 921 922 // Generate the code under test. 923 err = compiler.compileReturnFunction() 924 require.NoError(t, err) 925 code, _, err := compiler.compile() 926 require.NoError(t, err) 927 928 // Run code. 929 env.exec(code) 930 931 if tc.expError { 932 require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus()) 933 } else { 934 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 935 require.Equal(t, uint64(1), env.stackPointer()) 936 require.Equal(t, uint64(tc.exp), env.stackTopAsUint64()) 937 } 938 }) 939 } 940 } 941 942 func TestCompiler_compileRefFunc(t *testing.T) { 943 env := newCompilerEnvironment() 944 compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{Signature: &wasm.FunctionType{}}) 945 946 err := compiler.compilePreamble() 947 require.NoError(t, err) 948 949 me := env.moduleEngine() 950 const numFuncs = 20 951 for i := 0; i < numFuncs; i++ { 952 me.functions = append(me.functions, &function{source: &wasm.FunctionInstance{}}) 953 } 954 955 for i := 0; i < numFuncs; i++ { 956 i := i 957 t.Run(strconv.Itoa(i), func(t *testing.T) { 958 compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{Signature: &wasm.FunctionType{}}) 959 960 err := compiler.compilePreamble() 961 require.NoError(t, err) 962 963 err = compiler.compileRefFunc(&wazeroir.OperationRefFunc{FunctionIndex: uint32(i)}) 964 require.NoError(t, err) 965 966 // Generate the code under test. 967 err = compiler.compileReturnFunction() 968 require.NoError(t, err) 969 code, _, err := compiler.compile() 970 require.NoError(t, err) 971 972 // Run code. 973 env.exec(code) 974 975 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 976 require.Equal(t, uint64(1), env.stackPointer()) 977 require.Equal(t, uintptr(unsafe.Pointer(me.functions[i])), uintptr(env.stackTopAsUint64())) 978 }) 979 } 980 }