github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/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/wasilibs/wazerox/internal/asm" 10 "github.com/wasilibs/wazerox/internal/testing/require" 11 "github.com/wasilibs/wazerox/internal/wasm" 12 "github.com/wasilibs/wazerox/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{{1}, {2}, {3}, {4}, {5}} 480 481 for i := 0; i < len(origins); i++ { 482 t.Run(strconv.Itoa(i), func(t *testing.T) { 483 env := newCompilerEnvironment() 484 485 insts := make([]wasm.ElementInstance, len(origins)) 486 copy(insts, origins) 487 env.module().ElementInstances = insts 488 489 // Verify assumption that before Drop instruction, all the element instances are not empty. 490 for _, inst := range insts { 491 require.NotEqual(t, 0, len(inst)) 492 } 493 494 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{ 495 HasElementInstances: true, 496 }) 497 498 err := compiler.compilePreamble() 499 require.NoError(t, err) 500 501 err = compiler.compileElemDrop(operationPtr(wazeroir.NewOperationElemDrop(uint32(i)))) 502 require.NoError(t, err) 503 504 code := asm.CodeSegment{} 505 defer func() { require.NoError(t, code.Unmap()) }() 506 507 // Generate the code under test. 508 err = compiler.compileReturnFunction() 509 require.NoError(t, err) 510 _, err = compiler.compile(code.NextCodeSection()) 511 require.NoError(t, err) 512 513 // Run code. 514 env.exec(code.Bytes()) 515 516 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 517 518 for j := 0; j < len(insts); j++ { 519 if i == j { 520 require.Zero(t, len(env.module().ElementInstances[j])) 521 } else { 522 require.NotEqual(t, 0, len(env.module().ElementInstances[j])) 523 } 524 } 525 }) 526 } 527 } 528 529 func TestCompiler_compileTableCopy(t *testing.T) { 530 const tableSize = 100 531 tests := []struct { 532 sourceOffset, destOffset, size uint32 533 requireOutOfBoundsError bool 534 }{ 535 {sourceOffset: 0, destOffset: 0, size: 0}, 536 {sourceOffset: 10, destOffset: 5, size: 10}, 537 {sourceOffset: 10, destOffset: 9, size: 1}, 538 {sourceOffset: 10, destOffset: 9, size: 2}, 539 {sourceOffset: 0, destOffset: 10, size: 10}, 540 {sourceOffset: 0, destOffset: 5, size: 10}, 541 {sourceOffset: 9, destOffset: 10, size: 10}, 542 {sourceOffset: 11, destOffset: 13, size: 4}, 543 {sourceOffset: 0, destOffset: 10, size: 5}, 544 {sourceOffset: 1, destOffset: 10, size: 5}, 545 {sourceOffset: 0, destOffset: 10, size: 1}, 546 {sourceOffset: 0, destOffset: 10, size: 0}, 547 {sourceOffset: 5, destOffset: 10, size: 10}, 548 {sourceOffset: 5, destOffset: 10, size: 5}, 549 {sourceOffset: 5, destOffset: 10, size: 1}, 550 {sourceOffset: 5, destOffset: 10, size: 0}, 551 {sourceOffset: 10, destOffset: 0, size: 10}, 552 {sourceOffset: 1, destOffset: 0, size: 2}, 553 {sourceOffset: 1, destOffset: 0, size: 20}, 554 {sourceOffset: 10, destOffset: 0, size: 5}, 555 {sourceOffset: 10, destOffset: 0, size: 1}, 556 {sourceOffset: 10, destOffset: 0, size: 0}, 557 {sourceOffset: tableSize, destOffset: 0, size: 1, requireOutOfBoundsError: true}, 558 {sourceOffset: tableSize + 1, destOffset: 0, size: 0, requireOutOfBoundsError: true}, 559 {sourceOffset: 0, destOffset: tableSize, size: 1, requireOutOfBoundsError: true}, 560 {sourceOffset: 0, destOffset: tableSize + 1, size: 0, requireOutOfBoundsError: true}, 561 {sourceOffset: tableSize - 99, destOffset: 0, size: 100, requireOutOfBoundsError: true}, 562 {sourceOffset: 0, destOffset: tableSize - 99, size: 100, requireOutOfBoundsError: true}, 563 } 564 565 for i, tt := range tests { 566 tc := tt 567 t.Run(strconv.Itoa(i), func(t *testing.T) { 568 env := newCompilerEnvironment() 569 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{HasTable: true}) 570 571 err := compiler.compilePreamble() 572 require.NoError(t, err) 573 574 // Compile operands. 575 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.destOffset))) 576 require.NoError(t, err) 577 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.sourceOffset))) 578 require.NoError(t, err) 579 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.size))) 580 require.NoError(t, err) 581 582 err = compiler.compileTableCopy(operationPtr(wazeroir.NewOperationTableCopy(0, 0))) 583 require.NoError(t, err) 584 585 code := asm.CodeSegment{} 586 defer func() { require.NoError(t, code.Unmap()) }() 587 588 // Generate the code under test. 589 err = compiler.compileReturnFunction() 590 require.NoError(t, err) 591 _, err = compiler.compile(code.NextCodeSection()) 592 require.NoError(t, err) 593 594 // Setup the table. 595 table := make([]wasm.Reference, tableSize) 596 env.addTable(&wasm.TableInstance{References: table}) 597 for i := 0; i < tableSize; i++ { 598 table[i] = uintptr(i) 599 } 600 601 // Run code. 602 env.exec(code.Bytes()) 603 604 if !tc.requireOutOfBoundsError { 605 exp := make([]wasm.Reference, tableSize) 606 for i := 0; i < tableSize; i++ { 607 exp[i] = uintptr(i) 608 } 609 copy(exp[tc.destOffset:], 610 exp[tc.sourceOffset:tc.sourceOffset+tc.size]) 611 612 // Check the status code and the destination memory region. 613 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 614 require.Equal(t, exp, table) 615 } else { 616 require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus()) 617 } 618 }) 619 } 620 } 621 622 func TestCompiler_compileTableInit(t *testing.T) { 623 elementInstances := []wasm.ElementInstance{ 624 {}, {1, 2, 3, 4, 5}, 625 } 626 627 const tableSize = 100 628 tests := []struct { 629 sourceOffset, destOffset uint32 630 elemIndex uint32 631 copySize uint32 632 expOutOfBounds bool 633 }{ 634 {sourceOffset: 0, destOffset: 0, copySize: 0, elemIndex: 0}, 635 {sourceOffset: 0, destOffset: 0, copySize: 1, elemIndex: 0, expOutOfBounds: true}, 636 {sourceOffset: 1, destOffset: 0, copySize: 0, elemIndex: 0, expOutOfBounds: true}, 637 {sourceOffset: 0, destOffset: 0, copySize: 0, elemIndex: 1}, 638 {sourceOffset: 0, destOffset: 0, copySize: 5, elemIndex: 1}, 639 {sourceOffset: 0, destOffset: 0, copySize: 1, elemIndex: 1}, 640 {sourceOffset: 0, destOffset: 0, copySize: 3, elemIndex: 1}, 641 {sourceOffset: 0, destOffset: 1, copySize: 3, elemIndex: 1}, 642 {sourceOffset: 0, destOffset: 7, copySize: 4, elemIndex: 1}, 643 {sourceOffset: 1, destOffset: 7, copySize: 4, elemIndex: 1}, 644 {sourceOffset: 4, destOffset: 7, copySize: 1, elemIndex: 1}, 645 {sourceOffset: 5, destOffset: 7, copySize: 0, elemIndex: 1}, 646 {sourceOffset: 0, destOffset: 7, copySize: 5, elemIndex: 1}, 647 {sourceOffset: 1, destOffset: 0, copySize: 3, elemIndex: 1}, 648 {sourceOffset: 0, destOffset: 1, copySize: 4, elemIndex: 1}, 649 {sourceOffset: 1, destOffset: 1, copySize: 3, elemIndex: 1}, 650 {sourceOffset: 0, destOffset: 10, copySize: 5, elemIndex: 1}, 651 {sourceOffset: 0, destOffset: 0, copySize: 6, elemIndex: 1, expOutOfBounds: true}, 652 {sourceOffset: 6, destOffset: 0, copySize: 0, elemIndex: 1, expOutOfBounds: true}, 653 } 654 655 for i, tt := range tests { 656 tc := tt 657 t.Run(strconv.Itoa(i), func(t *testing.T) { 658 env := newCompilerEnvironment() 659 env.module().ElementInstances = elementInstances 660 661 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{ 662 HasElementInstances: true, HasTable: true, 663 }) 664 665 err := compiler.compilePreamble() 666 require.NoError(t, err) 667 668 // Compile operands. 669 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.destOffset))) 670 require.NoError(t, err) 671 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.sourceOffset))) 672 require.NoError(t, err) 673 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.copySize))) 674 require.NoError(t, err) 675 676 err = compiler.compileTableInit(operationPtr(wazeroir.NewOperationTableInit(tc.elemIndex, 0))) 677 require.NoError(t, err) 678 679 // Setup the table. 680 table := make([]wasm.Reference, tableSize) 681 env.addTable(&wasm.TableInstance{References: table}) 682 for i := 0; i < tableSize; i++ { 683 table[i] = uintptr(i) 684 } 685 686 code := asm.CodeSegment{} 687 defer func() { require.NoError(t, code.Unmap()) }() 688 689 // Generate the code under test. 690 err = compiler.compileReturnFunction() 691 require.NoError(t, err) 692 _, err = compiler.compile(code.NextCodeSection()) 693 require.NoError(t, err) 694 695 // Run code. 696 env.exec(code.Bytes()) 697 698 if !tc.expOutOfBounds { 699 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 700 exp := make([]wasm.Reference, tableSize) 701 for i := 0; i < tableSize; i++ { 702 exp[i] = uintptr(i) 703 } 704 if inst := elementInstances[tc.elemIndex]; inst != nil { 705 copy(exp[tc.destOffset:], inst[tc.sourceOffset:tc.sourceOffset+tc.copySize]) 706 } 707 require.Equal(t, exp, table) 708 } else { 709 require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus()) 710 } 711 }) 712 } 713 } 714 715 type dog struct{ name string } 716 717 func TestCompiler_compileTableSet(t *testing.T) { 718 externDog := &dog{name: "sushi"} 719 externrefOpaque := uintptr(unsafe.Pointer(externDog)) 720 funcref := &function{moduleInstance: &wasm.ModuleInstance{}} 721 funcrefOpaque := uintptr(unsafe.Pointer(funcref)) 722 723 externTable := &wasm.TableInstance{Type: wasm.RefTypeExternref, References: []wasm.Reference{0, 0, externrefOpaque, 0, 0}} 724 funcrefTable := &wasm.TableInstance{Type: wasm.RefTypeFuncref, References: []wasm.Reference{0, 0, 0, 0, funcrefOpaque}} 725 tables := []*wasm.TableInstance{externTable, funcrefTable} 726 727 tests := []struct { 728 name string 729 tableIndex uint32 730 offset uint32 731 in uintptr 732 expExtern bool 733 expError bool 734 }{ 735 { 736 name: "externref - non nil", 737 tableIndex: 0, 738 offset: 2, 739 in: externrefOpaque, 740 expExtern: true, 741 }, 742 { 743 name: "externref - nil", 744 tableIndex: 0, 745 offset: 1, 746 in: 0, 747 expExtern: true, 748 }, 749 { 750 name: "externref - out of bounds", 751 tableIndex: 0, 752 offset: 10, 753 in: 0, 754 expError: true, 755 }, 756 { 757 name: "funcref - non nil", 758 tableIndex: 1, 759 offset: 4, 760 in: funcrefOpaque, 761 expExtern: false, 762 }, 763 { 764 name: "funcref - nil", 765 tableIndex: 1, 766 offset: 3, 767 in: 0, 768 expExtern: false, 769 }, 770 { 771 name: "funcref - out of bounds", 772 tableIndex: 1, 773 offset: 100000, 774 in: 0, 775 expError: true, 776 }, 777 } 778 779 for _, tt := range tests { 780 tc := tt 781 t.Run(tc.name, func(t *testing.T) { 782 env := newCompilerEnvironment() 783 784 for _, table := range tables { 785 env.addTable(table) 786 } 787 788 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{ 789 HasTable: true, 790 }) 791 792 err := compiler.compilePreamble() 793 require.NoError(t, err) 794 795 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.offset))) 796 require.NoError(t, err) 797 798 err = compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(uint64(tc.in)))) 799 require.NoError(t, err) 800 801 err = compiler.compileTableSet(operationPtr(wazeroir.NewOperationTableSet(tc.tableIndex))) 802 require.NoError(t, err) 803 804 code := asm.CodeSegment{} 805 defer func() { require.NoError(t, code.Unmap()) }() 806 807 // Generate the code under test. 808 err = compiler.compileReturnFunction() 809 require.NoError(t, err) 810 _, err = compiler.compile(code.NextCodeSection()) 811 require.NoError(t, err) 812 813 // Run code. 814 env.exec(code.Bytes()) 815 816 if tc.expError { 817 require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus()) 818 } else { 819 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 820 require.Equal(t, uint64(0), env.stackPointer()) 821 822 if tc.expExtern { 823 actual := dogFromPtr(externTable.References[tc.offset]) 824 exp := externDog 825 if tc.in == 0 { 826 exp = nil 827 } 828 require.Equal(t, exp, actual) 829 } else { 830 actual := functionFromPtr(funcrefTable.References[tc.offset]) 831 exp := funcref 832 if tc.in == 0 { 833 exp = nil 834 } 835 require.Equal(t, exp, actual) 836 } 837 } 838 }) 839 } 840 } 841 842 //go:nocheckptr ignore "pointer arithmetic result points to invalid allocation" 843 func dogFromPtr(ptr uintptr) *dog { 844 if ptr == 0 { 845 return nil 846 } 847 return (*dog)(unsafe.Pointer(ptr)) 848 } 849 850 //go:nocheckptr ignore "pointer arithmetic result points to invalid allocation" 851 func functionFromPtr(ptr uintptr) *function { 852 if ptr == 0 { 853 return nil 854 } 855 return (*function)(unsafe.Pointer(ptr)) 856 } 857 858 func TestCompiler_compileTableGet(t *testing.T) { 859 externDog := &dog{name: "sushi"} 860 externrefOpaque := uintptr(unsafe.Pointer(externDog)) 861 funcref := &function{moduleInstance: &wasm.ModuleInstance{}} 862 funcrefOpaque := uintptr(unsafe.Pointer(funcref)) 863 tables := []*wasm.TableInstance{ 864 {Type: wasm.RefTypeExternref, References: []wasm.Reference{0, 0, externrefOpaque, 0, 0}}, 865 {Type: wasm.RefTypeFuncref, References: []wasm.Reference{0, 0, 0, 0, funcrefOpaque}}, 866 } 867 868 tests := []struct { 869 name string 870 tableIndex uint32 871 offset uint32 872 exp uintptr 873 expError bool 874 }{ 875 { 876 name: "externref - non nil", 877 tableIndex: 0, 878 offset: 2, 879 exp: externrefOpaque, 880 }, 881 { 882 name: "externref - nil", 883 tableIndex: 0, 884 offset: 4, 885 exp: 0, 886 }, 887 { 888 name: "externref - out of bounds", 889 tableIndex: 0, 890 offset: 5, 891 expError: true, 892 }, 893 { 894 name: "funcref - non nil", 895 tableIndex: 1, 896 offset: 4, 897 exp: funcrefOpaque, 898 }, 899 { 900 name: "funcref - nil", 901 tableIndex: 1, 902 offset: 1, 903 exp: 0, 904 }, 905 { 906 name: "funcref - out of bounds", 907 tableIndex: 1, 908 offset: 1000, 909 expError: true, 910 }, 911 } 912 913 for _, tt := range tests { 914 tc := tt 915 t.Run(tc.name, func(t *testing.T) { 916 env := newCompilerEnvironment() 917 918 for _, table := range tables { 919 env.addTable(table) 920 } 921 922 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{ 923 HasTable: true, 924 }) 925 926 err := compiler.compilePreamble() 927 require.NoError(t, err) 928 929 err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.offset))) 930 require.NoError(t, err) 931 932 err = compiler.compileTableGet(operationPtr(wazeroir.NewOperationTableGet(tc.tableIndex))) 933 require.NoError(t, err) 934 935 code := asm.CodeSegment{} 936 defer func() { require.NoError(t, code.Unmap()) }() 937 938 // Generate the code under test. 939 err = compiler.compileReturnFunction() 940 require.NoError(t, err) 941 _, err = compiler.compile(code.NextCodeSection()) 942 require.NoError(t, err) 943 944 // Run code. 945 env.exec(code.Bytes()) 946 947 if tc.expError { 948 require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus()) 949 } else { 950 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 951 require.Equal(t, uint64(1), env.stackPointer()) 952 require.Equal(t, uint64(tc.exp), env.stackTopAsUint64()) 953 } 954 }) 955 } 956 } 957 958 func TestCompiler_compileRefFunc(t *testing.T) { 959 env := newCompilerEnvironment() 960 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{}) 961 962 err := compiler.compilePreamble() 963 require.NoError(t, err) 964 965 me := env.moduleEngine() 966 const numFuncs = 20 967 for i := 0; i < numFuncs; i++ { 968 me.functions = append(me.functions, function{moduleInstance: &wasm.ModuleInstance{}}) 969 } 970 971 for i := 0; i < numFuncs; i++ { 972 i := i 973 t.Run(strconv.Itoa(i), func(t *testing.T) { 974 compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{}) 975 976 err := compiler.compilePreamble() 977 require.NoError(t, err) 978 979 err = compiler.compileRefFunc(operationPtr(wazeroir.NewOperationRefFunc(uint32(i)))) 980 require.NoError(t, err) 981 982 code := asm.CodeSegment{} 983 defer func() { require.NoError(t, code.Unmap()) }() 984 985 // Generate the code under test. 986 err = compiler.compileReturnFunction() 987 require.NoError(t, err) 988 _, err = compiler.compile(code.NextCodeSection()) 989 require.NoError(t, err) 990 991 // Run code. 992 env.exec(code.Bytes()) 993 994 require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus()) 995 require.Equal(t, uint64(1), env.stackPointer()) 996 require.Equal(t, uintptr(unsafe.Pointer(&me.functions[i])), uintptr(env.stackTopAsUint64())) 997 }) 998 } 999 }