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