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