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  }