github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/integration_test/engine/threads_test.go (about)

     1  package adhoc
     2  
     3  import (
     4  	_ "embed"
     5  	"testing"
     6  
     7  	wazero "github.com/wasilibs/wazerox"
     8  	"github.com/wasilibs/wazerox/api"
     9  	"github.com/wasilibs/wazerox/experimental"
    10  	"github.com/wasilibs/wazerox/internal/testing/hammer"
    11  	"github.com/wasilibs/wazerox/internal/testing/require"
    12  )
    13  
    14  // We do not currently have hammer tests for bitwise and/or operations. The tests are designed to have
    15  // input that changes deterministically every iteration, which is difficult to model with these operations.
    16  // This is likely why atomic and/or do not show up in the wild very often if at all.
    17  var (
    18  	// memory.atomic.notify, memory.atomic.wait32, memory.atomic.wait64
    19  	// i32.atomic.store, i32.atomic.rmw.cmpxchg
    20  	// i64.atomic.store, i64.atomic.rmw.cmpxchg
    21  	// i32.atomic.store8, i32.atomic.rmw8.cmpxchg_u
    22  	// i32.atomic.store16, i32.atomic.rmw16.cmpxchg_u
    23  	// i64.atomic.store8, i64.atomic.rmw8.cmpxchg_u
    24  	// i64.atomic.store16, i64.atomic.rmw16.cmpxchg_u
    25  	// i64.atomic.store32, i64.atomic.rmw32.cmpxchg_u
    26  	//go:embed testdata/threads/mutex.wasm
    27  	mutexWasm []byte
    28  
    29  	// i32.atomic.rmw.add, i64.atomic.rmw.add, i32.atomic.rmw8.add_u, i32.atomic.rmw16.add_u, i64.atomic.rmw8.add_u, i64.atomic.rmw16.add_u, i64.atomic.rmw32.add_u
    30  	//go:embed testdata/threads/add.wasm
    31  	addWasm []byte
    32  
    33  	// i32.atomic.rmw.sub, i64.atomic.rmw.sub, i32.atomic.rmw8.sub_u, i32.atomic.rmw16.sub_u, i64.atomic.rmw8.sub_u, i64.atomic.rmw16.sub_u, i64.atomic.rmw32.sub_u
    34  	//go:embed testdata/threads/sub.wasm
    35  	subWasm []byte
    36  
    37  	// i32.atomic.rmw.xor, i64.atomic.rmw.xor, i32.atomic.rmw8.xor_u, i32.atomic.rmw16.xor_u, i64.atomic.rmw8.xor_u, i64.atomic.rmw16.xor_u, i64.atomic.rmw32.xor_u
    38  	//go:embed testdata/threads/xor.wasm
    39  	xorWasm []byte
    40  )
    41  
    42  var threadTests = map[string]testCase{
    43  	"increment guarded by mutex": {f: incrementGuardedByMutex},
    44  	"atomic add":                 {f: atomicAdd},
    45  	"atomic sub":                 {f: atomicSub},
    46  	"atomic xor":                 {f: atomicXor},
    47  }
    48  
    49  func TestThreadsNotEnabled(t *testing.T) {
    50  	r := wazero.NewRuntime(testCtx)
    51  	_, err := r.Instantiate(testCtx, mutexWasm)
    52  	require.EqualError(t, err, "section memory: shared memory requested but threads feature not enabled")
    53  }
    54  
    55  func TestThreads(t *testing.T) {
    56  	runAllTests(t, threadTests, wazero.NewRuntimeConfig().WithCoreFeatures(api.CoreFeaturesV2|experimental.CoreFeaturesThreads), false)
    57  }
    58  
    59  func incrementGuardedByMutex(t *testing.T, r wazero.Runtime) {
    60  	P := 8               // max count of goroutines
    61  	if testing.Short() { // Adjust down if `-test.short`
    62  		P = 4
    63  	}
    64  	tests := []struct {
    65  		fn string
    66  	}{
    67  		{
    68  			fn: "run32",
    69  		},
    70  		{
    71  			fn: "run64",
    72  		},
    73  		{
    74  			fn: "run32_8",
    75  		},
    76  		{
    77  			fn: "run32_16",
    78  		},
    79  		{
    80  			fn: "run64_8",
    81  		},
    82  		{
    83  			fn: "run64_16",
    84  		},
    85  		{
    86  			fn: "run64_32",
    87  		},
    88  	}
    89  	for _, tc := range tests {
    90  		tt := tc
    91  		t.Run(tt.fn, func(t *testing.T) {
    92  			mod, err := r.Instantiate(testCtx, mutexWasm)
    93  			require.NoError(t, err)
    94  
    95  			hammer.NewHammer(t, P, 30000).Run(func(name string) {
    96  				_, err := mod.ExportedFunction(tt.fn).Call(testCtx)
    97  				require.NoError(t, err)
    98  			}, func() {})
    99  
   100  			// Cheat that LE encoding can read both 32 and 64 bits
   101  			res, ok := mod.Memory().ReadUint32Le(8)
   102  			require.True(t, ok)
   103  			require.Equal(t, uint32(P*30000), res)
   104  		})
   105  	}
   106  }
   107  
   108  func atomicAdd(t *testing.T, r wazero.Runtime) {
   109  	P := 8               // max count of goroutines
   110  	if testing.Short() { // Adjust down if `-test.short`
   111  		P = 4
   112  	}
   113  	tests := []struct {
   114  		fn  string
   115  		exp int
   116  	}{
   117  		{
   118  			fn:  "run32",
   119  			exp: P * 30000,
   120  		},
   121  		{
   122  			fn:  "run64",
   123  			exp: P * 30000,
   124  		},
   125  		{
   126  			fn: "run32_8",
   127  			// Overflows
   128  			exp: (P * 30000) % (1 << 8),
   129  		},
   130  		{
   131  			fn: "run32_16",
   132  			// Overflows
   133  			exp: (P * 30000) % (1 << 16),
   134  		},
   135  		{
   136  			fn: "run64_8",
   137  			// Overflows
   138  			exp: (P * 30000) % (1 << 8),
   139  		},
   140  		{
   141  			fn: "run64_16",
   142  			// Overflows
   143  			exp: (P * 30000) % (1 << 16),
   144  		},
   145  		{
   146  			fn:  "run64_32",
   147  			exp: P * 30000,
   148  		},
   149  	}
   150  	for _, tc := range tests {
   151  		tt := tc
   152  		t.Run(tt.fn, func(t *testing.T) {
   153  			mod, err := r.Instantiate(testCtx, addWasm)
   154  			require.NoError(t, err)
   155  
   156  			hammer.NewHammer(t, P, 30000).Run(func(name string) {
   157  				_, err := mod.ExportedFunction(tt.fn).Call(testCtx)
   158  				require.NoError(t, err)
   159  			}, func() {})
   160  
   161  			// Cheat that LE encoding can read both 32 and 64 bits
   162  			res, ok := mod.Memory().ReadUint32Le(0)
   163  			require.True(t, ok)
   164  			require.Equal(t, uint32(tt.exp), res)
   165  		})
   166  	}
   167  }
   168  
   169  func atomicSub(t *testing.T, r wazero.Runtime) {
   170  	P := 8               // max count of goroutines
   171  	if testing.Short() { // Adjust down if `-test.short`
   172  		P = 4
   173  	}
   174  	tests := []struct {
   175  		fn  string
   176  		exp int
   177  	}{
   178  		{
   179  			fn:  "run32",
   180  			exp: -(P * 30000),
   181  		},
   182  		{
   183  			fn:  "run64",
   184  			exp: -(P * 30000),
   185  		},
   186  		{
   187  			fn: "run32_8",
   188  			// Overflows
   189  			exp: (1 << 8) - ((P * 30000) % (1 << 8)),
   190  		},
   191  		{
   192  			fn: "run32_16",
   193  			// Overflows
   194  			exp: (1 << 16) - ((P * 30000) % (1 << 16)),
   195  		},
   196  		{
   197  			fn: "run64_8",
   198  			// Overflows
   199  			exp: (1 << 8) - ((P * 30000) % (1 << 8)),
   200  		},
   201  		{
   202  			fn: "run64_16",
   203  			// Overflows
   204  			exp: (1 << 16) - ((P * 30000) % (1 << 16)),
   205  		},
   206  		{
   207  			fn:  "run64_32",
   208  			exp: -(P * 30000),
   209  		},
   210  	}
   211  	for _, tc := range tests {
   212  		tt := tc
   213  		t.Run(tt.fn, func(t *testing.T) {
   214  			mod, err := r.Instantiate(testCtx, subWasm)
   215  			require.NoError(t, err)
   216  
   217  			hammer.NewHammer(t, P, 30000).Run(func(name string) {
   218  				_, err := mod.ExportedFunction(tt.fn).Call(testCtx)
   219  				require.NoError(t, err)
   220  			}, func() {})
   221  
   222  			// Cheat that LE encoding can read both 32 and 64 bits
   223  			res, ok := mod.Memory().ReadUint32Le(0)
   224  			require.True(t, ok)
   225  			require.Equal(t, int32(tt.exp), int32(res))
   226  		})
   227  	}
   228  }
   229  
   230  func atomicXor(t *testing.T, r wazero.Runtime) {
   231  	P := 8               // max count of goroutines
   232  	if testing.Short() { // Adjust down if `-test.short`
   233  		P = 4
   234  	}
   235  	tests := []struct {
   236  		fn string
   237  	}{
   238  		{
   239  			fn: "run32",
   240  		},
   241  		{
   242  			fn: "run64",
   243  		},
   244  		{
   245  			fn: "run32_8",
   246  		},
   247  		{
   248  			fn: "run32_16",
   249  		},
   250  		{
   251  			fn: "run64_8",
   252  		},
   253  		{
   254  			fn: "run64_16",
   255  		},
   256  		{
   257  			fn: "run64_32",
   258  		},
   259  	}
   260  	for _, tc := range tests {
   261  		tt := tc
   262  		t.Run(tt.fn, func(t *testing.T) {
   263  			mod, err := r.Instantiate(testCtx, xorWasm)
   264  			require.NoError(t, err)
   265  
   266  			mod.Memory().WriteUint32Le(0, 12345)
   267  
   268  			hammer.NewHammer(t, P, 30000).Run(func(name string) {
   269  				_, err := mod.ExportedFunction(tt.fn).Call(testCtx)
   270  				require.NoError(t, err)
   271  			}, func() {})
   272  
   273  			// Cheat that LE encoding can read both 32 and 64 bits
   274  			res, ok := mod.Memory().ReadUint32Le(0)
   275  			require.True(t, ok)
   276  			// Even number of iterations, the value should be unchanged.
   277  			require.Equal(t, uint32(12345), res)
   278  		})
   279  	}
   280  }