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 }