github.com/tetratelabs/wazero@v1.7.1/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 TestNewMemoryInstance_Shared(t *testing.T) { 832 tests := []struct { 833 name string 834 mem *Memory 835 }{ 836 { 837 name: "min 0, max 1", 838 mem: &Memory{Min: 0, Max: 1, IsMaxEncoded: true, IsShared: true}, 839 }, 840 { 841 name: "min 0, max 0", 842 mem: &Memory{Min: 0, Max: 0, IsMaxEncoded: true, IsShared: true}, 843 }, 844 } 845 846 for _, tc := range tests { 847 tc := tc 848 t.Run(tc.name, func(t *testing.T) { 849 m := NewMemoryInstance(tc.mem, nil) 850 require.Equal(t, tc.mem.Min, m.Min) 851 require.Equal(t, tc.mem.Max, m.Max) 852 require.True(t, m.Shared) 853 }) 854 } 855 } 856 857 func TestMemoryInstance_WaitNotifyOnce(t *testing.T) { 858 reader := func(mem *MemoryInstance, offset uint32) uint32 { 859 val, _ := mem.ReadUint32Le(offset) 860 return val 861 } 862 t.Run("no waiters", func(t *testing.T) { 863 mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1, Shared: true} 864 865 notifyWaiters(t, mem, 0, 1, 0) 866 }) 867 868 t.Run("single wait, notify", func(t *testing.T) { 869 mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1, Shared: true} 870 871 ch := make(chan string) 872 // Reuse same offset 3 times to verify reuse 873 for i := 0; i < 3; i++ { 874 go func() { 875 res := mem.Wait32(0, 0, -1, reader) 876 propagateWaitResult(t, ch, res) 877 }() 878 879 requireChannelEmpty(t, ch) 880 notifyWaiters(t, mem, 0, 1, 1) 881 require.Equal(t, "", <-ch) 882 883 notifyWaiters(t, mem, 0, 1, 0) 884 } 885 }) 886 887 t.Run("multiple waiters, notify all", func(t *testing.T) { 888 mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1, Shared: true} 889 890 ch := make(chan string) 891 go func() { 892 res := mem.Wait32(0, 0, -1, reader) 893 propagateWaitResult(t, ch, res) 894 }() 895 go func() { 896 res := mem.Wait32(0, 0, -1, reader) 897 propagateWaitResult(t, ch, res) 898 }() 899 900 requireChannelEmpty(t, ch) 901 902 notifyWaiters(t, mem, 0, 2, 2) 903 require.Equal(t, "", <-ch) 904 require.Equal(t, "", <-ch) 905 }) 906 907 t.Run("multiple waiters, notify one", func(t *testing.T) { 908 mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1, Shared: true} 909 910 ch := make(chan string) 911 go func() { 912 res := mem.Wait32(0, 0, -1, reader) 913 propagateWaitResult(t, ch, res) 914 }() 915 go func() { 916 res := mem.Wait32(0, 0, -1, reader) 917 propagateWaitResult(t, ch, res) 918 }() 919 920 requireChannelEmpty(t, ch) 921 notifyWaiters(t, mem, 0, 1, 1) 922 require.Equal(t, "", <-ch) 923 requireChannelEmpty(t, ch) 924 notifyWaiters(t, mem, 0, 1, 1) 925 require.Equal(t, "", <-ch) 926 }) 927 928 t.Run("multiple offsets", func(t *testing.T) { 929 mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1, Shared: true} 930 931 ch := make(chan string) 932 go func() { 933 res := mem.Wait32(0, 0, -1, reader) 934 propagateWaitResult(t, ch, res) 935 }() 936 go func() { 937 res := mem.Wait32(1, 268435456, -1, reader) 938 propagateWaitResult(t, ch, res) 939 }() 940 941 requireChannelEmpty(t, ch) 942 notifyWaiters(t, mem, 0, 2, 1) 943 require.Equal(t, "", <-ch) 944 requireChannelEmpty(t, ch) 945 notifyWaiters(t, mem, 1, 2, 1) 946 require.Equal(t, "", <-ch) 947 }) 948 949 t.Run("timeout", func(t *testing.T) { 950 mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1, Shared: true} 951 952 ch := make(chan string) 953 go func() { 954 res := mem.Wait32(0, 0, 10 /* ns */, reader) 955 propagateWaitResult(t, ch, res) 956 }() 957 958 require.Equal(t, "timeout", <-ch) 959 }) 960 } 961 962 func notifyWaiters(t *testing.T, mem *MemoryInstance, offset, count, exp int) { 963 t.Helper() 964 cur := 0 965 tries := 0 966 for cur < exp { 967 if tries > 100 { 968 t.Fatal("too many tries waiting for wait and notify to converge") 969 } 970 n := mem.Notify(uint32(offset), uint32(count)) 971 cur += int(n) 972 time.Sleep(1 * time.Millisecond) 973 tries++ 974 } 975 } 976 977 func propagateWaitResult(t *testing.T, ch chan string, res uint64) { 978 t.Helper() 979 switch res { 980 case 2: 981 ch <- "timeout" 982 default: 983 ch <- "" 984 } 985 } 986 987 func requireChannelEmpty(t *testing.T, ch chan string) { 988 t.Helper() 989 select { 990 case <-ch: 991 t.Fatal("channel should be empty") 992 default: 993 // fallthrough 994 } 995 } 996 997 func sliceAllocator(cap, max uint64) experimental.LinearMemory { 998 return &sliceBuffer{make([]byte, cap), max} 999 } 1000 1001 type sliceBuffer struct { 1002 buf []byte 1003 max uint64 1004 } 1005 1006 func (b *sliceBuffer) Free() {} 1007 1008 func (b *sliceBuffer) Reallocate(size uint64) []byte { 1009 if cap := uint64(cap(b.buf)); size > cap { 1010 b.buf = append(b.buf[:cap], make([]byte, size-cap)...) 1011 } else { 1012 b.buf = b.buf[:size] 1013 } 1014 return b.buf 1015 }