github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/wasm/memory_test.go (about) 1 package wasm 2 3 import ( 4 "math" 5 "reflect" 6 "strings" 7 "testing" 8 "time" 9 "unsafe" 10 11 "github.com/tetratelabs/wazero/api" 12 "github.com/tetratelabs/wazero/experimental" 13 "github.com/tetratelabs/wazero/internal/testing/require" 14 ) 15 16 func TestMemoryPageConsts(t *testing.T) { 17 require.Equal(t, MemoryPageSize, uint32(1)<<MemoryPageSizeInBits) 18 require.Equal(t, MemoryPageSize, uint32(1<<16)) 19 require.Equal(t, MemoryLimitPages, uint32(1<<16)) 20 } 21 22 func TestMemoryPagesToBytesNum(t *testing.T) { 23 for _, numPage := range []uint32{0, 1, 5, 10} { 24 require.Equal(t, uint64(numPage*MemoryPageSize), MemoryPagesToBytesNum(numPage)) 25 } 26 } 27 28 func TestMemoryBytesNumToPages(t *testing.T) { 29 for _, numbytes := range []uint32{0, MemoryPageSize * 1, MemoryPageSize * 10} { 30 require.Equal(t, numbytes/MemoryPageSize, memoryBytesNumToPages(uint64(numbytes))) 31 } 32 } 33 34 func TestMemoryInstance_Grow_Size(t *testing.T) { 35 tests := []struct { 36 name string 37 capEqualsMax bool 38 expAllocator bool 39 }{ 40 {name: ""}, 41 {name: "capEqualsMax", capEqualsMax: true}, 42 {name: "expAllocator", expAllocator: true}, 43 } 44 45 for _, tt := range tests { 46 tc := tt 47 48 t.Run(tc.name, func(t *testing.T) { 49 max := uint32(10) 50 maxBytes := MemoryPagesToBytesNum(max) 51 var m *MemoryInstance 52 switch { 53 default: 54 m = &MemoryInstance{Max: max, Buffer: make([]byte, 0)} 55 case tc.capEqualsMax: 56 m = &MemoryInstance{Cap: max, Max: max, Buffer: make([]byte, 0, maxBytes)} 57 case tc.expAllocator: 58 expBuffer := sliceAllocator(0, maxBytes) 59 m = &MemoryInstance{Max: max, Buffer: expBuffer.Reallocate(0), expBuffer: expBuffer} 60 } 61 62 res, ok := m.Grow(5) 63 require.True(t, ok) 64 require.Equal(t, uint32(0), res) 65 require.Equal(t, uint32(5), m.Pages()) 66 67 // Zero page grow is well-defined, should return the current page correctly. 68 res, ok = m.Grow(0) 69 require.True(t, ok) 70 require.Equal(t, uint32(5), res) 71 require.Equal(t, uint32(5), m.Pages()) 72 73 res, ok = m.Grow(4) 74 require.True(t, ok) 75 require.Equal(t, uint32(5), res) 76 require.Equal(t, uint32(9), m.Pages()) 77 78 res, ok = m.Grow(0) 79 require.True(t, ok) 80 require.Equal(t, uint32(9), res) 81 require.Equal(t, uint32(9), m.Pages()) 82 83 // At this point, the page size equal 9, 84 // so trying to grow two pages should result in failure. 85 _, ok = m.Grow(2) 86 require.False(t, ok) 87 require.Equal(t, uint32(9), m.Pages()) 88 89 // But growing one page is still permitted. 90 res, ok = m.Grow(1) 91 require.True(t, ok) 92 require.Equal(t, uint32(9), res) 93 94 // Ensure that the current page size equals the max. 95 require.Equal(t, max, m.Pages()) 96 97 if tc.capEqualsMax { // Ensure the capacity isn't more than max. 98 require.Equal(t, maxBytes, uint64(cap(m.Buffer))) 99 } else { // Slice doubles, so it should have a higher capacity than max. 100 require.True(t, maxBytes < uint64(cap(m.Buffer))) 101 } 102 }) 103 } 104 } 105 106 func TestMemoryInstance_NegativeDelta(t *testing.T) { 107 m := &MemoryInstance{Buffer: make([]byte, 2*MemoryPageSize)} 108 _negative := -1 109 negativeu32 := uint32(_negative) 110 _, ok := m.Grow(negativeu32) 111 // If the negative page size is given, current_page+delta might overflow, and it can result in accidentally shrinking the memory, 112 // which is obviously not spec compliant. 113 require.False(t, ok) 114 } 115 116 func TestMemoryInstance_ReadByte(t *testing.T) { 117 mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 0, 0, 0, 16}, Min: 1} 118 v, ok := mem.ReadByte(7) 119 require.True(t, ok) 120 require.Equal(t, byte(16), v) 121 122 _, ok = mem.ReadByte(8) 123 require.False(t, ok) 124 125 _, ok = mem.ReadByte(9) 126 require.False(t, ok) 127 } 128 129 func TestPagesToUnitOfBytes(t *testing.T) { 130 tests := []struct { 131 name string 132 pages uint32 133 expected string 134 }{ 135 { 136 name: "zero", 137 pages: 0, 138 expected: "0 Ki", 139 }, 140 { 141 name: "one", 142 pages: 1, 143 expected: "64 Ki", 144 }, 145 { 146 name: "megs", 147 pages: 100, 148 expected: "6 Mi", 149 }, 150 { 151 name: "max memory", 152 pages: MemoryLimitPages, 153 expected: "4 Gi", 154 }, 155 { 156 name: "max uint32", 157 pages: math.MaxUint32, 158 expected: "3 Ti", 159 }, 160 } 161 162 for _, tt := range tests { 163 tc := tt 164 165 t.Run(tc.name, func(t *testing.T) { 166 require.Equal(t, tc.expected, PagesToUnitOfBytes(tc.pages)) 167 }) 168 } 169 } 170 171 func TestMemoryInstance_HasSize(t *testing.T) { 172 memory := &MemoryInstance{Buffer: make([]byte, MemoryPageSize)} 173 174 tests := []struct { 175 name string 176 offset uint32 177 sizeInBytes uint64 178 expected bool 179 }{ 180 { 181 name: "simple valid arguments", 182 offset: 0, // arbitrary valid offset 183 sizeInBytes: 8, // arbitrary valid size 184 expected: true, 185 }, 186 { 187 name: "maximum valid sizeInBytes", 188 offset: memory.Size() - 8, 189 sizeInBytes: 8, 190 expected: true, 191 }, 192 { 193 name: "sizeInBytes exceeds the valid size by 1", 194 offset: 100, // arbitrary valid offset 195 sizeInBytes: uint64(memory.Size() - 99), 196 expected: false, 197 }, 198 { 199 name: "offset exceeds the memory size", 200 offset: memory.Size(), 201 sizeInBytes: 1, // arbitrary size 202 expected: false, 203 }, 204 { 205 name: "offset + sizeInBytes overflows in uint32", 206 offset: math.MaxUint32 - 1, // invalid too large offset 207 sizeInBytes: 4, // if there's overflow, offset + sizeInBytes is 3, and it may pass the check 208 expected: false, 209 }, 210 { 211 name: "address.wast:200", 212 offset: 4294967295, 213 sizeInBytes: 1, 214 expected: false, 215 }, 216 } 217 218 for _, tt := range tests { 219 tc := tt 220 221 t.Run(tc.name, func(t *testing.T) { 222 require.Equal(t, tc.expected, memory.hasSize(tc.offset, tc.sizeInBytes)) 223 }) 224 } 225 } 226 227 func TestMemoryInstance_ReadUint16Le(t *testing.T) { 228 tests := []struct { 229 name string 230 memory []byte 231 offset uint32 232 expected uint16 233 expectedOk bool 234 }{ 235 { 236 name: "valid offset with an endian-insensitive v", 237 memory: []byte{0xff, 0xff}, 238 offset: 0, // arbitrary valid offset. 239 expected: math.MaxUint16, 240 expectedOk: true, 241 }, 242 { 243 name: "valid offset with an endian-sensitive v", 244 memory: []byte{0xfe, 0xff}, 245 offset: 0, // arbitrary valid offset. 246 expected: math.MaxUint16 - 1, 247 expectedOk: true, 248 }, 249 { 250 name: "maximum boundary valid offset", 251 offset: 1, 252 memory: []byte{0x00, 0x1, 0x00}, 253 expected: 1, // arbitrary valid v 254 expectedOk: true, 255 }, 256 { 257 name: "offset exceeds the maximum valid offset by 1", 258 memory: []byte{0xff, 0xff}, 259 offset: 1, 260 }, 261 } 262 263 for _, tt := range tests { 264 tc := tt 265 266 t.Run(tc.name, func(t *testing.T) { 267 memory := &MemoryInstance{Buffer: tc.memory} 268 269 v, ok := memory.ReadUint16Le(tc.offset) 270 require.Equal(t, tc.expectedOk, ok) 271 require.Equal(t, tc.expected, v) 272 }) 273 } 274 } 275 276 func TestMemoryInstance_ReadUint32Le(t *testing.T) { 277 tests := []struct { 278 name string 279 memory []byte 280 offset uint32 281 expected uint32 282 expectedOk bool 283 }{ 284 { 285 name: "valid offset with an endian-insensitive v", 286 memory: []byte{0xff, 0xff, 0xff, 0xff}, 287 offset: 0, // arbitrary valid offset. 288 expected: math.MaxUint32, 289 expectedOk: true, 290 }, 291 { 292 name: "valid offset with an endian-sensitive v", 293 memory: []byte{0xfe, 0xff, 0xff, 0xff}, 294 offset: 0, // arbitrary valid offset. 295 expected: math.MaxUint32 - 1, 296 expectedOk: true, 297 }, 298 { 299 name: "maximum boundary valid offset", 300 offset: 1, 301 memory: []byte{0x00, 0x1, 0x00, 0x00, 0x00}, 302 expected: 1, // arbitrary valid v 303 expectedOk: true, 304 }, 305 { 306 name: "offset exceeds the maximum valid offset by 1", 307 memory: []byte{0xff, 0xff, 0xff, 0xff}, 308 offset: 1, 309 }, 310 } 311 312 for _, tt := range tests { 313 tc := tt 314 315 t.Run(tc.name, func(t *testing.T) { 316 memory := &MemoryInstance{Buffer: tc.memory} 317 318 v, ok := memory.ReadUint32Le(tc.offset) 319 require.Equal(t, tc.expectedOk, ok) 320 require.Equal(t, tc.expected, v) 321 }) 322 } 323 } 324 325 func TestMemoryInstance_ReadUint64Le(t *testing.T) { 326 tests := []struct { 327 name string 328 memory []byte 329 offset uint32 330 expected uint64 331 expectedOk bool 332 }{ 333 { 334 name: "valid offset with an endian-insensitive v", 335 memory: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 336 offset: 0, // arbitrary valid offset. 337 expected: math.MaxUint64, 338 expectedOk: true, 339 }, 340 { 341 name: "valid offset with an endian-sensitive v", 342 memory: []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 343 offset: 0, // arbitrary valid offset. 344 expected: math.MaxUint64 - 1, 345 expectedOk: true, 346 }, 347 { 348 name: "maximum boundary valid offset", 349 offset: 1, 350 memory: []byte{0x00, 0x1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 351 expected: 1, // arbitrary valid v 352 expectedOk: true, 353 }, 354 { 355 name: "offset exceeds the maximum valid offset by 1", 356 memory: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 357 offset: 1, 358 }, 359 } 360 361 for _, tt := range tests { 362 tc := tt 363 364 t.Run(tc.name, func(t *testing.T) { 365 memory := &MemoryInstance{Buffer: tc.memory} 366 367 v, ok := memory.ReadUint64Le(tc.offset) 368 require.Equal(t, tc.expectedOk, ok) 369 require.Equal(t, tc.expected, v) 370 }) 371 } 372 } 373 374 func TestMemoryInstance_ReadFloat32Le(t *testing.T) { 375 tests := []struct { 376 name string 377 memory []byte 378 offset uint32 379 expected float32 380 expectedOk bool 381 }{ 382 { 383 name: "valid offset with an endian-insensitive v", 384 memory: []byte{0xff, 0x00, 0x00, 0xff}, 385 offset: 0, // arbitrary valid offset. 386 expected: math.Float32frombits(uint32(0xff0000ff)), 387 expectedOk: true, 388 }, 389 { 390 name: "valid offset with an endian-sensitive v", 391 memory: []byte{0xfe, 0x00, 0x00, 0xff}, 392 offset: 0, // arbitrary valid offset. 393 expected: math.Float32frombits(uint32(0xff0000fe)), 394 expectedOk: true, 395 }, 396 { 397 name: "maximum boundary valid offset", 398 offset: 1, 399 memory: []byte{0x00, 0xcd, 0xcc, 0xcc, 0x3d}, 400 expected: 0.1, // arbitrary valid v 401 expectedOk: true, 402 }, 403 { 404 name: "offset exceeds the maximum valid offset by 1", 405 memory: []byte{0xff, 0xff, 0xff, 0xff}, 406 offset: 1, 407 }, 408 } 409 410 for _, tt := range tests { 411 tc := tt 412 413 t.Run(tc.name, func(t *testing.T) { 414 memory := &MemoryInstance{Buffer: tc.memory} 415 416 v, ok := memory.ReadFloat32Le(tc.offset) 417 require.Equal(t, tc.expectedOk, ok) 418 require.Equal(t, tc.expected, v) 419 }) 420 } 421 } 422 423 func TestMemoryInstance_ReadFloat64Le(t *testing.T) { 424 tests := []struct { 425 name string 426 memory []byte 427 offset uint32 428 expected float64 429 expectedOk bool 430 }{ 431 { 432 name: "valid offset with an endian-insensitive v", 433 memory: []byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff}, 434 offset: 0, // arbitrary valid offset. 435 expected: math.Float64frombits(uint64(0xff000000000000ff)), 436 expectedOk: true, 437 }, 438 { 439 name: "valid offset with an endian-sensitive v", 440 memory: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f}, 441 offset: 0, // arbitrary valid offset. 442 expected: math.MaxFloat64, // arbitrary valid v 443 expectedOk: true, 444 }, 445 { 446 name: "maximum boundary valid offset", 447 offset: 1, 448 memory: []byte{0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f}, 449 expected: math.MaxFloat64, // arbitrary valid v 450 expectedOk: true, 451 }, 452 { 453 name: "offset exceeds the maximum valid offset by 1", 454 memory: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 455 offset: 1, 456 }, 457 } 458 459 for _, tt := range tests { 460 tc := tt 461 462 t.Run(tc.name, func(t *testing.T) { 463 memory := &MemoryInstance{Buffer: tc.memory} 464 465 v, ok := memory.ReadFloat64Le(tc.offset) 466 require.Equal(t, tc.expectedOk, ok) 467 require.Equal(t, tc.expected, v) 468 }) 469 } 470 } 471 472 func TestMemoryInstance_Read(t *testing.T) { 473 mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1} 474 475 buf, ok := mem.Read(4, 4) 476 require.True(t, ok) 477 require.Equal(t, []byte{16, 0, 0, 0}, buf) 478 479 // Test write-through 480 buf[3] = 4 481 require.Equal(t, []byte{16, 0, 0, 4}, buf) 482 require.Equal(t, []byte{0, 0, 0, 0, 16, 0, 0, 4}, mem.Buffer) 483 484 _, ok = mem.Read(5, 4) 485 require.False(t, ok) 486 487 _, ok = mem.Read(9, 4) 488 require.False(t, ok) 489 } 490 491 func TestMemoryInstance_WriteUint16Le(t *testing.T) { 492 memory := &MemoryInstance{Buffer: make([]byte, 100)} 493 494 tests := []struct { 495 name string 496 offset uint32 497 v uint16 498 expectedOk bool 499 expectedBytes []byte 500 }{ 501 { 502 name: "valid offset with an endian-insensitive v", 503 offset: 0, // arbitrary valid offset. 504 v: math.MaxUint16, 505 expectedOk: true, 506 expectedBytes: []byte{0xff, 0xff}, 507 }, 508 { 509 name: "valid offset with an endian-sensitive v", 510 offset: 0, // arbitrary valid offset. 511 v: math.MaxUint16 - 1, 512 expectedOk: true, 513 expectedBytes: []byte{0xfe, 0xff}, 514 }, 515 { 516 name: "maximum boundary valid offset", 517 offset: memory.Size() - 2, // 2 is the size of uint16 518 v: 1, // arbitrary valid v 519 expectedOk: true, 520 expectedBytes: []byte{0x1, 0x00}, 521 }, 522 { 523 name: "offset exceeds the maximum valid offset by 1", 524 offset: memory.Size() - 2 + 1, // 2 is the size of uint16 525 v: 1, // arbitrary valid v 526 expectedBytes: []byte{0xff, 0xff}, 527 }, 528 } 529 530 for _, tt := range tests { 531 tc := tt 532 533 t.Run(tc.name, func(t *testing.T) { 534 require.Equal(t, tc.expectedOk, memory.WriteUint16Le(tc.offset, tc.v)) 535 if tc.expectedOk { 536 require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+2]) // 2 is the size of uint16 537 } 538 }) 539 } 540 } 541 542 func TestMemoryInstance_WriteUint32Le(t *testing.T) { 543 memory := &MemoryInstance{Buffer: make([]byte, 100)} 544 545 tests := []struct { 546 name string 547 offset uint32 548 v uint32 549 expectedOk bool 550 expectedBytes []byte 551 }{ 552 { 553 name: "valid offset with an endian-insensitive v", 554 offset: 0, // arbitrary valid offset. 555 v: math.MaxUint32, 556 expectedOk: true, 557 expectedBytes: []byte{0xff, 0xff, 0xff, 0xff}, 558 }, 559 { 560 name: "valid offset with an endian-sensitive v", 561 offset: 0, // arbitrary valid offset. 562 v: math.MaxUint32 - 1, 563 expectedOk: true, 564 expectedBytes: []byte{0xfe, 0xff, 0xff, 0xff}, 565 }, 566 { 567 name: "maximum boundary valid offset", 568 offset: memory.Size() - 4, // 4 is the size of uint32 569 v: 1, // arbitrary valid v 570 expectedOk: true, 571 expectedBytes: []byte{0x1, 0x00, 0x00, 0x00}, 572 }, 573 { 574 name: "offset exceeds the maximum valid offset by 1", 575 offset: memory.Size() - 4 + 1, // 4 is the size of uint32 576 v: 1, // arbitrary valid v 577 expectedBytes: []byte{0xff, 0xff, 0xff, 0xff}, 578 }, 579 } 580 581 for _, tt := range tests { 582 tc := tt 583 584 t.Run(tc.name, func(t *testing.T) { 585 require.Equal(t, tc.expectedOk, memory.WriteUint32Le(tc.offset, tc.v)) 586 if tc.expectedOk { 587 require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+4]) // 4 is the size of uint32 588 } 589 }) 590 } 591 } 592 593 func TestMemoryInstance_WriteUint64Le(t *testing.T) { 594 memory := &MemoryInstance{Buffer: make([]byte, 100)} 595 tests := []struct { 596 name string 597 offset uint32 598 v uint64 599 expectedOk bool 600 expectedBytes []byte 601 }{ 602 { 603 name: "valid offset with an endian-insensitive v", 604 offset: 0, // arbitrary valid offset. 605 v: math.MaxUint64, 606 expectedOk: true, 607 expectedBytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 608 }, 609 { 610 name: "valid offset with an endian-sensitive v", 611 offset: 0, // arbitrary valid offset. 612 v: math.MaxUint64 - 1, 613 expectedOk: true, 614 expectedBytes: []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 615 }, 616 { 617 name: "maximum boundary valid offset", 618 offset: memory.Size() - 8, // 8 is the size of uint64 619 v: 1, // arbitrary valid v 620 expectedOk: true, 621 expectedBytes: []byte{0x1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 622 }, 623 { 624 name: "offset exceeds the maximum valid offset by 1", 625 offset: memory.Size() - 8 + 1, // 8 is the size of uint64 626 v: 1, // arbitrary valid v 627 expectedOk: false, 628 }, 629 } 630 631 for _, tt := range tests { 632 tc := tt 633 634 t.Run(tc.name, func(t *testing.T) { 635 require.Equal(t, tc.expectedOk, memory.WriteUint64Le(tc.offset, tc.v)) 636 if tc.expectedOk { 637 require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+8]) // 8 is the size of uint64 638 } 639 }) 640 } 641 } 642 643 func TestMemoryInstance_WriteFloat32Le(t *testing.T) { 644 memory := &MemoryInstance{Buffer: make([]byte, 100)} 645 646 tests := []struct { 647 name string 648 offset uint32 649 v float32 650 expectedOk bool 651 expectedBytes []byte 652 }{ 653 { 654 name: "valid offset with an endian-insensitive v", 655 offset: 0, // arbitrary valid offset. 656 v: math.Float32frombits(uint32(0xff0000ff)), 657 expectedOk: true, 658 expectedBytes: []byte{0xff, 0x00, 0x00, 0xff}, 659 }, 660 { 661 name: "valid offset with an endian-sensitive v", 662 offset: 0, // arbitrary valid offset. 663 v: math.Float32frombits(uint32(0xff0000fe)), // arbitrary valid v 664 expectedOk: true, 665 expectedBytes: []byte{0xfe, 0x00, 0x00, 0xff}, 666 }, 667 { 668 name: "maximum boundary valid offset", 669 offset: memory.Size() - 4, // 4 is the size of float32 670 v: 0.1, // arbitrary valid v 671 expectedOk: true, 672 expectedBytes: []byte{0xcd, 0xcc, 0xcc, 0x3d}, 673 }, 674 { 675 name: "offset exceeds the maximum valid offset by 1", 676 offset: memory.Size() - 4 + 1, // 4 is the size of float32 677 v: math.MaxFloat32, // arbitrary valid v 678 expectedBytes: []byte{0xff, 0xff, 0xff, 0xff}, 679 }, 680 } 681 682 for _, tt := range tests { 683 tc := tt 684 685 t.Run(tc.name, func(t *testing.T) { 686 require.Equal(t, tc.expectedOk, memory.WriteFloat32Le(tc.offset, tc.v)) 687 if tc.expectedOk { 688 require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+4]) // 4 is the size of float32 689 } 690 }) 691 } 692 } 693 694 func TestMemoryInstance_WriteFloat64Le(t *testing.T) { 695 memory := &MemoryInstance{Buffer: make([]byte, 100)} 696 tests := []struct { 697 name string 698 offset uint32 699 v float64 700 expectedOk bool 701 expectedBytes []byte 702 }{ 703 { 704 name: "valid offset with an endian-insensitive v", 705 offset: 0, // arbitrary valid offset. 706 v: math.Float64frombits(uint64(0xff000000000000ff)), 707 expectedOk: true, 708 expectedBytes: []byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff}, 709 }, 710 { 711 name: "valid offset with an endian-sensitive v", 712 offset: 0, // arbitrary valid offset. 713 v: math.MaxFloat64, // arbitrary valid v 714 expectedOk: true, 715 expectedBytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f}, 716 }, 717 { 718 name: "maximum boundary valid offset", 719 offset: memory.Size() - 8, // 8 is the size of float64 720 v: math.MaxFloat64, // arbitrary valid v 721 expectedOk: true, 722 expectedBytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f}, 723 }, 724 { 725 name: "offset exceeds the maximum valid offset by 1", 726 offset: memory.Size() - 8 + 1, // 8 is the size of float64 727 v: math.MaxFloat64, // arbitrary valid v 728 expectedOk: false, 729 }, 730 } 731 732 for _, tt := range tests { 733 tc := tt 734 735 t.Run(tc.name, func(t *testing.T) { 736 require.Equal(t, tc.expectedOk, memory.WriteFloat64Le(tc.offset, tc.v)) 737 if tc.expectedOk { 738 require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+8]) // 8 is the size of float64 739 } 740 }) 741 } 742 } 743 744 func TestMemoryInstance_Write(t *testing.T) { 745 mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1} 746 747 buf := []byte{16, 0, 0, 4} 748 require.True(t, mem.Write(4, buf)) 749 require.Equal(t, []byte{0, 0, 0, 0, 16, 0, 0, 4}, mem.Buffer) 750 751 // Test it isn't write-through 752 buf[3] = 0 753 require.Equal(t, []byte{16, 0, 0, 0}, buf) 754 require.Equal(t, []byte{0, 0, 0, 0, 16, 0, 0, 4}, mem.Buffer) 755 756 ok := mem.Write(5, buf) 757 require.False(t, ok) 758 759 ok = mem.Write(9, buf) 760 require.False(t, ok) 761 } 762 763 func TestMemoryInstance_Write_overflow(t *testing.T) { 764 mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1} 765 766 // Test overflow 767 huge := uint64(math.MaxUint32 + 1 + 4) 768 if huge != uint64(int(huge)) { 769 t.Skip("Skipping on 32-bit") 770 } 771 772 buf := []byte{16, 0, 0, 4} 773 header := (*reflect.SliceHeader)(unsafe.Pointer(&buf)) 774 header.Len = int(huge) 775 header.Cap = int(huge) 776 777 require.False(t, mem.Write(4, buf)) 778 } 779 780 func TestMemoryInstance_WriteString(t *testing.T) { 781 mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1} 782 783 s := "bear" 784 require.True(t, mem.WriteString(4, s)) 785 require.Equal(t, []byte{0, 0, 0, 0, 'b', 'e', 'a', 'r'}, mem.Buffer) 786 787 ok := mem.WriteString(5, s) 788 require.False(t, ok) 789 790 ok = mem.WriteString(9, s) 791 require.False(t, ok) 792 } 793 794 func BenchmarkWriteString(b *testing.B) { 795 tests := []string{ 796 "", 797 "bear", 798 "hello world", 799 strings.Repeat("hello ", 10), 800 } 801 //nolint intentionally testing interface access 802 var mem api.Memory 803 mem = &MemoryInstance{Buffer: make([]byte, 1000), Min: 1} 804 for _, tt := range tests { 805 b.Run("", func(b *testing.B) { 806 b.Run("Write", func(b *testing.B) { 807 for i := 0; i < b.N; i++ { 808 if !mem.Write(0, []byte(tt)) { 809 b.Fail() 810 } 811 } 812 }) 813 b.Run("WriteString", func(b *testing.B) { 814 for i := 0; i < b.N; i++ { 815 if !mem.WriteString(0, tt) { 816 b.Fail() 817 } 818 } 819 }) 820 }) 821 } 822 } 823 824 func Test_atomicStoreLength(t *testing.T) { 825 // Doesn't verify atomicity, but at least we're updating the correct thing. 826 slice := make([]byte, 10, 20) 827 atomicStoreLength(&slice, 15) 828 require.Equal(t, 15, len(slice)) 829 } 830 831 func Test_atomicStoreLengthAndCap(t *testing.T) { 832 // Doesn't verify atomicity, but at least we're updating the correct thing. 833 slice := make([]byte, 10, 20) 834 atomicStoreLengthAndCap(&slice, 12, 18) 835 require.Equal(t, 12, len(slice)) 836 require.Equal(t, 18, cap(slice)) 837 } 838 839 func TestNewMemoryInstance_Shared(t *testing.T) { 840 tests := []struct { 841 name string 842 mem *Memory 843 }{ 844 { 845 name: "min 0, max 1", 846 mem: &Memory{Min: 0, Max: 1, IsMaxEncoded: true, IsShared: true}, 847 }, 848 { 849 name: "min 0, max 0", 850 mem: &Memory{Min: 0, Max: 0, IsMaxEncoded: true, IsShared: true}, 851 }, 852 } 853 854 for _, tc := range tests { 855 tc := tc 856 t.Run(tc.name, func(t *testing.T) { 857 m := NewMemoryInstance(tc.mem, nil) 858 require.Equal(t, tc.mem.Min, m.Min) 859 require.Equal(t, tc.mem.Max, m.Max) 860 require.True(t, m.Shared) 861 }) 862 } 863 } 864 865 func TestMemoryInstance_WaitNotifyOnce(t *testing.T) { 866 reader := func(mem *MemoryInstance, offset uint32) uint32 { 867 val, _ := mem.ReadUint32Le(offset) 868 return val 869 } 870 t.Run("no waiters", func(t *testing.T) { 871 mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1, Shared: true} 872 873 notifyWaiters(t, mem, 0, 1, 0) 874 }) 875 876 t.Run("single wait, notify", func(t *testing.T) { 877 mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1, Shared: true} 878 879 ch := make(chan string) 880 // Reuse same offset 3 times to verify reuse 881 for i := 0; i < 3; i++ { 882 go func() { 883 res := mem.Wait32(0, 0, -1, reader) 884 propagateWaitResult(t, ch, res) 885 }() 886 887 requireChannelEmpty(t, ch) 888 notifyWaiters(t, mem, 0, 1, 1) 889 require.Equal(t, "", <-ch) 890 891 notifyWaiters(t, mem, 0, 1, 0) 892 } 893 }) 894 895 t.Run("multiple waiters, notify all", func(t *testing.T) { 896 mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1, Shared: true} 897 898 ch := make(chan string) 899 go func() { 900 res := mem.Wait32(0, 0, -1, reader) 901 propagateWaitResult(t, ch, res) 902 }() 903 go func() { 904 res := mem.Wait32(0, 0, -1, reader) 905 propagateWaitResult(t, ch, res) 906 }() 907 908 requireChannelEmpty(t, ch) 909 910 notifyWaiters(t, mem, 0, 2, 2) 911 require.Equal(t, "", <-ch) 912 require.Equal(t, "", <-ch) 913 }) 914 915 t.Run("multiple waiters, notify one", func(t *testing.T) { 916 mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1, Shared: true} 917 918 ch := make(chan string) 919 go func() { 920 res := mem.Wait32(0, 0, -1, reader) 921 propagateWaitResult(t, ch, res) 922 }() 923 go func() { 924 res := mem.Wait32(0, 0, -1, reader) 925 propagateWaitResult(t, ch, res) 926 }() 927 928 requireChannelEmpty(t, ch) 929 notifyWaiters(t, mem, 0, 1, 1) 930 require.Equal(t, "", <-ch) 931 requireChannelEmpty(t, ch) 932 notifyWaiters(t, mem, 0, 1, 1) 933 require.Equal(t, "", <-ch) 934 }) 935 936 t.Run("multiple offsets", func(t *testing.T) { 937 mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1, Shared: true} 938 939 ch := make(chan string) 940 go func() { 941 res := mem.Wait32(0, 0, -1, reader) 942 propagateWaitResult(t, ch, res) 943 }() 944 go func() { 945 res := mem.Wait32(1, 268435456, -1, reader) 946 propagateWaitResult(t, ch, res) 947 }() 948 949 requireChannelEmpty(t, ch) 950 notifyWaiters(t, mem, 0, 2, 1) 951 require.Equal(t, "", <-ch) 952 requireChannelEmpty(t, ch) 953 notifyWaiters(t, mem, 1, 2, 1) 954 require.Equal(t, "", <-ch) 955 }) 956 957 t.Run("timeout", func(t *testing.T) { 958 mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1, Shared: true} 959 960 ch := make(chan string) 961 go func() { 962 res := mem.Wait32(0, 0, 10 /* ns */, reader) 963 propagateWaitResult(t, ch, res) 964 }() 965 966 require.Equal(t, "timeout", <-ch) 967 }) 968 } 969 970 func notifyWaiters(t *testing.T, mem *MemoryInstance, offset, count, exp int) { 971 t.Helper() 972 cur := 0 973 tries := 0 974 for cur < exp { 975 if tries > 100 { 976 t.Fatal("too many tries waiting for wait and notify to converge") 977 } 978 n := mem.Notify(uint32(offset), uint32(count)) 979 cur += int(n) 980 time.Sleep(1 * time.Millisecond) 981 tries++ 982 } 983 } 984 985 func propagateWaitResult(t *testing.T, ch chan string, res uint64) { 986 t.Helper() 987 switch res { 988 case 2: 989 ch <- "timeout" 990 default: 991 ch <- "" 992 } 993 } 994 995 func requireChannelEmpty(t *testing.T, ch chan string) { 996 t.Helper() 997 select { 998 case <-ch: 999 t.Fatal("channel should be empty") 1000 default: 1001 // fallthrough 1002 } 1003 } 1004 1005 func sliceAllocator(cap, max uint64) experimental.LinearMemory { 1006 return &sliceBuffer{make([]byte, cap), max} 1007 } 1008 1009 type sliceBuffer struct { 1010 buf []byte 1011 max uint64 1012 } 1013 1014 func (b *sliceBuffer) Free() {} 1015 1016 func (b *sliceBuffer) Reallocate(size uint64) []byte { 1017 if cap := uint64(cap(b.buf)); size > cap { 1018 b.buf = append(b.buf[:cap], make([]byte, size-cap)...) 1019 } else { 1020 b.buf = b.buf[:size] 1021 } 1022 return b.buf 1023 }