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  }