github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/engine/wazevo/e2e_test.go (about) 1 package wazevo_test 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/binary" 7 "fmt" 8 "math" 9 "testing" 10 11 wazero "github.com/wasilibs/wazerox" 12 "github.com/wasilibs/wazerox/api" 13 "github.com/wasilibs/wazerox/experimental" 14 "github.com/wasilibs/wazerox/experimental/logging" 15 "github.com/wasilibs/wazerox/experimental/opt" 16 "github.com/wasilibs/wazerox/internal/engine/wazevo/testcases" 17 "github.com/wasilibs/wazerox/internal/leb128" 18 "github.com/wasilibs/wazerox/internal/testing/binaryencoding" 19 "github.com/wasilibs/wazerox/internal/testing/dwarftestdata" 20 "github.com/wasilibs/wazerox/internal/testing/require" 21 "github.com/wasilibs/wazerox/internal/wasm" 22 ) 23 24 const ( 25 i32 = wasm.ValueTypeI32 26 i64 = wasm.ValueTypeI64 27 f32 = wasm.ValueTypeF32 28 f64 = wasm.ValueTypeF64 29 v128 = wasm.ValueTypeV128 30 ) 31 32 func TestE2E(t *testing.T) { 33 tmp := t.TempDir() 34 type callCase struct { 35 funcName string // defaults to testcases.ExportedFunctionName 36 params, expResults []uint64 37 expErr string 38 } 39 for _, tc := range []struct { 40 name string 41 imported, m *wasm.Module 42 calls []callCase 43 }{ 44 { 45 name: "selects", m: testcases.Selects.Module, 46 calls: []callCase{ 47 { 48 params: []uint64{ 49 0, 1, // i32, 50 200, 100, // i64, 51 uint64(math.Float32bits(3.0)), uint64(math.Float32bits(10.0)), 52 math.Float64bits(-123.4), math.Float64bits(-10000000000.0), 53 }, 54 expResults: []uint64{ 55 1, 56 200, 57 uint64(math.Float32bits(3.0)), 58 math.Float64bits(-123.4), 59 }, 60 }, 61 }, 62 }, 63 { 64 name: "swap", m: testcases.SwapParamAndReturn.Module, 65 calls: []callCase{ 66 {params: []uint64{math.MaxUint32, math.MaxInt32}, expResults: []uint64{math.MaxInt32, math.MaxUint32}}, 67 }, 68 }, 69 { 70 name: "consts", m: testcases.Constants.Module, 71 calls: []callCase{ 72 {expResults: []uint64{1, 2, uint64(math.Float32bits(32.0)), math.Float64bits(64.0)}}, 73 }, 74 }, 75 { 76 name: "unreachable", m: testcases.Unreachable.Module, 77 calls: []callCase{{expErr: "unreachable"}}, 78 }, 79 { 80 name: "fibonacci_recursive", m: testcases.FibonacciRecursive.Module, 81 calls: []callCase{ 82 {params: []uint64{0}, expResults: []uint64{0}}, 83 {params: []uint64{1}, expResults: []uint64{1}}, 84 {params: []uint64{2}, expResults: []uint64{1}}, 85 {params: []uint64{10}, expResults: []uint64{55}}, 86 {params: []uint64{20}, expResults: []uint64{6765}}, 87 {params: []uint64{30}, expResults: []uint64{0xcb228}}, 88 }, 89 }, 90 {name: "call", m: testcases.Call.Module, calls: []callCase{{expResults: []uint64{45, 45}}}}, 91 { 92 name: "stack overflow", 93 m: &wasm.Module{ 94 TypeSection: []wasm.FunctionType{{}}, 95 FunctionSection: []wasm.Index{0}, 96 CodeSection: []wasm.Code{{Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}}}, 97 ExportSection: []wasm.Export{{Name: testcases.ExportedFunctionName, Index: 0, Type: wasm.ExternTypeFunc}}, 98 }, 99 calls: []callCase{ 100 {expErr: "stack overflow"}, {expErr: "stack overflow"}, {expErr: "stack overflow"}, {expErr: "stack overflow"}, 101 }, 102 }, 103 { 104 name: "imported_function_call", 105 imported: testcases.ImportedFunctionCall.Imported, 106 m: testcases.ImportedFunctionCall.Module, 107 calls: []callCase{ 108 {params: []uint64{0}, expResults: []uint64{0}}, 109 {params: []uint64{2}, expResults: []uint64{2 * 2}}, 110 {params: []uint64{45}, expResults: []uint64{45 * 45}}, 111 {params: []uint64{90}, expResults: []uint64{90 * 90}}, 112 {params: []uint64{100}, expResults: []uint64{100 * 100}}, 113 {params: []uint64{100, 200}, expResults: []uint64{100 * 200}, funcName: "imported_exported"}, 114 }, 115 }, 116 { 117 name: "memory_store_basic", 118 m: testcases.MemoryStoreBasic.Module, 119 calls: []callCase{ 120 {params: []uint64{0, 0xf}, expResults: []uint64{0xf}}, 121 {params: []uint64{256, 0xff}, expResults: []uint64{0xff}}, 122 {params: []uint64{100, 0xffffffff}, expResults: []uint64{0xffffffff}}, 123 // We load I32, so we can't load from the last 3 bytes. 124 {params: []uint64{uint64(wasm.MemoryPageSize) - 3, 0}, expErr: "out of bounds memory access"}, 125 }, 126 }, 127 { 128 name: "memory_load_basic", 129 m: testcases.MemoryLoadBasic.Module, 130 calls: []callCase{ 131 {params: []uint64{0}, expResults: []uint64{0x03_02_01_00}}, 132 {params: []uint64{256}, expResults: []uint64{0x03_02_01_00}}, 133 {params: []uint64{100}, expResults: []uint64{103<<24 | 102<<16 | 101<<8 | 100}}, 134 // Last 4 bytes. 135 {params: []uint64{uint64(wasm.MemoryPageSize) - 4}, expResults: []uint64{0xfffefdfc}}, 136 }, 137 }, 138 { 139 name: "memory out of bounds", 140 m: testcases.MemoryLoadBasic.Module, 141 calls: []callCase{ 142 {params: []uint64{uint64(wasm.MemoryPageSize)}, expErr: "out of bounds memory access"}, 143 // We load I32, so we can't load from the last 3 bytes. 144 {params: []uint64{uint64(wasm.MemoryPageSize) - 3}, expErr: "out of bounds memory access"}, 145 }, 146 }, 147 { 148 name: "memory_loads", 149 m: testcases.MemoryLoads.Module, 150 calls: []callCase{ 151 // These expected results are derived by commenting out `configureWazevo(config)` below to run the old compiler, assuming that it is correct. 152 {params: []uint64{0}, expResults: []uint64{0x3020100, 0x706050403020100, 0x3020100, 0x706050403020100, 0x1211100f, 0x161514131211100f, 0x1211100f, 0x161514131211100f, 0x0, 0xf, 0x0, 0xf, 0x100, 0x100f, 0x100, 0x100f, 0x0, 0xf, 0x0, 0xf, 0x100, 0x100f, 0x100, 0x100f, 0x3020100, 0x1211100f, 0x3020100, 0x1211100f}}, 153 {params: []uint64{1}, expResults: []uint64{0x4030201, 0x807060504030201, 0x4030201, 0x807060504030201, 0x13121110, 0x1716151413121110, 0x13121110, 0x1716151413121110, 0x1, 0x10, 0x1, 0x10, 0x201, 0x1110, 0x201, 0x1110, 0x1, 0x10, 0x1, 0x10, 0x201, 0x1110, 0x201, 0x1110, 0x4030201, 0x13121110, 0x4030201, 0x13121110}}, 154 {params: []uint64{8}, expResults: []uint64{0xb0a0908, 0xf0e0d0c0b0a0908, 0xb0a0908, 0xf0e0d0c0b0a0908, 0x1a191817, 0x1e1d1c1b1a191817, 0x1a191817, 0x1e1d1c1b1a191817, 0x8, 0x17, 0x8, 0x17, 0x908, 0x1817, 0x908, 0x1817, 0x8, 0x17, 0x8, 0x17, 0x908, 0x1817, 0x908, 0x1817, 0xb0a0908, 0x1a191817, 0xb0a0908, 0x1a191817}}, 155 {params: []uint64{0xb}, expResults: []uint64{0xe0d0c0b, 0x1211100f0e0d0c0b, 0xe0d0c0b, 0x1211100f0e0d0c0b, 0x1d1c1b1a, 0x21201f1e1d1c1b1a, 0x1d1c1b1a, 0x21201f1e1d1c1b1a, 0xb, 0x1a, 0xb, 0x1a, 0xc0b, 0x1b1a, 0xc0b, 0x1b1a, 0xb, 0x1a, 0xb, 0x1a, 0xc0b, 0x1b1a, 0xc0b, 0x1b1a, 0xe0d0c0b, 0x1d1c1b1a, 0xe0d0c0b, 0x1d1c1b1a}}, 156 {params: []uint64{0xc}, expResults: []uint64{0xf0e0d0c, 0x131211100f0e0d0c, 0xf0e0d0c, 0x131211100f0e0d0c, 0x1e1d1c1b, 0x2221201f1e1d1c1b, 0x1e1d1c1b, 0x2221201f1e1d1c1b, 0xc, 0x1b, 0xc, 0x1b, 0xd0c, 0x1c1b, 0xd0c, 0x1c1b, 0xc, 0x1b, 0xc, 0x1b, 0xd0c, 0x1c1b, 0xd0c, 0x1c1b, 0xf0e0d0c, 0x1e1d1c1b, 0xf0e0d0c, 0x1e1d1c1b}}, 157 {params: []uint64{0xd}, expResults: []uint64{0x100f0e0d, 0x14131211100f0e0d, 0x100f0e0d, 0x14131211100f0e0d, 0x1f1e1d1c, 0x232221201f1e1d1c, 0x1f1e1d1c, 0x232221201f1e1d1c, 0xd, 0x1c, 0xd, 0x1c, 0xe0d, 0x1d1c, 0xe0d, 0x1d1c, 0xd, 0x1c, 0xd, 0x1c, 0xe0d, 0x1d1c, 0xe0d, 0x1d1c, 0x100f0e0d, 0x1f1e1d1c, 0x100f0e0d, 0x1f1e1d1c}}, 158 {params: []uint64{0xe}, expResults: []uint64{0x11100f0e, 0x1514131211100f0e, 0x11100f0e, 0x1514131211100f0e, 0x201f1e1d, 0x24232221201f1e1d, 0x201f1e1d, 0x24232221201f1e1d, 0xe, 0x1d, 0xe, 0x1d, 0xf0e, 0x1e1d, 0xf0e, 0x1e1d, 0xe, 0x1d, 0xe, 0x1d, 0xf0e, 0x1e1d, 0xf0e, 0x1e1d, 0x11100f0e, 0x201f1e1d, 0x11100f0e, 0x201f1e1d}}, 159 {params: []uint64{0xf}, expResults: []uint64{0x1211100f, 0x161514131211100f, 0x1211100f, 0x161514131211100f, 0x21201f1e, 0x2524232221201f1e, 0x21201f1e, 0x2524232221201f1e, 0xf, 0x1e, 0xf, 0x1e, 0x100f, 0x1f1e, 0x100f, 0x1f1e, 0xf, 0x1e, 0xf, 0x1e, 0x100f, 0x1f1e, 0x100f, 0x1f1e, 0x1211100f, 0x21201f1e, 0x1211100f, 0x21201f1e}}, 160 }, 161 }, 162 { 163 name: "globals_get", 164 m: testcases.GlobalsGet.Module, 165 calls: []callCase{ 166 {expResults: []uint64{0x80000000, 0x8000000000000000, 0x7f7fffff, 0x7fefffffffffffff}}, 167 }, 168 }, 169 { 170 name: "globals_set", 171 m: testcases.GlobalsSet.Module, 172 calls: []callCase{{expResults: []uint64{1, 2, uint64(math.Float32bits(3.0)), math.Float64bits(4.0)}}}, 173 }, 174 { 175 name: "globals_mutable", 176 m: testcases.GlobalsMutable.Module, 177 calls: []callCase{{expResults: []uint64{ 178 100, 200, uint64(math.Float32bits(300.0)), math.Float64bits(400.0), 179 1, 2, uint64(math.Float32bits(3.0)), math.Float64bits(4.0), 180 }}}, 181 }, 182 { 183 name: "memory_size_grow", 184 m: testcases.MemorySizeGrow.Module, 185 calls: []callCase{{expResults: []uint64{1, 2, 0xffffffff}}}, 186 }, 187 { 188 name: "imported_memory_grow", 189 imported: testcases.ImportedMemoryGrow.Imported, 190 m: testcases.ImportedMemoryGrow.Module, 191 calls: []callCase{{expResults: []uint64{1, 1, 11, 11}}}, 192 }, 193 { 194 name: "call_indirect", 195 m: testcases.CallIndirect.Module, 196 // parameter == table offset. 197 calls: []callCase{ 198 {params: []uint64{0}, expErr: "indirect call type mismatch"}, 199 {params: []uint64{1}, expResults: []uint64{10}}, 200 {params: []uint64{2}, expErr: "indirect call type mismatch"}, 201 {params: []uint64{10}, expErr: "invalid table access"}, // Null pointer. 202 {params: []uint64{math.MaxUint32}, expErr: "invalid table access"}, // Out of bounds. 203 }, 204 }, 205 { 206 name: "br_table", 207 m: testcases.BrTable.Module, 208 calls: []callCase{ 209 {params: []uint64{0}, expResults: []uint64{11}}, 210 {params: []uint64{1}, expResults: []uint64{12}}, 211 {params: []uint64{2}, expResults: []uint64{13}}, 212 {params: []uint64{3}, expResults: []uint64{14}}, 213 {params: []uint64{4}, expResults: []uint64{15}}, 214 {params: []uint64{5}, expResults: []uint64{16}}, 215 // Out of range --> default. 216 {params: []uint64{6}, expResults: []uint64{11}}, 217 {params: []uint64{1000}, expResults: []uint64{11}}, 218 }, 219 }, 220 { 221 name: "br_table_with_args", 222 m: testcases.BrTableWithArg.Module, 223 calls: []callCase{ 224 {params: []uint64{0, 100}, expResults: []uint64{11 + 100}}, 225 {params: []uint64{1, 100}, expResults: []uint64{12 + 100}}, 226 {params: []uint64{2, 100}, expResults: []uint64{13 + 100}}, 227 {params: []uint64{3, 100}, expResults: []uint64{14 + 100}}, 228 {params: []uint64{4, 100}, expResults: []uint64{15 + 100}}, 229 {params: []uint64{5, 100}, expResults: []uint64{16 + 100}}, 230 // Out of range --> default. 231 {params: []uint64{6, 200}, expResults: []uint64{11 + 200}}, 232 {params: []uint64{1000, 300}, expResults: []uint64{11 + 300}}, 233 }, 234 }, 235 { 236 name: "multi_predecessor_local_ref", 237 m: testcases.MultiPredecessorLocalRef.Module, 238 calls: []callCase{ 239 {params: []uint64{0, 100}, expResults: []uint64{100}}, 240 {params: []uint64{1, 100}, expResults: []uint64{1}}, 241 {params: []uint64{1, 200}, expResults: []uint64{1}}, 242 }, 243 }, 244 { 245 name: "vector_bit_select", 246 m: testcases.VecBitSelect.Module, 247 calls: []callCase{ 248 {params: []uint64{1, 2, 3, 4, 5, 6}, expResults: []uint64{0x3, 0x2, 0x5, 0x6}}, 249 }, 250 }, 251 { 252 name: "vector_shuffle", 253 m: testcases.VecShuffle.Module, 254 calls: []callCase{ 255 {params: []uint64{0x01010101, 0x02020202, 0x03030303, 0x04040404}, expResults: []uint64{0x01010101, 0x04040404}}, 256 {params: []uint64{0x03030303, 0x04040404, 0x01010101, 0x02020202}, expResults: []uint64{0x03030303, 0x02020202}}, 257 {params: []uint64{0x00000000, 0xffffffff, 0xffffffff, 0x00000000}, expResults: []uint64{0x00000000, 0x00000000}}, 258 {params: []uint64{0xffffffff, 0x00000000, 0x00000000, 0xffffffff}, expResults: []uint64{0xffffffff, 0xffffffff}}, 259 {params: []uint64{0x00000000, 0x11111111, 0x11111111, 0xffffffff}, expResults: []uint64{0x00000000, 0xffffffff}}, 260 }, 261 }, 262 { 263 name: "vector_shuffle (1st only)", 264 m: testcases.VecShuffleWithLane(1, 1, 1, 1, 0, 0, 0, 0, 10, 10, 10, 10, 0, 0, 0, 0), 265 calls: []callCase{ 266 {params: []uint64{0x0000000000000b0a, 0x0c0000, 0xffffffffffffffff, 0xffffffffffffffff}, expResults: []uint64{0x0a0a0a0a0b0b0b0b, 0x0a0a0a0a0c0c0c0c}}, 267 {params: []uint64{0x01010101, 0x02020202, 0x03030303, 0x04040404}, expResults: []uint64{0x0101010101010101, 0x101010102020202}}, 268 {params: []uint64{0x03030303, 0x04040404, 0x01010101, 0x02020202}, expResults: []uint64{0x0303030303030303, 0x303030304040404}}, 269 {params: []uint64{0x00000000, 0xffffffff, 0xffffffff, 0x00000000}, expResults: []uint64{0x0000000000000000, 0x0000000ffffffff}}, 270 {params: []uint64{0xffffffff, 0x00000000, 0x00000000, 0xffffffff}, expResults: []uint64{0xffffffffffffffff, 0xffffffff00000000}}, 271 {params: []uint64{0x00000000, 0x11111111, 0x11111111, 0xffffffff}, expResults: []uint64{0x0000000000000000, 0x0000000011111111}}, 272 }, 273 }, 274 { 275 name: "vector_shuffle (2nd only)", 276 m: testcases.VecShuffleWithLane(17, 17, 17, 17, 16, 16, 16, 16, 26, 26, 26, 26, 16, 16, 16, 16), 277 calls: []callCase{ 278 {params: []uint64{0xffffffffffffffff, 0xffffffffffffffff, 0x0000000000000b0a, 0x0c0000}, expResults: []uint64{0x0a0a0a0a0b0b0b0b, 0x0a0a0a0a0c0c0c0c}}, 279 {params: []uint64{0x01010101, 0x02020202, 0x03030303, 0x04040404}, expResults: []uint64{0x303030303030303, 0x303030304040404}}, 280 {params: []uint64{0x03030303, 0x04040404, 0x01010101, 0x02020202}, expResults: []uint64{0x101010101010101, 0x101010102020202}}, 281 {params: []uint64{0x00000000, 0xffffffff, 0xffffffff, 0x00000000}, expResults: []uint64{0xffffffffffffffff, 0xffffffff00000000}}, 282 {params: []uint64{0xffffffff, 0x00000000, 0x00000000, 0xffffffff}, expResults: []uint64{0x0000000000000000, 0xffffffff}}, 283 {params: []uint64{0x00000000, 0x11111111, 0x11111111, 0xffffffff}, expResults: []uint64{0x1111111111111111, 0x11111111ffffffff}}, 284 }, 285 }, 286 { 287 name: "vector_shuffle (mixed)", 288 m: testcases.VecShuffleWithLane(0, 17, 2, 19, 4, 21, 6, 23, 8, 25, 10, 27, 12, 29, 14, 31), 289 calls: []callCase{ 290 {params: []uint64{0xff08ff07ff06ff05, 0xff04ff03ff02ff01, 0x18ff17ff16ff15ff, 0x14ff13ff12ff11ff}, expResults: []uint64{0x1808170716061505, 0x1404130312021101}}, 291 {params: []uint64{0x01010101, 0x02020202, 0x03030303, 0x04040404}, expResults: []uint64{0x3010301, 0x4020402}}, 292 {params: []uint64{0x03030303, 0x04040404, 0x01010101, 0x02020202}, expResults: []uint64{0x1030103, 0x2040204}}, 293 {params: []uint64{0x00000000, 0xffffffff, 0xffffffff, 0x00000000}, expResults: []uint64{0xff00ff00, 0xff00ff}}, 294 {params: []uint64{0xffffffff, 0x00000000, 0x00000000, 0xffffffff}, expResults: []uint64{0xff00ff, 0xff00ff00}}, 295 {params: []uint64{0x00000000, 0x11111111, 0x11111111, 0xffffffff}, expResults: []uint64{0x11001100, 0xff11ff11}}, 296 }, 297 }, 298 } { 299 tc := tc 300 t.Run(tc.name, func(t *testing.T) { 301 for i := 0; i < 2; i++ { 302 var name string 303 if i == 0 { 304 name = "no cache" 305 } else { 306 name = "with cache" 307 } 308 t.Run(name, func(t *testing.T) { 309 cache, err := wazero.NewCompilationCacheWithDir(tmp) 310 require.NoError(t, err) 311 config := opt.NewRuntimeConfigOptimizingCompiler().WithCompilationCache(cache) 312 313 ctx := context.Background() 314 r := wazero.NewRuntimeWithConfig(ctx, config) 315 defer func() { 316 require.NoError(t, r.Close(ctx)) 317 }() 318 319 if tc.imported != nil { 320 imported, err := r.CompileModule(ctx, binaryencoding.EncodeModule(tc.imported)) 321 require.NoError(t, err) 322 323 _, err = r.InstantiateModule(ctx, imported, wazero.NewModuleConfig()) 324 require.NoError(t, err) 325 } 326 327 compiled, err := r.CompileModule(ctx, binaryencoding.EncodeModule(tc.m)) 328 require.NoError(t, err) 329 330 inst, err := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig()) 331 require.NoError(t, err) 332 333 for _, cc := range tc.calls { 334 name := cc.funcName 335 if name == "" { 336 name = testcases.ExportedFunctionName 337 } 338 t.Run(fmt.Sprintf("call_%s%v", name, cc.params), func(t *testing.T) { 339 f := inst.ExportedFunction(name) 340 require.NotNil(t, f) 341 result, err := f.Call(ctx, cc.params...) 342 if cc.expErr != "" { 343 require.Contains(t, err.Error(), cc.expErr) 344 } else { 345 require.NoError(t, err) 346 require.Equal(t, len(cc.expResults), len(result)) 347 require.Equal(t, cc.expResults, result) 348 for i := range cc.expResults { 349 if cc.expResults[i] != result[i] { 350 t.Errorf("result[%d]: exp %d, got %d", i, cc.expResults[i], result[i]) 351 } 352 } 353 } 354 }) 355 } 356 }) 357 } 358 }) 359 } 360 } 361 362 func TestE2E_host_functions(t *testing.T) { 363 var buf bytes.Buffer 364 ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&buf)) 365 366 for _, tc := range []struct { 367 name string 368 ctx context.Context 369 }{ 370 {name: "listener", ctx: ctx}, 371 {name: "no listener", ctx: context.Background()}, 372 } { 373 tc := tc 374 t.Run(tc.name, func(t *testing.T) { 375 ctx := tc.ctx 376 377 config := opt.NewRuntimeConfigOptimizingCompiler() 378 379 r := wazero.NewRuntimeWithConfig(ctx, config) 380 defer func() { 381 require.NoError(t, r.Close(ctx)) 382 }() 383 384 var expectedMod api.Module 385 386 b := r.NewHostModuleBuilder("env") 387 b.NewFunctionBuilder().WithFunc(func(ctx2 context.Context, d float64) float64 { 388 require.Equal(t, ctx, ctx2) 389 require.Equal(t, 35.0, d) 390 return math.Sqrt(d) 391 }).Export("root") 392 b.NewFunctionBuilder().WithFunc(func(ctx2 context.Context, mod api.Module, a uint32, b uint64, c float32, d float64) (uint32, uint64, float32, float64) { 393 require.Equal(t, expectedMod, mod) 394 require.Equal(t, ctx, ctx2) 395 require.Equal(t, uint32(2), a) 396 require.Equal(t, uint64(100), b) 397 require.Equal(t, float32(15.0), c) 398 require.Equal(t, 35.0, d) 399 return a * a, b * b, c * c, d * d 400 }).Export("square") 401 402 _, err := b.Instantiate(ctx) 403 require.NoError(t, err) 404 405 m := &wasm.Module{ 406 ImportFunctionCount: 2, 407 ImportSection: []wasm.Import{ 408 {Module: "env", Name: "root", Type: wasm.ExternTypeFunc, DescFunc: 0}, 409 {Module: "env", Name: "square", Type: wasm.ExternTypeFunc, DescFunc: 1}, 410 }, 411 TypeSection: []wasm.FunctionType{ 412 {Results: []wasm.ValueType{f64}, Params: []wasm.ValueType{f64}}, 413 {Results: []wasm.ValueType{i32, i64, f32, f64}, Params: []wasm.ValueType{i32, i64, f32, f64}}, 414 {Results: []wasm.ValueType{i32, i64, f32, f64, f64}, Params: []wasm.ValueType{i32, i64, f32, f64}}, 415 }, 416 FunctionSection: []wasm.Index{2}, 417 CodeSection: []wasm.Code{{ 418 Body: []byte{ 419 wasm.OpcodeLocalGet, 0, wasm.OpcodeLocalGet, 1, wasm.OpcodeLocalGet, 2, wasm.OpcodeLocalGet, 3, 420 wasm.OpcodeCall, 1, 421 wasm.OpcodeLocalGet, 3, 422 wasm.OpcodeCall, 0, 423 wasm.OpcodeEnd, 424 }, 425 }}, 426 ExportSection: []wasm.Export{{Name: testcases.ExportedFunctionName, Type: wasm.ExternTypeFunc, Index: 2}}, 427 } 428 429 compiled, err := r.CompileModule(ctx, binaryencoding.EncodeModule(m)) 430 require.NoError(t, err) 431 432 inst, err := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig()) 433 require.NoError(t, err) 434 435 expectedMod = inst 436 437 f := inst.ExportedFunction(testcases.ExportedFunctionName) 438 439 res, err := f.Call(ctx, []uint64{2, 100, uint64(math.Float32bits(15.0)), math.Float64bits(35.0)}...) 440 require.NoError(t, err) 441 require.Equal(t, []uint64{ 442 2 * 2, 100 * 100, uint64(math.Float32bits(15.0 * 15.0)), math.Float64bits(35.0 * 35.0), 443 math.Float64bits(math.Sqrt(35.0)), 444 }, res) 445 }) 446 } 447 448 require.Equal(t, ` 449 --> .$2(2,100,15,35) 450 ==> env.square(2,100,15,35) 451 <== (4,10000,225,1225) 452 ==> env.root(35) 453 <== 5.916079783099616 454 <-- (4,10000,225,1225,5.916079783099616) 455 `, "\n"+buf.String()) 456 } 457 458 func TestE2E_stores(t *testing.T) { 459 config := opt.NewRuntimeConfigOptimizingCompiler() 460 461 ctx := context.Background() 462 r := wazero.NewRuntimeWithConfig(ctx, config) 463 defer func() { 464 require.NoError(t, r.Close(ctx)) 465 }() 466 467 compiled, err := r.CompileModule(ctx, binaryencoding.EncodeModule(testcases.MemoryStores.Module)) 468 require.NoError(t, err) 469 470 inst, err := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig()) 471 require.NoError(t, err) 472 473 f := inst.ExportedFunction(testcases.ExportedFunctionName) 474 475 mem, ok := inst.Memory().Read(0, wasm.MemoryPageSize) 476 require.True(t, ok) 477 for _, tc := range []struct { 478 i32 uint32 479 i64 uint64 480 f32 float32 481 f64 float64 482 }{ 483 {0, 0, 0, 0}, 484 {1, 2, 3.0, 4.0}, 485 {math.MaxUint32, math.MaxUint64, float32(math.NaN()), math.NaN()}, 486 {1 << 31, 1 << 63, 3.0, 4.0}, 487 } { 488 t.Run(fmt.Sprintf("i32=%#x,i64=%#x,f32=%#x,f64=%#x", tc.i32, tc.i64, tc.f32, tc.f64), func(t *testing.T) { 489 _, err = f.Call(ctx, []uint64{uint64(tc.i32), tc.i64, uint64(math.Float32bits(tc.f32)), math.Float64bits(tc.f64)}...) 490 require.NoError(t, err) 491 492 offset := 0 493 require.Equal(t, binary.LittleEndian.Uint32(mem[offset:]), tc.i32) 494 offset += 8 495 require.Equal(t, binary.LittleEndian.Uint64(mem[offset:]), tc.i64) 496 offset += 8 497 require.Equal(t, math.Float32bits(tc.f32), binary.LittleEndian.Uint32(mem[offset:])) 498 offset += 8 499 require.Equal(t, math.Float64bits(tc.f64), binary.LittleEndian.Uint64(mem[offset:])) 500 offset += 8 501 502 // i32.store_8 503 view := binary.LittleEndian.Uint64(mem[offset:]) 504 require.Equal(t, uint64(tc.i32)&0xff, view) 505 offset += 8 506 // i32.store_16 507 view = binary.LittleEndian.Uint64(mem[offset:]) 508 require.Equal(t, uint64(tc.i32)&0xffff, view) 509 offset += 8 510 // i64.store_8 511 view = binary.LittleEndian.Uint64(mem[offset:]) 512 require.Equal(t, tc.i64&0xff, view) 513 offset += 8 514 // i64.store_16 515 view = binary.LittleEndian.Uint64(mem[offset:]) 516 require.Equal(t, tc.i64&0xffff, view) 517 offset += 8 518 // i64.store_32 519 view = binary.LittleEndian.Uint64(mem[offset:]) 520 require.Equal(t, tc.i64&0xffffffff, view) 521 }) 522 } 523 } 524 525 func TestE2E_reexported_memory(t *testing.T) { 526 m1 := &wasm.Module{ 527 ExportSection: []wasm.Export{{Name: "mem", Type: wasm.ExternTypeMemory, Index: 0}}, 528 MemorySection: &wasm.Memory{Min: 1}, 529 NameSection: &wasm.NameSection{ModuleName: "m1"}, 530 } 531 m2 := &wasm.Module{ 532 ImportMemoryCount: 1, 533 ExportSection: []wasm.Export{{Name: "mem2", Type: wasm.ExternTypeMemory, Index: 0}}, 534 ImportSection: []wasm.Import{{Module: "m1", Name: "mem", Type: wasm.ExternTypeMemory, DescMem: &wasm.Memory{Min: 1}}}, 535 NameSection: &wasm.NameSection{ModuleName: "m2"}, 536 } 537 m3 := &wasm.Module{ 538 ImportMemoryCount: 1, 539 ImportSection: []wasm.Import{{Module: "m2", Name: "mem2", Type: wasm.ExternTypeMemory, DescMem: &wasm.Memory{Min: 1}}}, 540 TypeSection: []wasm.FunctionType{{Results: []wasm.ValueType{i32}}}, 541 ExportSection: []wasm.Export{{Name: testcases.ExportedFunctionName, Type: wasm.ExternTypeFunc, Index: 0}}, 542 FunctionSection: []wasm.Index{0}, 543 CodeSection: []wasm.Code{{Body: []byte{wasm.OpcodeI32Const, 10, wasm.OpcodeMemoryGrow, 0, wasm.OpcodeEnd}}}, 544 } 545 546 config := opt.NewRuntimeConfigOptimizingCompiler() 547 548 ctx := context.Background() 549 r := wazero.NewRuntimeWithConfig(ctx, config) 550 defer func() { 551 require.NoError(t, r.Close(ctx)) 552 }() 553 554 m1Inst, err := r.Instantiate(ctx, binaryencoding.EncodeModule(m1)) 555 require.NoError(t, err) 556 557 m2Inst, err := r.Instantiate(ctx, binaryencoding.EncodeModule(m2)) 558 require.NoError(t, err) 559 560 m3Inst, err := r.Instantiate(ctx, binaryencoding.EncodeModule(m3)) 561 require.NoError(t, err) 562 563 f := m3Inst.ExportedFunction(testcases.ExportedFunctionName) 564 result, err := f.Call(ctx) 565 require.NoError(t, err) 566 require.Equal(t, uint64(1), result[0]) 567 mem := m1Inst.Memory() 568 require.Equal(t, mem, m3Inst.Memory()) 569 require.Equal(t, mem, m2Inst.Memory()) 570 require.Equal(t, uint32(11), mem.Size()/65536) 571 } 572 573 func TestStackUnwind_panic_in_host(t *testing.T) { 574 unreachable := &wasm.Module{ 575 ImportFunctionCount: 1, 576 ImportSection: []wasm.Import{{Module: "host", Name: "cause_unreachable", Type: wasm.ExternTypeFunc, DescFunc: 0}}, 577 TypeSection: []wasm.FunctionType{{}}, 578 ExportSection: []wasm.Export{{Name: "main", Type: wasm.ExternTypeFunc, Index: 1}}, 579 FunctionSection: []wasm.Index{0, 0, 0}, 580 CodeSection: []wasm.Code{ 581 {Body: []byte{wasm.OpcodeCall, 2, wasm.OpcodeEnd}}, 582 {Body: []byte{wasm.OpcodeCall, 3, wasm.OpcodeEnd}}, 583 {Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, // call host.cause_unreachable. 584 }, 585 NameSection: &wasm.NameSection{ 586 FunctionNames: wasm.NameMap{ 587 wasm.NameAssoc{Index: 0, Name: "host.unreachable"}, 588 wasm.NameAssoc{Index: 1, Name: "main"}, 589 wasm.NameAssoc{Index: 2, Name: "one"}, 590 wasm.NameAssoc{Index: 3, Name: "two"}, 591 }, 592 }, 593 } 594 595 config := opt.NewRuntimeConfigOptimizingCompiler() 596 597 ctx := context.Background() 598 r := wazero.NewRuntimeWithConfig(ctx, config) 599 defer func() { 600 require.NoError(t, r.Close(ctx)) 601 }() 602 603 callUnreachable := func() { 604 panic("panic in host function") 605 } 606 607 _, err := r.NewHostModuleBuilder("host"). 608 NewFunctionBuilder().WithFunc(callUnreachable).Export("cause_unreachable"). 609 Instantiate(ctx) 610 require.NoError(t, err) 611 612 module, err := r.Instantiate(ctx, binaryencoding.EncodeModule(unreachable)) 613 require.NoError(t, err) 614 defer module.Close(ctx) 615 616 _, err = module.ExportedFunction("main").Call(ctx) 617 exp := `panic in host function (recovered by wazero) 618 wasm stack trace: 619 host.cause_unreachable() 620 .two() 621 .one() 622 .main()` 623 require.Equal(t, exp, err.Error()) 624 } 625 626 func TestStackUnwind_unreachable(t *testing.T) { 627 unreachable := &wasm.Module{ 628 TypeSection: []wasm.FunctionType{{}}, 629 ExportSection: []wasm.Export{{Name: "main", Type: wasm.ExternTypeFunc, Index: 0}}, 630 FunctionSection: []wasm.Index{0, 0, 0}, 631 CodeSection: []wasm.Code{ 632 {Body: []byte{wasm.OpcodeCall, 1, wasm.OpcodeEnd}}, 633 {Body: []byte{wasm.OpcodeCall, 2, wasm.OpcodeEnd}}, 634 {Body: []byte{wasm.OpcodeUnreachable, wasm.OpcodeEnd}}, 635 }, 636 NameSection: &wasm.NameSection{ 637 FunctionNames: wasm.NameMap{ 638 wasm.NameAssoc{Index: 0, Name: "main"}, 639 wasm.NameAssoc{Index: 1, Name: "one"}, 640 wasm.NameAssoc{Index: 2, Name: "two"}, 641 }, 642 }, 643 } 644 645 config := opt.NewRuntimeConfigOptimizingCompiler() 646 ctx := context.Background() 647 r := wazero.NewRuntimeWithConfig(ctx, config) 648 defer func() { 649 require.NoError(t, r.Close(ctx)) 650 }() 651 652 module, err := r.Instantiate(ctx, binaryencoding.EncodeModule(unreachable)) 653 require.NoError(t, err) 654 defer module.Close(ctx) 655 656 _, err = module.ExportedFunction("main").Call(ctx) 657 exp := `wasm error: unreachable 658 wasm stack trace: 659 .two() 660 .one() 661 .main()` 662 require.Equal(t, exp, err.Error()) 663 } 664 665 func TestListener_local(t *testing.T) { 666 var buf bytes.Buffer 667 config := opt.NewRuntimeConfigOptimizingCompiler() 668 ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&buf)) 669 670 r := wazero.NewRuntimeWithConfig(ctx, config) 671 defer func() { 672 require.NoError(t, r.Close(ctx)) 673 }() 674 675 compiled, err := r.CompileModule(ctx, binaryencoding.EncodeModule(testcases.CallIndirect.Module)) 676 require.NoError(t, err) 677 678 inst, err := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig()) 679 require.NoError(t, err) 680 681 res, err := inst.ExportedFunction(testcases.ExportedFunctionName).Call(ctx, 1) 682 require.NoError(t, err) 683 require.Equal(t, []uint64{10}, res) 684 685 require.Equal(t, ` 686 --> .$0(1) 687 --> .$2() 688 <-- 10 689 <-- 10 690 `, "\n"+buf.String()) 691 } 692 693 func TestListener_imported(t *testing.T) { 694 var buf bytes.Buffer 695 config := opt.NewRuntimeConfigOptimizingCompiler() 696 ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&buf)) 697 698 r := wazero.NewRuntimeWithConfig(ctx, config) 699 defer func() { 700 require.NoError(t, r.Close(ctx)) 701 }() 702 703 _, err := r.Instantiate(ctx, binaryencoding.EncodeModule(testcases.ImportedFunctionCall.Imported)) 704 require.NoError(t, err) 705 706 compiled, err := r.CompileModule(ctx, binaryencoding.EncodeModule(testcases.ImportedFunctionCall.Module)) 707 require.NoError(t, err) 708 709 inst, err := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig()) 710 require.NoError(t, err) 711 712 res, err := inst.ExportedFunction(testcases.ExportedFunctionName).Call(ctx, 100) 713 require.NoError(t, err) 714 require.Equal(t, []uint64{10000}, res) 715 716 require.Equal(t, ` 717 --> .$1(100) 718 --> env.$0(100,100) 719 <-- 10000 720 <-- 10000 721 `, "\n"+buf.String()) 722 } 723 724 func TestListener_long(t *testing.T) { 725 pickOneParam := binaryencoding.EncodeModule(&wasm.Module{ 726 TypeSection: []wasm.FunctionType{{Results: []wasm.ValueType{i32}, Params: []wasm.ValueType{ 727 i32, i32, f32, f64, i64, i32, i32, v128, f32, 728 i32, i32, f32, f64, i64, i32, i32, v128, f32, 729 i32, i32, f32, f64, i64, i32, i32, v128, f32, 730 i32, i32, f32, f64, i64, i32, i32, v128, f32, 731 i32, i32, f32, f64, i64, i32, i32, v128, f32, 732 i32, i32, f32, f64, i64, i32, i32, v128, f32, 733 i32, i32, f32, f64, i64, i32, i32, v128, f32, 734 i32, i32, f32, f64, i64, i32, i32, v128, f32, 735 i32, i32, f32, f64, i64, i32, i32, v128, f32, 736 i32, i32, f32, f64, i64, i32, i32, v128, f32, 737 }}}, 738 ExportSection: []wasm.Export{{Name: "main", Type: wasm.ExternTypeFunc, Index: 0}}, 739 FunctionSection: []wasm.Index{0}, 740 CodeSection: []wasm.Code{ 741 {Body: []byte{wasm.OpcodeLocalGet, 10, wasm.OpcodeEnd}}, 742 }, 743 }) 744 745 var buf bytes.Buffer 746 config := opt.NewRuntimeConfigOptimizingCompiler() 747 ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&buf)) 748 749 r := wazero.NewRuntimeWithConfig(ctx, config) 750 defer func() { 751 require.NoError(t, r.Close(ctx)) 752 }() 753 754 inst, err := r.Instantiate(ctx, pickOneParam) 755 require.NoError(t, err) 756 757 f := inst.ExportedFunction("main") 758 require.NotNil(t, f) 759 param := make([]uint64, 100) 760 for i := range param { 761 param[i] = uint64(i) 762 } 763 res, err := f.Call(ctx, param...) 764 require.NoError(t, err) 765 require.Equal(t, []uint64{0xb}, res) 766 767 require.Equal(t, ` 768 --> .$0(0,1,3e-45,1.5e-323,4,5,6,00000000000000070000000000000008,1.1e-44,9,10,1.5e-44,6e-323,13,14,15,00000000000000100000000000000011,2.4e-44,18,19,2.8e-44,1.04e-322,22,23,24,0000000000000019000000000000001a,3.6e-44,27,28,4e-44,1.5e-322,31,32,33,00000000000000220000000000000023,4.9e-44,36,37,5.3e-44,1.93e-322,40,41,42,000000000000002b000000000000002c,6.2e-44,45,46,6.6e-44,2.37e-322,49,50,51,00000000000000340000000000000035,7.4e-44,54,55,7.8e-44,2.8e-322,58,59,60,000000000000003d000000000000003e,8.7e-44,63,64,9.1e-44,3.26e-322,67,68,69,00000000000000460000000000000047,1e-43,72,73,1.04e-43,3.7e-322,76,77,78,000000000000004f0000000000000050,1.12e-43,81,82,1.16e-43,4.15e-322,85,86,87,00000000000000580000000000000059,1.25e-43) 769 <-- 11 770 `, "\n"+buf.String()) 771 } 772 773 func TestListener_long_as_is(t *testing.T) { 774 params := []wasm.ValueType{ 775 i32, i64, i32, i64, i32, i64, i32, i64, i32, i64, 776 i32, i64, i32, i64, i32, i64, i32, i64, i32, i64, 777 } 778 779 const paramNum = 20 780 781 var body []byte 782 for i := 0; i < paramNum; i++ { 783 body = append(body, wasm.OpcodeLocalGet) 784 body = append(body, leb128.EncodeUint32(uint32(i))...) 785 } 786 body = append(body, wasm.OpcodeEnd) 787 788 bin := binaryencoding.EncodeModule(&wasm.Module{ 789 TypeSection: []wasm.FunctionType{{Results: params, Params: params}}, 790 ExportSection: []wasm.Export{{Name: "main", Type: wasm.ExternTypeFunc, Index: 0}}, 791 FunctionSection: []wasm.Index{0}, 792 CodeSection: []wasm.Code{{Body: body}}, 793 }) 794 795 var buf bytes.Buffer 796 config := opt.NewRuntimeConfigOptimizingCompiler() 797 ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&buf)) 798 799 r := wazero.NewRuntimeWithConfig(ctx, config) 800 defer func() { 801 require.NoError(t, r.Close(ctx)) 802 }() 803 804 inst, err := r.Instantiate(ctx, bin) 805 require.NoError(t, err) 806 807 f := inst.ExportedFunction("main") 808 require.NotNil(t, f) 809 param := make([]uint64, paramNum) 810 for i := range param { 811 param[i] = uint64(i) 812 } 813 res, err := f.Call(ctx, param...) 814 require.NoError(t, err) 815 require.Equal(t, param, res) 816 817 require.Equal(t, ` 818 --> .$0(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19) 819 <-- (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19) 820 `, "\n"+buf.String()) 821 } 822 823 func TestListener_long_many_consts(t *testing.T) { 824 const paramNum = 61 825 826 var exp []uint64 827 var body []byte 828 var resultTypes []wasm.ValueType 829 for i := 0; i < paramNum; i++ { 830 exp = append(exp, uint64(i)) 831 resultTypes = append(resultTypes, i32) 832 body = append(body, wasm.OpcodeI32Const) 833 body = append(body, leb128.EncodeInt32(int32(i))...) 834 } 835 body = append(body, wasm.OpcodeEnd) 836 837 bin := binaryencoding.EncodeModule(&wasm.Module{ 838 TypeSection: []wasm.FunctionType{{Results: resultTypes}}, 839 ExportSection: []wasm.Export{{Name: "main", Type: wasm.ExternTypeFunc, Index: 0}}, 840 FunctionSection: []wasm.Index{0}, 841 CodeSection: []wasm.Code{{Body: body}}, 842 }) 843 844 var buf bytes.Buffer 845 config := opt.NewRuntimeConfigOptimizingCompiler() 846 ctx := context.WithValue(context.Background(), experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&buf)) 847 848 r := wazero.NewRuntimeWithConfig(ctx, config) 849 defer func() { 850 require.NoError(t, r.Close(ctx)) 851 }() 852 853 inst, err := r.Instantiate(ctx, bin) 854 require.NoError(t, err) 855 856 f := inst.ExportedFunction("main") 857 require.NotNil(t, f) 858 res, err := f.Call(ctx) 859 require.NoError(t, err) 860 require.Equal(t, exp, res) 861 862 require.Equal(t, ` 863 --> .$0() 864 <-- (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60) 865 `, "\n"+buf.String()) 866 } 867 868 // TestDWARF verifies that the DWARF based stack traces work as expected before/after compilation cache. 869 func TestDWARF(t *testing.T) { 870 config := opt.NewRuntimeConfigOptimizingCompiler() 871 ctx := context.Background() 872 873 bin := dwarftestdata.ZigWasm 874 875 dir := t.TempDir() 876 877 var expErr error 878 { 879 cc, err := wazero.NewCompilationCacheWithDir(dir) 880 require.NoError(t, err) 881 rc := config.WithCompilationCache(cc) 882 883 r := wazero.NewRuntimeWithConfig(ctx, rc) 884 _, expErr = r.Instantiate(ctx, bin) 885 require.Error(t, expErr) 886 887 err = r.Close(ctx) 888 require.NoError(t, err) 889 } 890 891 cc, err := wazero.NewCompilationCacheWithDir(dir) 892 require.NoError(t, err) 893 rc := config.WithCompilationCache(cc) 894 r := wazero.NewRuntimeWithConfig(ctx, rc) 895 _, err = r.Instantiate(ctx, bin) 896 require.Error(t, err) 897 require.Equal(t, expErr.Error(), err.Error()) 898 899 err = r.Close(ctx) 900 require.NoError(t, err) 901 }