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