This stops gathering `NonStaticLocals` during validation phase,
which was previously used to do the "fast pass" on variable search
by the frontend. However, it had no impact after the last mile refactoring
included in 1.7.0 and caused tons of allocations.
As as result, you can see the compilation perf improvements especially
around memory pressure without any impacts on the runtime perf
### Zig
```
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/stdlibs
│ old_zig.txt │ new_zig.txt │
│ sec/op │ sec/op vs base │
Zig/Compile/test-opt.wasm-10 3.769 ± 1% 3.761 ± 1% ~ (p=0.485 n=6)
Zig/Run/test-opt.wasm-10 18.78 ± 1% 18.74 ± 0% -0.21% (p=0.041 n=6)
Zig/Compile/test.wasm-10 4.677 ± 1% 4.515 ± 0% -3.48% (p=0.002 n=6)
Zig/Run/test.wasm-10 19.31 ± 1% 19.27 ± 1% ~ (p=1.000 n=6)
geomean 8.942 8.850 -1.04%
│ old_zig.txt │ new_zig.txt │
│ B/op │ B/op vs base │
Zig/Compile/test-opt.wasm-10 394.7Mi ± 0% 393.5Mi ± 0% -0.30% (p=0.002 n=6)
Zig/Run/test-opt.wasm-10 741.7Mi ± 0% 741.7Mi ± 0% ~ (p=0.621 n=6)
Zig/Compile/test.wasm-10 659.5Mi ± 0% 599.3Mi ± 0% -9.12% (p=0.002 n=6)
Zig/Run/test.wasm-10 1.296Gi ± 0% 1.296Gi ± 0% ~ (p=0.102 n=6)
geomean 711.5Mi 694.2Mi -2.44%
│ old_zig.txt │ new_zig.txt │
│ allocs/op │ allocs/op vs base │
Zig/Compile/test-opt.wasm-10 362.6k ± 0% 343.2k ± 0% -5.34% (p=0.002 n=6)
Zig/Run/test-opt.wasm-10 51.58k ± 0% 51.58k ± 0% ~ (p=0.978 n=6)
Zig/Compile/test.wasm-10 514.7k ± 0% 288.1k ± 0% -44.04% (p=0.002 n=6)
Zig/Run/test.wasm-10 2.156M ± 0% 2.156M ± 0% ~ (p=0.273 n=6)
geomean 379.5k 323.8k -14.69%
```
### TinyGo
```
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/stdlibs
│ old_tinygo.txt │ new_tinygo.txt │
│ sec/op │ sec/op vs base │
TinyGo/Compile/container_heap.test-10 410.8m ± 1% 399.8m ± 0% -2.69% (p=0.001 n=7)
TinyGo/Run/container_heap.test-10 14.41m ± 0% 14.29m ± 2% -0.77% (p=0.026 n=7)
TinyGo/Compile/container_list.test-10 410.5m ± 1% 398.1m ± 0% -3.02% (p=0.001 n=7)
TinyGo/Run/container_list.test-10 14.27m ± 2% 14.16m ± 1% ~ (p=0.073 n=7)
TinyGo/Compile/container_ring.test-10 403.7m ± 1% 392.5m ± 2% -2.77% (p=0.001 n=7)
TinyGo/Run/container_ring.test-10 14.24m ± 0% 14.27m ± 1% ~ (p=0.259 n=7)
TinyGo/Compile/crypto_des.test-10 418.8m ± 0% 408.1m ± 0% -2.56% (p=0.001 n=7)
TinyGo/Run/crypto_des.test-10 18.23m ± 0% 18.17m ± 1% ~ (p=0.456 n=7)
TinyGo/Compile/crypto_md5.test-10 417.3m ± 2% 406.1m ± 1% -2.68% (p=0.001 n=7)
TinyGo/Run/crypto_md5.test-10 20.50m ± 0% 20.45m ± 1% ~ (p=0.128 n=7)
TinyGo/Compile/crypto_rc4.test-10 402.2m ± 1% 390.5m ± 0% -2.90% (p=0.001 n=7)
TinyGo/Run/crypto_rc4.test-10 160.8m ± 0% 161.0m ± 1% ~ (p=1.000 n=7)
TinyGo/Compile/crypto_sha1.test-10 417.2m ± 1% 404.5m ± 1% -3.04% (p=0.001 n=7)
TinyGo/Run/crypto_sha1.test-10 15.93m ± 1% 15.90m ± 1% ~ (p=0.710 n=7)
TinyGo/Compile/crypto_sha256.test-10 423.4m ± 1% 412.4m ± 1% -2.60% (p=0.001 n=7)
TinyGo/Run/crypto_sha256.test-10 16.16m ± ∞ ¹ 16.05m ± ∞ ¹ ~ (p=0.381 n=2+5)
geomean 94.17m 92.70m -1.56%
¹ need >= 6 samples for confidence interval at level 0.95
│ old_tinygo.txt │ new_tinygo.txt │
│ B/op │ B/op vs base │
TinyGo/Compile/container_heap.test-10 48.55Mi ± 0% 48.30Mi ± 0% -0.52% (p=0.001 n=7)
TinyGo/Run/container_heap.test-10 16.63Mi ± 0% 16.63Mi ± 0% ~ (p=0.557 n=7)
TinyGo/Compile/container_list.test-10 48.53Mi ± 0% 48.29Mi ± 0% -0.51% (p=0.001 n=7)
TinyGo/Run/container_list.test-10 16.40Mi ± 0% 16.40Mi ± 0% ~ (p=0.364 n=7)
TinyGo/Compile/container_ring.test-10 47.78Mi ± 0% 47.53Mi ± 0% -0.52% (p=0.001 n=7)
TinyGo/Run/container_ring.test-10 16.30Mi ± 0% 16.30Mi ± 0% ~ (p=0.128 n=7)
TinyGo/Compile/crypto_des.test-10 48.67Mi ± 0% 48.42Mi ± 0% -0.51% (p=0.001 n=7)
TinyGo/Run/crypto_des.test-10 16.76Mi ± 0% 16.76Mi ± 0% ~ (p=0.902 n=7)
TinyGo/Compile/crypto_md5.test-10 48.73Mi ± 0% 48.48Mi ± 0% -0.51% (p=0.001 n=7)
TinyGo/Run/crypto_md5.test-10 44.97Mi ± 0% 44.97Mi ± 0% ~ (p=0.402 n=7)
TinyGo/Compile/crypto_rc4.test-10 47.76Mi ± 0% 47.52Mi ± 0% -0.51% (p=0.001 n=7)
TinyGo/Run/crypto_rc4.test-10 29.28Mi ± 0% 29.28Mi ± 0% ~ (p=0.104 n=7)
TinyGo/Compile/crypto_sha1.test-10 48.97Mi ± 0% 48.72Mi ± 0% -0.52% (p=0.001 n=7)
TinyGo/Run/crypto_sha1.test-10 17.44Mi ± 0% 17.44Mi ± 0% ~ (p=1.000 n=7)
TinyGo/Compile/crypto_sha256.test-10 48.81Mi ± 0% 48.56Mi ± 0% -0.51% (p=0.001 n=7)
TinyGo/Run/crypto_sha256.test-10 17.53Mi ± ∞ ¹ 17.53Mi ± ∞ ¹ ~ (p=0.381 n=2+5)
geomean 31.45Mi 31.37Mi -0.26%
¹ need >= 6 samples for confidence interval at level 0.95
│ old_tinygo.txt │ new_tinygo.txt │
│ allocs/op │ allocs/op vs base │
TinyGo/Compile/container_heap.test-10 83.67k ± 0% 83.46k ± 0% -0.25% (p=0.011 n=7)
TinyGo/Run/container_heap.test-10 374.9k ± 0% 374.9k ± 0% ~ (p=1.000 n=7)
TinyGo/Compile/container_list.test-10 83.34k ± 0% 83.19k ± 0% -0.19% (p=0.002 n=7)
TinyGo/Run/container_list.test-10 370.0k ± 0% 370.0k ± 0% ~ (p=0.674 n=7)
TinyGo/Compile/container_ring.test-10 83.26k ± 0% 83.08k ± 0% -0.22% (p=0.004 n=7)
TinyGo/Run/container_ring.test-10 367.6k ± 0% 367.6k ± 0% ~ (p=0.249 n=7)
TinyGo/Compile/crypto_des.test-10 83.68k ± 0% 83.53k ± 0% -0.18% (p=0.004 n=7)
TinyGo/Run/crypto_des.test-10 378.1k ± 0% 378.1k ± 0% ~ (p=0.437 n=7)
TinyGo/Compile/crypto_md5.test-10 83.86k ± 0% 83.67k ± 0% -0.23% (p=0.001 n=7)
TinyGo/Run/crypto_md5.test-10 393.3k ± 0% 393.3k ± 0% ~ (p=0.592 n=7)
TinyGo/Compile/crypto_rc4.test-10 83.32k ± 0% 83.20k ± 0% -0.14% (p=0.011 n=7)
TinyGo/Run/crypto_rc4.test-10 367.1k ± 0% 367.1k ± 0% ~ (p=0.102 n=7)
TinyGo/Compile/crypto_sha1.test-10 84.05k ± 0% 83.87k ± 0% -0.21% (p=0.002 n=7)
TinyGo/Run/crypto_sha1.test-10 392.7k ± 0% 392.7k ± 0% ~ (p=1.000 n=7)
TinyGo/Compile/crypto_sha256.test-10 83.86k ± 0% 83.67k ± 0% -0.24% (p=0.001 n=7)
TinyGo/Run/crypto_sha256.test-10 394.5k ± ∞ ¹ 394.5k ± ∞ ¹ ~ (p=0.952 n=2+5)
geomean 178.2k 178.0k -0.10%
```
### wasip1
```
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/stdlibs
│ old_wasip1.txt │ new_wasip1.txt │
│ sec/op │ sec/op vs base │
Wasip1/Compile/src_archive_tar.test-10 2.066 ± 1% 2.066 ± 1% ~ (p=1.000 n=7)
Wasip1/Run/src_archive_tar.test-10 398.9m ± 1% 398.9m ± 0% ~ (p=0.902 n=7)
Wasip1/Compile/src_bufio.test-10 1.405 ± 0% 1.405 ± 0% ~ (p=0.318 n=7)
Wasip1/Run/src_bufio.test-10 120.1m ± 0% 120.0m ± 0% ~ (p=0.456 n=7)
Wasip1/Compile/src_bytes.test-10 1.453 ± 0% 1.452 ± 0% ~ (p=0.383 n=7)
Wasip1/Run/src_bytes.test-10 468.9m ± 1% 467.7m ± 1% ~ (p=1.000 n=7)
Wasip1/Compile/src_context.test-10 1.565 ± 0% 1.562 ± 0% -0.18% (p=0.001 n=7)
Wasip1/Run/src_context.test-10 31.52m ± 1% 31.51m ± 1% ~ (p=0.620 n=7)
Wasip1/Compile/src_encoding_ascii85.test-10 1.262 ± ∞ ¹ 1.262 ± 0% ~ (p=0.889 n=2+7)
geomean 565.3m 564.9m -0.07%
¹ need >= 6 samples for confidence interval at level 0.95
│ old_wasip1.txt │ new_wasip1.txt │
│ B/op │ B/op vs base │
Wasip1/Compile/src_archive_tar.test-10 93.16Mi ± 0% 92.70Mi ± 0% -0.50% (p=0.001 n=7)
Wasip1/Run/src_archive_tar.test-10 286.0Mi ± 0% 286.0Mi ± 0% ~ (p=0.246 n=7)
Wasip1/Compile/src_bufio.test-10 74.12Mi ± 0% 73.79Mi ± 0% -0.45% (p=0.001 n=7)
Wasip1/Run/src_bufio.test-10 105.3Mi ± 0% 105.3Mi ± 0% ~ (p=0.780 n=7)
Wasip1/Compile/src_bytes.test-10 75.32Mi ± 0% 74.96Mi ± 0% -0.47% (p=0.001 n=7)
Wasip1/Run/src_bytes.test-10 605.0Mi ± 0% 605.0Mi ± 0% ~ (p=1.000 n=7)
Wasip1/Compile/src_context.test-10 78.07Mi ± 0% 77.68Mi ± 0% -0.49% (p=0.001 n=7)
Wasip1/Run/src_context.test-10 71.52Mi ± 0% 71.52Mi ± 0% ~ (p=0.516 n=7)
Wasip1/Compile/src_encoding_ascii85.test-10 70.38Mi ± ∞ ¹ 70.08Mi ± 0% ~ (p=0.056 n=2+7)
geomean 115.7Mi 115.4Mi -0.26%
¹ need >= 6 samples for confidence interval at level 0.95
│ old_wasip1.txt │ new_wasip1.txt │
│ allocs/op │ allocs/op vs base │
Wasip1/Compile/src_archive_tar.test-10 265.0k ± 0% 256.1k ± 0% -3.37% (p=0.001 n=7)
Wasip1/Run/src_archive_tar.test-10 7.831k ± 0% 7.830k ± 0% ~ (p=0.592 n=7)
Wasip1/Compile/src_bufio.test-10 195.3k ± 0% 189.1k ± 0% -3.19% (p=0.001 n=7)
Wasip1/Run/src_bufio.test-10 3.728k ± 0% 3.728k ± 0% ~ (p=1.000 n=7) ¹
Wasip1/Compile/src_bytes.test-10 203.7k ± 0% 197.0k ± 0% -3.31% (p=0.001 n=7)
Wasip1/Run/src_bytes.test-10 6.377k ± 0% 6.377k ± 0% ~ (p=0.559 n=7)
Wasip1/Compile/src_context.test-10 221.4k ± 0% 214.2k ± 0% -3.29% (p=0.001 n=7)
Wasip1/Run/src_context.test-10 3.814k ± 1% 3.814k ± 0% ~ (p=0.192 n=7)
Wasip1/Compile/src_encoding_ascii85.test-10 182.3k ± ∞ ² 176.6k ± 0% ~ (p=0.056 n=2+7)
geomean 40.64k 39.90k -1.82%
```
Signed-off-by: Takeshi Yoneda <t.y.mathetake@gmail.com>
4261 lines
134 KiB
Go
4261 lines
134 KiB
Go
package frontend
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"math"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"github.com/tetratelabs/wazero/api"
|
|
"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
|
|
"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
|
|
"github.com/tetratelabs/wazero/internal/leb128"
|
|
"github.com/tetratelabs/wazero/internal/wasm"
|
|
)
|
|
|
|
type (
|
|
// loweringState is used to keep the state of lowering.
|
|
loweringState struct {
|
|
// values holds the values on the Wasm stack.
|
|
values []ssa.Value
|
|
controlFrames []controlFrame
|
|
unreachable bool
|
|
unreachableDepth int
|
|
tmpForBrTable []uint32
|
|
pc int
|
|
}
|
|
controlFrame struct {
|
|
kind controlFrameKind
|
|
// originalStackLen holds the number of values on the Wasm stack
|
|
// when start executing this control frame minus params for the block.
|
|
originalStackLenWithoutParam int
|
|
// blk is the loop header if this is loop, and is the else-block if this is an if frame.
|
|
blk,
|
|
// followingBlock is the basic block we enter if we reach "end" of block.
|
|
followingBlock ssa.BasicBlock
|
|
blockType *wasm.FunctionType
|
|
// clonedArgs hold the arguments to Else block.
|
|
clonedArgs ssa.Values
|
|
}
|
|
|
|
controlFrameKind byte
|
|
)
|
|
|
|
// String implements fmt.Stringer for debugging.
|
|
func (l *loweringState) String() string {
|
|
var str []string
|
|
for _, v := range l.values {
|
|
str = append(str, fmt.Sprintf("v%v", v.ID()))
|
|
}
|
|
var frames []string
|
|
for i := range l.controlFrames {
|
|
frames = append(frames, l.controlFrames[i].kind.String())
|
|
}
|
|
return fmt.Sprintf("\n\tunreachable=%v(depth=%d)\n\tstack: %s\n\tcontrol frames: %s",
|
|
l.unreachable, l.unreachableDepth,
|
|
strings.Join(str, ", "),
|
|
strings.Join(frames, ", "),
|
|
)
|
|
}
|
|
|
|
const (
|
|
controlFrameKindFunction = iota + 1
|
|
controlFrameKindLoop
|
|
controlFrameKindIfWithElse
|
|
controlFrameKindIfWithoutElse
|
|
controlFrameKindBlock
|
|
)
|
|
|
|
// String implements fmt.Stringer for debugging.
|
|
func (k controlFrameKind) String() string {
|
|
switch k {
|
|
case controlFrameKindFunction:
|
|
return "function"
|
|
case controlFrameKindLoop:
|
|
return "loop"
|
|
case controlFrameKindIfWithElse:
|
|
return "if_with_else"
|
|
case controlFrameKindIfWithoutElse:
|
|
return "if_without_else"
|
|
case controlFrameKindBlock:
|
|
return "block"
|
|
default:
|
|
panic(k)
|
|
}
|
|
}
|
|
|
|
// isLoop returns true if this is a loop frame.
|
|
func (ctrl *controlFrame) isLoop() bool {
|
|
return ctrl.kind == controlFrameKindLoop
|
|
}
|
|
|
|
// reset resets the state of loweringState for reuse.
|
|
func (l *loweringState) reset() {
|
|
l.values = l.values[:0]
|
|
l.controlFrames = l.controlFrames[:0]
|
|
l.pc = 0
|
|
l.unreachable = false
|
|
l.unreachableDepth = 0
|
|
}
|
|
|
|
func (l *loweringState) peek() (ret ssa.Value) {
|
|
tail := len(l.values) - 1
|
|
return l.values[tail]
|
|
}
|
|
|
|
func (l *loweringState) pop() (ret ssa.Value) {
|
|
tail := len(l.values) - 1
|
|
ret = l.values[tail]
|
|
l.values = l.values[:tail]
|
|
return
|
|
}
|
|
|
|
func (l *loweringState) push(ret ssa.Value) {
|
|
l.values = append(l.values, ret)
|
|
}
|
|
|
|
func (c *Compiler) nPeekDup(n int) ssa.Values {
|
|
if n == 0 {
|
|
return ssa.ValuesNil
|
|
}
|
|
|
|
l := c.state()
|
|
tail := len(l.values)
|
|
|
|
args := c.allocateVarLengthValues(n)
|
|
args = args.Append(c.ssaBuilder.VarLengthPool(), l.values[tail-n:tail]...)
|
|
return args
|
|
}
|
|
|
|
func (l *loweringState) ctrlPop() (ret controlFrame) {
|
|
tail := len(l.controlFrames) - 1
|
|
ret = l.controlFrames[tail]
|
|
l.controlFrames = l.controlFrames[:tail]
|
|
return
|
|
}
|
|
|
|
func (l *loweringState) ctrlPush(ret controlFrame) {
|
|
l.controlFrames = append(l.controlFrames, ret)
|
|
}
|
|
|
|
func (l *loweringState) ctrlPeekAt(n int) (ret *controlFrame) {
|
|
tail := len(l.controlFrames) - 1
|
|
return &l.controlFrames[tail-n]
|
|
}
|
|
|
|
// lowerBody lowers the body of the Wasm function to the SSA form.
|
|
func (c *Compiler) lowerBody(entryBlk ssa.BasicBlock) {
|
|
c.ssaBuilder.Seal(entryBlk)
|
|
|
|
if c.needListener {
|
|
c.callListenerBefore()
|
|
}
|
|
|
|
// Pushes the empty control frame which corresponds to the function return.
|
|
c.loweringState.ctrlPush(controlFrame{
|
|
kind: controlFrameKindFunction,
|
|
blockType: c.wasmFunctionTyp,
|
|
followingBlock: c.ssaBuilder.ReturnBlock(),
|
|
})
|
|
|
|
for c.loweringState.pc < len(c.wasmFunctionBody) {
|
|
blkBeforeLowering := c.ssaBuilder.CurrentBlock()
|
|
c.lowerCurrentOpcode()
|
|
blkAfterLowering := c.ssaBuilder.CurrentBlock()
|
|
if blkBeforeLowering != blkAfterLowering {
|
|
// In Wasm, once a block exits, that means we've done compiling the block.
|
|
// Therefore, we finalize the known bounds at the end of the block for the exiting block.
|
|
c.finalizeKnownSafeBoundsAtTheEndOfBlock(blkBeforeLowering.ID())
|
|
// After that, we initialize the known bounds for the new compilation target block.
|
|
c.initializeCurrentBlockKnownBounds()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Compiler) state() *loweringState {
|
|
return &c.loweringState
|
|
}
|
|
|
|
func (c *Compiler) lowerCurrentOpcode() {
|
|
op := c.wasmFunctionBody[c.loweringState.pc]
|
|
|
|
if c.needSourceOffsetInfo {
|
|
c.ssaBuilder.SetCurrentSourceOffset(
|
|
ssa.SourceOffset(c.loweringState.pc) + ssa.SourceOffset(c.wasmFunctionBodyOffsetInCodeSection),
|
|
)
|
|
}
|
|
|
|
builder := c.ssaBuilder
|
|
state := c.state()
|
|
switch op {
|
|
case wasm.OpcodeI32Const:
|
|
c := c.readI32s()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
iconst := builder.AllocateInstruction().AsIconst32(uint32(c)).Insert(builder)
|
|
value := iconst.Return()
|
|
state.push(value)
|
|
case wasm.OpcodeI64Const:
|
|
c := c.readI64s()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
iconst := builder.AllocateInstruction().AsIconst64(uint64(c)).Insert(builder)
|
|
value := iconst.Return()
|
|
state.push(value)
|
|
case wasm.OpcodeF32Const:
|
|
f32 := c.readF32()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
f32const := builder.AllocateInstruction().
|
|
AsF32const(f32).
|
|
Insert(builder).
|
|
Return()
|
|
state.push(f32const)
|
|
case wasm.OpcodeF64Const:
|
|
f64 := c.readF64()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
f64const := builder.AllocateInstruction().
|
|
AsF64const(f64).
|
|
Insert(builder).
|
|
Return()
|
|
state.push(f64const)
|
|
case wasm.OpcodeI32Add, wasm.OpcodeI64Add:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
y, x := state.pop(), state.pop()
|
|
iadd := builder.AllocateInstruction()
|
|
iadd.AsIadd(x, y)
|
|
builder.InsertInstruction(iadd)
|
|
value := iadd.Return()
|
|
state.push(value)
|
|
case wasm.OpcodeI32Sub, wasm.OpcodeI64Sub:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
y, x := state.pop(), state.pop()
|
|
isub := builder.AllocateInstruction()
|
|
isub.AsIsub(x, y)
|
|
builder.InsertInstruction(isub)
|
|
value := isub.Return()
|
|
state.push(value)
|
|
case wasm.OpcodeF32Add, wasm.OpcodeF64Add:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
y, x := state.pop(), state.pop()
|
|
iadd := builder.AllocateInstruction()
|
|
iadd.AsFadd(x, y)
|
|
builder.InsertInstruction(iadd)
|
|
value := iadd.Return()
|
|
state.push(value)
|
|
case wasm.OpcodeI32Mul, wasm.OpcodeI64Mul:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
y, x := state.pop(), state.pop()
|
|
imul := builder.AllocateInstruction()
|
|
imul.AsImul(x, y)
|
|
builder.InsertInstruction(imul)
|
|
value := imul.Return()
|
|
state.push(value)
|
|
case wasm.OpcodeF32Sub, wasm.OpcodeF64Sub:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
y, x := state.pop(), state.pop()
|
|
isub := builder.AllocateInstruction()
|
|
isub.AsFsub(x, y)
|
|
builder.InsertInstruction(isub)
|
|
value := isub.Return()
|
|
state.push(value)
|
|
case wasm.OpcodeF32Mul, wasm.OpcodeF64Mul:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
y, x := state.pop(), state.pop()
|
|
isub := builder.AllocateInstruction()
|
|
isub.AsFmul(x, y)
|
|
builder.InsertInstruction(isub)
|
|
value := isub.Return()
|
|
state.push(value)
|
|
case wasm.OpcodeF32Div, wasm.OpcodeF64Div:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
y, x := state.pop(), state.pop()
|
|
isub := builder.AllocateInstruction()
|
|
isub.AsFdiv(x, y)
|
|
builder.InsertInstruction(isub)
|
|
value := isub.Return()
|
|
state.push(value)
|
|
case wasm.OpcodeF32Max, wasm.OpcodeF64Max:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
y, x := state.pop(), state.pop()
|
|
isub := builder.AllocateInstruction()
|
|
isub.AsFmax(x, y)
|
|
builder.InsertInstruction(isub)
|
|
value := isub.Return()
|
|
state.push(value)
|
|
case wasm.OpcodeF32Min, wasm.OpcodeF64Min:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
y, x := state.pop(), state.pop()
|
|
isub := builder.AllocateInstruction()
|
|
isub.AsFmin(x, y)
|
|
builder.InsertInstruction(isub)
|
|
value := isub.Return()
|
|
state.push(value)
|
|
case wasm.OpcodeI64Extend8S:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.insertIntegerExtend(true, 8, 64)
|
|
case wasm.OpcodeI64Extend16S:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.insertIntegerExtend(true, 16, 64)
|
|
case wasm.OpcodeI64Extend32S, wasm.OpcodeI64ExtendI32S:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.insertIntegerExtend(true, 32, 64)
|
|
case wasm.OpcodeI64ExtendI32U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.insertIntegerExtend(false, 32, 64)
|
|
case wasm.OpcodeI32Extend8S:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.insertIntegerExtend(true, 8, 32)
|
|
case wasm.OpcodeI32Extend16S:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.insertIntegerExtend(true, 16, 32)
|
|
case wasm.OpcodeI32Eqz, wasm.OpcodeI64Eqz:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
x := state.pop()
|
|
zero := builder.AllocateInstruction()
|
|
if op == wasm.OpcodeI32Eqz {
|
|
zero.AsIconst32(0)
|
|
} else {
|
|
zero.AsIconst64(0)
|
|
}
|
|
builder.InsertInstruction(zero)
|
|
icmp := builder.AllocateInstruction().
|
|
AsIcmp(x, zero.Return(), ssa.IntegerCmpCondEqual).
|
|
Insert(builder).
|
|
Return()
|
|
state.push(icmp)
|
|
case wasm.OpcodeI32Eq, wasm.OpcodeI64Eq:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.insertIcmp(ssa.IntegerCmpCondEqual)
|
|
case wasm.OpcodeI32Ne, wasm.OpcodeI64Ne:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.insertIcmp(ssa.IntegerCmpCondNotEqual)
|
|
case wasm.OpcodeI32LtS, wasm.OpcodeI64LtS:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.insertIcmp(ssa.IntegerCmpCondSignedLessThan)
|
|
case wasm.OpcodeI32LtU, wasm.OpcodeI64LtU:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.insertIcmp(ssa.IntegerCmpCondUnsignedLessThan)
|
|
case wasm.OpcodeI32GtS, wasm.OpcodeI64GtS:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.insertIcmp(ssa.IntegerCmpCondSignedGreaterThan)
|
|
case wasm.OpcodeI32GtU, wasm.OpcodeI64GtU:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.insertIcmp(ssa.IntegerCmpCondUnsignedGreaterThan)
|
|
case wasm.OpcodeI32LeS, wasm.OpcodeI64LeS:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.insertIcmp(ssa.IntegerCmpCondSignedLessThanOrEqual)
|
|
case wasm.OpcodeI32LeU, wasm.OpcodeI64LeU:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.insertIcmp(ssa.IntegerCmpCondUnsignedLessThanOrEqual)
|
|
case wasm.OpcodeI32GeS, wasm.OpcodeI64GeS:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.insertIcmp(ssa.IntegerCmpCondSignedGreaterThanOrEqual)
|
|
case wasm.OpcodeI32GeU, wasm.OpcodeI64GeU:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.insertIcmp(ssa.IntegerCmpCondUnsignedGreaterThanOrEqual)
|
|
|
|
case wasm.OpcodeF32Eq, wasm.OpcodeF64Eq:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.insertFcmp(ssa.FloatCmpCondEqual)
|
|
case wasm.OpcodeF32Ne, wasm.OpcodeF64Ne:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.insertFcmp(ssa.FloatCmpCondNotEqual)
|
|
case wasm.OpcodeF32Lt, wasm.OpcodeF64Lt:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.insertFcmp(ssa.FloatCmpCondLessThan)
|
|
case wasm.OpcodeF32Gt, wasm.OpcodeF64Gt:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.insertFcmp(ssa.FloatCmpCondGreaterThan)
|
|
case wasm.OpcodeF32Le, wasm.OpcodeF64Le:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.insertFcmp(ssa.FloatCmpCondLessThanOrEqual)
|
|
case wasm.OpcodeF32Ge, wasm.OpcodeF64Ge:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.insertFcmp(ssa.FloatCmpCondGreaterThanOrEqual)
|
|
case wasm.OpcodeF32Neg, wasm.OpcodeF64Neg:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
x := state.pop()
|
|
v := builder.AllocateInstruction().AsFneg(x).Insert(builder).Return()
|
|
state.push(v)
|
|
case wasm.OpcodeF32Sqrt, wasm.OpcodeF64Sqrt:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
x := state.pop()
|
|
v := builder.AllocateInstruction().AsSqrt(x).Insert(builder).Return()
|
|
state.push(v)
|
|
case wasm.OpcodeF32Abs, wasm.OpcodeF64Abs:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
x := state.pop()
|
|
v := builder.AllocateInstruction().AsFabs(x).Insert(builder).Return()
|
|
state.push(v)
|
|
case wasm.OpcodeF32Copysign, wasm.OpcodeF64Copysign:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
y, x := state.pop(), state.pop()
|
|
v := builder.AllocateInstruction().AsFcopysign(x, y).Insert(builder).Return()
|
|
state.push(v)
|
|
|
|
case wasm.OpcodeF32Ceil, wasm.OpcodeF64Ceil:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
x := state.pop()
|
|
v := builder.AllocateInstruction().AsCeil(x).Insert(builder).Return()
|
|
state.push(v)
|
|
case wasm.OpcodeF32Floor, wasm.OpcodeF64Floor:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
x := state.pop()
|
|
v := builder.AllocateInstruction().AsFloor(x).Insert(builder).Return()
|
|
state.push(v)
|
|
case wasm.OpcodeF32Trunc, wasm.OpcodeF64Trunc:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
x := state.pop()
|
|
v := builder.AllocateInstruction().AsTrunc(x).Insert(builder).Return()
|
|
state.push(v)
|
|
case wasm.OpcodeF32Nearest, wasm.OpcodeF64Nearest:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
x := state.pop()
|
|
v := builder.AllocateInstruction().AsNearest(x).Insert(builder).Return()
|
|
state.push(v)
|
|
case wasm.OpcodeI64TruncF64S, wasm.OpcodeI64TruncF32S,
|
|
wasm.OpcodeI32TruncF64S, wasm.OpcodeI32TruncF32S,
|
|
wasm.OpcodeI64TruncF64U, wasm.OpcodeI64TruncF32U,
|
|
wasm.OpcodeI32TruncF64U, wasm.OpcodeI32TruncF32U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
ret := builder.AllocateInstruction().AsFcvtToInt(
|
|
state.pop(),
|
|
c.execCtxPtrValue,
|
|
op == wasm.OpcodeI64TruncF64S || op == wasm.OpcodeI64TruncF32S || op == wasm.OpcodeI32TruncF32S || op == wasm.OpcodeI32TruncF64S,
|
|
op == wasm.OpcodeI64TruncF64S || op == wasm.OpcodeI64TruncF32S || op == wasm.OpcodeI64TruncF64U || op == wasm.OpcodeI64TruncF32U,
|
|
false,
|
|
).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeMiscPrefix:
|
|
state.pc++
|
|
// A misc opcode is encoded as an unsigned variable 32-bit integer.
|
|
miscOpUint, num, err := leb128.LoadUint32(c.wasmFunctionBody[state.pc:])
|
|
if err != nil {
|
|
// In normal conditions this should never happen because the function has passed validation.
|
|
panic(fmt.Sprintf("failed to read misc opcode: %v", err))
|
|
}
|
|
state.pc += int(num - 1)
|
|
miscOp := wasm.OpcodeMisc(miscOpUint)
|
|
switch miscOp {
|
|
case wasm.OpcodeMiscI64TruncSatF64S, wasm.OpcodeMiscI64TruncSatF32S,
|
|
wasm.OpcodeMiscI32TruncSatF64S, wasm.OpcodeMiscI32TruncSatF32S,
|
|
wasm.OpcodeMiscI64TruncSatF64U, wasm.OpcodeMiscI64TruncSatF32U,
|
|
wasm.OpcodeMiscI32TruncSatF64U, wasm.OpcodeMiscI32TruncSatF32U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
ret := builder.AllocateInstruction().AsFcvtToInt(
|
|
state.pop(),
|
|
c.execCtxPtrValue,
|
|
miscOp == wasm.OpcodeMiscI64TruncSatF64S || miscOp == wasm.OpcodeMiscI64TruncSatF32S || miscOp == wasm.OpcodeMiscI32TruncSatF32S || miscOp == wasm.OpcodeMiscI32TruncSatF64S,
|
|
miscOp == wasm.OpcodeMiscI64TruncSatF64S || miscOp == wasm.OpcodeMiscI64TruncSatF32S || miscOp == wasm.OpcodeMiscI64TruncSatF64U || miscOp == wasm.OpcodeMiscI64TruncSatF32U,
|
|
true,
|
|
).Insert(builder).Return()
|
|
state.push(ret)
|
|
|
|
case wasm.OpcodeMiscTableSize:
|
|
tableIndex := c.readI32u()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
// Load the table.
|
|
loadTableInstancePtr := builder.AllocateInstruction()
|
|
loadTableInstancePtr.AsLoad(c.moduleCtxPtrValue, c.offset.TableOffset(int(tableIndex)).U32(), ssa.TypeI64)
|
|
builder.InsertInstruction(loadTableInstancePtr)
|
|
tableInstancePtr := loadTableInstancePtr.Return()
|
|
|
|
// Load the table's length.
|
|
loadTableLen := builder.AllocateInstruction().
|
|
AsLoad(tableInstancePtr, tableInstanceLenOffset, ssa.TypeI32).
|
|
Insert(builder)
|
|
state.push(loadTableLen.Return())
|
|
|
|
case wasm.OpcodeMiscTableGrow:
|
|
tableIndex := c.readI32u()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
c.storeCallerModuleContext()
|
|
|
|
tableIndexVal := builder.AllocateInstruction().AsIconst32(tableIndex).Insert(builder).Return()
|
|
|
|
num := state.pop()
|
|
r := state.pop()
|
|
|
|
tableGrowPtr := builder.AllocateInstruction().
|
|
AsLoad(c.execCtxPtrValue,
|
|
wazevoapi.ExecutionContextOffsetTableGrowTrampolineAddress.U32(),
|
|
ssa.TypeI64,
|
|
).Insert(builder).Return()
|
|
|
|
args := c.allocateVarLengthValues(4, c.execCtxPtrValue, tableIndexVal, num, r)
|
|
callGrowRet := builder.
|
|
AllocateInstruction().
|
|
AsCallIndirect(tableGrowPtr, &c.tableGrowSig, args).
|
|
Insert(builder).Return()
|
|
state.push(callGrowRet)
|
|
|
|
case wasm.OpcodeMiscTableCopy:
|
|
dstTableIndex := c.readI32u()
|
|
srcTableIndex := c.readI32u()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
copySize := builder.
|
|
AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return()
|
|
srcOffset := builder.
|
|
AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return()
|
|
dstOffset := builder.
|
|
AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return()
|
|
|
|
// Out of bounds check.
|
|
dstTableInstancePtr := c.boundsCheckInTable(dstTableIndex, dstOffset, copySize)
|
|
srcTableInstancePtr := c.boundsCheckInTable(srcTableIndex, srcOffset, copySize)
|
|
|
|
dstTableBaseAddr := c.loadTableBaseAddr(dstTableInstancePtr)
|
|
srcTableBaseAddr := c.loadTableBaseAddr(srcTableInstancePtr)
|
|
|
|
three := builder.AllocateInstruction().AsIconst64(3).Insert(builder).Return()
|
|
|
|
dstOffsetInBytes := builder.AllocateInstruction().AsIshl(dstOffset, three).Insert(builder).Return()
|
|
dstAddr := builder.AllocateInstruction().AsIadd(dstTableBaseAddr, dstOffsetInBytes).Insert(builder).Return()
|
|
srcOffsetInBytes := builder.AllocateInstruction().AsIshl(srcOffset, three).Insert(builder).Return()
|
|
srcAddr := builder.AllocateInstruction().AsIadd(srcTableBaseAddr, srcOffsetInBytes).Insert(builder).Return()
|
|
|
|
copySizeInBytes := builder.AllocateInstruction().AsIshl(copySize, three).Insert(builder).Return()
|
|
c.callMemmove(dstAddr, srcAddr, copySizeInBytes)
|
|
|
|
case wasm.OpcodeMiscMemoryCopy:
|
|
state.pc += 2 // +2 to skip two memory indexes which are fixed to zero.
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
copySize := builder.
|
|
AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return()
|
|
srcOffset := builder.
|
|
AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return()
|
|
dstOffset := builder.
|
|
AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return()
|
|
|
|
// Out of bounds check.
|
|
memLen := c.getMemoryLenValue(false)
|
|
c.boundsCheckInMemory(memLen, dstOffset, copySize)
|
|
c.boundsCheckInMemory(memLen, srcOffset, copySize)
|
|
|
|
memBase := c.getMemoryBaseValue(false)
|
|
dstAddr := builder.AllocateInstruction().AsIadd(memBase, dstOffset).Insert(builder).Return()
|
|
srcAddr := builder.AllocateInstruction().AsIadd(memBase, srcOffset).Insert(builder).Return()
|
|
|
|
c.callMemmove(dstAddr, srcAddr, copySize)
|
|
|
|
case wasm.OpcodeMiscTableFill:
|
|
tableIndex := c.readI32u()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
fillSize := state.pop()
|
|
value := state.pop()
|
|
offset := state.pop()
|
|
|
|
fillSizeExt := builder.
|
|
AllocateInstruction().AsUExtend(fillSize, 32, 64).Insert(builder).Return()
|
|
offsetExt := builder.
|
|
AllocateInstruction().AsUExtend(offset, 32, 64).Insert(builder).Return()
|
|
tableInstancePtr := c.boundsCheckInTable(tableIndex, offsetExt, fillSizeExt)
|
|
|
|
three := builder.AllocateInstruction().AsIconst64(3).Insert(builder).Return()
|
|
offsetInBytes := builder.AllocateInstruction().AsIshl(offsetExt, three).Insert(builder).Return()
|
|
fillSizeInBytes := builder.AllocateInstruction().AsIshl(fillSizeExt, three).Insert(builder).Return()
|
|
|
|
// Calculate the base address of the table.
|
|
tableBaseAddr := c.loadTableBaseAddr(tableInstancePtr)
|
|
addr := builder.AllocateInstruction().AsIadd(tableBaseAddr, offsetInBytes).Insert(builder).Return()
|
|
|
|
// Prepare the loop and following block.
|
|
beforeLoop := builder.AllocateBasicBlock()
|
|
loopBlk := builder.AllocateBasicBlock()
|
|
loopVar := loopBlk.AddParam(builder, ssa.TypeI64)
|
|
followingBlk := builder.AllocateBasicBlock()
|
|
|
|
// Uses the copy trick for faster filling buffer like memory.fill, but in this case we copy 8 bytes at a time.
|
|
// buf := memoryInst.Buffer[offset : offset+fillSize]
|
|
// buf[0:8] = value
|
|
// for i := 8; i < fillSize; i *= 2 { Begin with 8 bytes.
|
|
// copy(buf[i:], buf[:i])
|
|
// }
|
|
|
|
// Insert the jump to the beforeLoop block; If the fillSize is zero, then jump to the following block to skip entire logics.
|
|
zero := builder.AllocateInstruction().AsIconst64(0).Insert(builder).Return()
|
|
ifFillSizeZero := builder.AllocateInstruction().AsIcmp(fillSizeExt, zero, ssa.IntegerCmpCondEqual).
|
|
Insert(builder).Return()
|
|
builder.AllocateInstruction().AsBrnz(ifFillSizeZero, ssa.ValuesNil, followingBlk).Insert(builder)
|
|
c.insertJumpToBlock(ssa.ValuesNil, beforeLoop)
|
|
|
|
// buf[0:8] = value
|
|
builder.SetCurrentBlock(beforeLoop)
|
|
builder.AllocateInstruction().AsStore(ssa.OpcodeStore, value, addr, 0).Insert(builder)
|
|
initValue := builder.AllocateInstruction().AsIconst64(8).Insert(builder).Return()
|
|
c.insertJumpToBlock(c.allocateVarLengthValues(1, initValue), loopBlk)
|
|
|
|
builder.SetCurrentBlock(loopBlk)
|
|
dstAddr := builder.AllocateInstruction().AsIadd(addr, loopVar).Insert(builder).Return()
|
|
|
|
// If loopVar*2 > fillSizeInBytes, then count must be fillSizeInBytes-loopVar.
|
|
var count ssa.Value
|
|
{
|
|
loopVarDoubled := builder.AllocateInstruction().AsIadd(loopVar, loopVar).Insert(builder).Return()
|
|
loopVarDoubledLargerThanFillSize := builder.
|
|
AllocateInstruction().AsIcmp(loopVarDoubled, fillSizeInBytes, ssa.IntegerCmpCondUnsignedGreaterThanOrEqual).
|
|
Insert(builder).Return()
|
|
diff := builder.AllocateInstruction().AsIsub(fillSizeInBytes, loopVar).Insert(builder).Return()
|
|
count = builder.AllocateInstruction().AsSelect(loopVarDoubledLargerThanFillSize, diff, loopVar).Insert(builder).Return()
|
|
}
|
|
|
|
c.callMemmove(dstAddr, addr, count)
|
|
|
|
shiftAmount := builder.AllocateInstruction().AsIconst64(1).Insert(builder).Return()
|
|
newLoopVar := builder.AllocateInstruction().AsIshl(loopVar, shiftAmount).Insert(builder).Return()
|
|
loopVarLessThanFillSize := builder.AllocateInstruction().
|
|
AsIcmp(newLoopVar, fillSizeInBytes, ssa.IntegerCmpCondUnsignedLessThan).Insert(builder).Return()
|
|
|
|
builder.AllocateInstruction().
|
|
AsBrnz(loopVarLessThanFillSize, c.allocateVarLengthValues(1, newLoopVar), loopBlk).
|
|
Insert(builder)
|
|
|
|
c.insertJumpToBlock(ssa.ValuesNil, followingBlk)
|
|
builder.SetCurrentBlock(followingBlk)
|
|
|
|
builder.Seal(beforeLoop)
|
|
builder.Seal(loopBlk)
|
|
builder.Seal(followingBlk)
|
|
|
|
case wasm.OpcodeMiscMemoryFill:
|
|
state.pc++ // Skip the memory index which is fixed to zero.
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
fillSize := builder.
|
|
AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return()
|
|
value := state.pop()
|
|
offset := builder.
|
|
AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return()
|
|
|
|
// Out of bounds check.
|
|
c.boundsCheckInMemory(c.getMemoryLenValue(false), offset, fillSize)
|
|
|
|
// Calculate the base address:
|
|
addr := builder.AllocateInstruction().AsIadd(c.getMemoryBaseValue(false), offset).Insert(builder).Return()
|
|
|
|
// Uses the copy trick for faster filling buffer: https://gist.github.com/taylorza/df2f89d5f9ab3ffd06865062a4cf015d
|
|
// buf := memoryInst.Buffer[offset : offset+fillSize]
|
|
// buf[0] = value
|
|
// for i := 1; i < fillSize; i *= 2 {
|
|
// copy(buf[i:], buf[:i])
|
|
// }
|
|
|
|
// Prepare the loop and following block.
|
|
beforeLoop := builder.AllocateBasicBlock()
|
|
loopBlk := builder.AllocateBasicBlock()
|
|
loopVar := loopBlk.AddParam(builder, ssa.TypeI64)
|
|
followingBlk := builder.AllocateBasicBlock()
|
|
|
|
// Insert the jump to the beforeLoop block; If the fillSize is zero, then jump to the following block to skip entire logics.
|
|
zero := builder.AllocateInstruction().AsIconst64(0).Insert(builder).Return()
|
|
ifFillSizeZero := builder.AllocateInstruction().AsIcmp(fillSize, zero, ssa.IntegerCmpCondEqual).
|
|
Insert(builder).Return()
|
|
builder.AllocateInstruction().AsBrnz(ifFillSizeZero, ssa.ValuesNil, followingBlk).Insert(builder)
|
|
c.insertJumpToBlock(ssa.ValuesNil, beforeLoop)
|
|
|
|
// buf[0] = value
|
|
builder.SetCurrentBlock(beforeLoop)
|
|
builder.AllocateInstruction().AsStore(ssa.OpcodeIstore8, value, addr, 0).Insert(builder)
|
|
initValue := builder.AllocateInstruction().AsIconst64(1).Insert(builder).Return()
|
|
c.insertJumpToBlock(c.allocateVarLengthValues(1, initValue), loopBlk)
|
|
|
|
builder.SetCurrentBlock(loopBlk)
|
|
dstAddr := builder.AllocateInstruction().AsIadd(addr, loopVar).Insert(builder).Return()
|
|
|
|
// If loopVar*2 > fillSizeExt, then count must be fillSizeExt-loopVar.
|
|
var count ssa.Value
|
|
{
|
|
loopVarDoubled := builder.AllocateInstruction().AsIadd(loopVar, loopVar).Insert(builder).Return()
|
|
loopVarDoubledLargerThanFillSize := builder.
|
|
AllocateInstruction().AsIcmp(loopVarDoubled, fillSize, ssa.IntegerCmpCondUnsignedGreaterThanOrEqual).
|
|
Insert(builder).Return()
|
|
diff := builder.AllocateInstruction().AsIsub(fillSize, loopVar).Insert(builder).Return()
|
|
count = builder.AllocateInstruction().AsSelect(loopVarDoubledLargerThanFillSize, diff, loopVar).Insert(builder).Return()
|
|
}
|
|
|
|
c.callMemmove(dstAddr, addr, count)
|
|
|
|
shiftAmount := builder.AllocateInstruction().AsIconst64(1).Insert(builder).Return()
|
|
newLoopVar := builder.AllocateInstruction().AsIshl(loopVar, shiftAmount).Insert(builder).Return()
|
|
loopVarLessThanFillSize := builder.AllocateInstruction().
|
|
AsIcmp(newLoopVar, fillSize, ssa.IntegerCmpCondUnsignedLessThan).Insert(builder).Return()
|
|
|
|
builder.AllocateInstruction().
|
|
AsBrnz(loopVarLessThanFillSize, c.allocateVarLengthValues(1, newLoopVar), loopBlk).
|
|
Insert(builder)
|
|
|
|
c.insertJumpToBlock(ssa.ValuesNil, followingBlk)
|
|
builder.SetCurrentBlock(followingBlk)
|
|
|
|
builder.Seal(beforeLoop)
|
|
builder.Seal(loopBlk)
|
|
builder.Seal(followingBlk)
|
|
|
|
case wasm.OpcodeMiscMemoryInit:
|
|
index := c.readI32u()
|
|
state.pc++ // Skip the memory index which is fixed to zero.
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
copySize := builder.
|
|
AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return()
|
|
offsetInDataInstance := builder.
|
|
AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return()
|
|
offsetInMemory := builder.
|
|
AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return()
|
|
|
|
dataInstPtr := c.dataOrElementInstanceAddr(index, c.offset.DataInstances1stElement)
|
|
|
|
// Bounds check.
|
|
c.boundsCheckInMemory(c.getMemoryLenValue(false), offsetInMemory, copySize)
|
|
c.boundsCheckInDataOrElementInstance(dataInstPtr, offsetInDataInstance, copySize, wazevoapi.ExitCodeMemoryOutOfBounds)
|
|
|
|
dataInstBaseAddr := builder.AllocateInstruction().AsLoad(dataInstPtr, 0, ssa.TypeI64).Insert(builder).Return()
|
|
srcAddr := builder.AllocateInstruction().AsIadd(dataInstBaseAddr, offsetInDataInstance).Insert(builder).Return()
|
|
|
|
memBase := c.getMemoryBaseValue(false)
|
|
dstAddr := builder.AllocateInstruction().AsIadd(memBase, offsetInMemory).Insert(builder).Return()
|
|
|
|
c.callMemmove(dstAddr, srcAddr, copySize)
|
|
|
|
case wasm.OpcodeMiscTableInit:
|
|
elemIndex := c.readI32u()
|
|
tableIndex := c.readI32u()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
copySize := builder.
|
|
AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return()
|
|
offsetInElementInstance := builder.
|
|
AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return()
|
|
offsetInTable := builder.
|
|
AllocateInstruction().AsUExtend(state.pop(), 32, 64).Insert(builder).Return()
|
|
|
|
elemInstPtr := c.dataOrElementInstanceAddr(elemIndex, c.offset.ElementInstances1stElement)
|
|
|
|
// Bounds check.
|
|
tableInstancePtr := c.boundsCheckInTable(tableIndex, offsetInTable, copySize)
|
|
c.boundsCheckInDataOrElementInstance(elemInstPtr, offsetInElementInstance, copySize, wazevoapi.ExitCodeTableOutOfBounds)
|
|
|
|
three := builder.AllocateInstruction().AsIconst64(3).Insert(builder).Return()
|
|
// Calculates the destination address in the table.
|
|
tableOffsetInBytes := builder.AllocateInstruction().AsIshl(offsetInTable, three).Insert(builder).Return()
|
|
tableBaseAddr := c.loadTableBaseAddr(tableInstancePtr)
|
|
dstAddr := builder.AllocateInstruction().AsIadd(tableBaseAddr, tableOffsetInBytes).Insert(builder).Return()
|
|
|
|
// Calculates the source address in the element instance.
|
|
srcOffsetInBytes := builder.AllocateInstruction().AsIshl(offsetInElementInstance, three).Insert(builder).Return()
|
|
elemInstBaseAddr := builder.AllocateInstruction().AsLoad(elemInstPtr, 0, ssa.TypeI64).Insert(builder).Return()
|
|
srcAddr := builder.AllocateInstruction().AsIadd(elemInstBaseAddr, srcOffsetInBytes).Insert(builder).Return()
|
|
|
|
copySizeInBytes := builder.AllocateInstruction().AsIshl(copySize, three).Insert(builder).Return()
|
|
c.callMemmove(dstAddr, srcAddr, copySizeInBytes)
|
|
|
|
case wasm.OpcodeMiscElemDrop:
|
|
index := c.readI32u()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
c.dropDataOrElementInstance(index, c.offset.ElementInstances1stElement)
|
|
|
|
case wasm.OpcodeMiscDataDrop:
|
|
index := c.readI32u()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.dropDataOrElementInstance(index, c.offset.DataInstances1stElement)
|
|
|
|
default:
|
|
panic("Unknown MiscOp " + wasm.MiscInstructionName(miscOp))
|
|
}
|
|
|
|
case wasm.OpcodeI32ReinterpretF32:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
reinterpret := builder.AllocateInstruction().
|
|
AsBitcast(state.pop(), ssa.TypeI32).
|
|
Insert(builder).Return()
|
|
state.push(reinterpret)
|
|
|
|
case wasm.OpcodeI64ReinterpretF64:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
reinterpret := builder.AllocateInstruction().
|
|
AsBitcast(state.pop(), ssa.TypeI64).
|
|
Insert(builder).Return()
|
|
state.push(reinterpret)
|
|
|
|
case wasm.OpcodeF32ReinterpretI32:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
reinterpret := builder.AllocateInstruction().
|
|
AsBitcast(state.pop(), ssa.TypeF32).
|
|
Insert(builder).Return()
|
|
state.push(reinterpret)
|
|
|
|
case wasm.OpcodeF64ReinterpretI64:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
reinterpret := builder.AllocateInstruction().
|
|
AsBitcast(state.pop(), ssa.TypeF64).
|
|
Insert(builder).Return()
|
|
state.push(reinterpret)
|
|
|
|
case wasm.OpcodeI32DivS, wasm.OpcodeI64DivS:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
y, x := state.pop(), state.pop()
|
|
result := builder.AllocateInstruction().AsSDiv(x, y, c.execCtxPtrValue).Insert(builder).Return()
|
|
state.push(result)
|
|
|
|
case wasm.OpcodeI32DivU, wasm.OpcodeI64DivU:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
y, x := state.pop(), state.pop()
|
|
result := builder.AllocateInstruction().AsUDiv(x, y, c.execCtxPtrValue).Insert(builder).Return()
|
|
state.push(result)
|
|
|
|
case wasm.OpcodeI32RemS, wasm.OpcodeI64RemS:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
y, x := state.pop(), state.pop()
|
|
result := builder.AllocateInstruction().AsSRem(x, y, c.execCtxPtrValue).Insert(builder).Return()
|
|
state.push(result)
|
|
|
|
case wasm.OpcodeI32RemU, wasm.OpcodeI64RemU:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
y, x := state.pop(), state.pop()
|
|
result := builder.AllocateInstruction().AsURem(x, y, c.execCtxPtrValue).Insert(builder).Return()
|
|
state.push(result)
|
|
|
|
case wasm.OpcodeI32And, wasm.OpcodeI64And:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
y, x := state.pop(), state.pop()
|
|
and := builder.AllocateInstruction()
|
|
and.AsBand(x, y)
|
|
builder.InsertInstruction(and)
|
|
value := and.Return()
|
|
state.push(value)
|
|
case wasm.OpcodeI32Or, wasm.OpcodeI64Or:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
y, x := state.pop(), state.pop()
|
|
or := builder.AllocateInstruction()
|
|
or.AsBor(x, y)
|
|
builder.InsertInstruction(or)
|
|
value := or.Return()
|
|
state.push(value)
|
|
case wasm.OpcodeI32Xor, wasm.OpcodeI64Xor:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
y, x := state.pop(), state.pop()
|
|
xor := builder.AllocateInstruction()
|
|
xor.AsBxor(x, y)
|
|
builder.InsertInstruction(xor)
|
|
value := xor.Return()
|
|
state.push(value)
|
|
case wasm.OpcodeI32Shl, wasm.OpcodeI64Shl:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
y, x := state.pop(), state.pop()
|
|
ishl := builder.AllocateInstruction()
|
|
ishl.AsIshl(x, y)
|
|
builder.InsertInstruction(ishl)
|
|
value := ishl.Return()
|
|
state.push(value)
|
|
case wasm.OpcodeI32ShrU, wasm.OpcodeI64ShrU:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
y, x := state.pop(), state.pop()
|
|
ishl := builder.AllocateInstruction()
|
|
ishl.AsUshr(x, y)
|
|
builder.InsertInstruction(ishl)
|
|
value := ishl.Return()
|
|
state.push(value)
|
|
case wasm.OpcodeI32ShrS, wasm.OpcodeI64ShrS:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
y, x := state.pop(), state.pop()
|
|
ishl := builder.AllocateInstruction()
|
|
ishl.AsSshr(x, y)
|
|
builder.InsertInstruction(ishl)
|
|
value := ishl.Return()
|
|
state.push(value)
|
|
case wasm.OpcodeI32Rotl, wasm.OpcodeI64Rotl:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
y, x := state.pop(), state.pop()
|
|
rotl := builder.AllocateInstruction()
|
|
rotl.AsRotl(x, y)
|
|
builder.InsertInstruction(rotl)
|
|
value := rotl.Return()
|
|
state.push(value)
|
|
case wasm.OpcodeI32Rotr, wasm.OpcodeI64Rotr:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
y, x := state.pop(), state.pop()
|
|
rotr := builder.AllocateInstruction()
|
|
rotr.AsRotr(x, y)
|
|
builder.InsertInstruction(rotr)
|
|
value := rotr.Return()
|
|
state.push(value)
|
|
case wasm.OpcodeI32Clz, wasm.OpcodeI64Clz:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
x := state.pop()
|
|
clz := builder.AllocateInstruction()
|
|
clz.AsClz(x)
|
|
builder.InsertInstruction(clz)
|
|
value := clz.Return()
|
|
state.push(value)
|
|
case wasm.OpcodeI32Ctz, wasm.OpcodeI64Ctz:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
x := state.pop()
|
|
ctz := builder.AllocateInstruction()
|
|
ctz.AsCtz(x)
|
|
builder.InsertInstruction(ctz)
|
|
value := ctz.Return()
|
|
state.push(value)
|
|
case wasm.OpcodeI32Popcnt, wasm.OpcodeI64Popcnt:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
x := state.pop()
|
|
popcnt := builder.AllocateInstruction()
|
|
popcnt.AsPopcnt(x)
|
|
builder.InsertInstruction(popcnt)
|
|
value := popcnt.Return()
|
|
state.push(value)
|
|
|
|
case wasm.OpcodeI32WrapI64:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
x := state.pop()
|
|
wrap := builder.AllocateInstruction().AsIreduce(x, ssa.TypeI32).Insert(builder).Return()
|
|
state.push(wrap)
|
|
case wasm.OpcodeGlobalGet:
|
|
index := c.readI32u()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v := c.getWasmGlobalValue(index, false)
|
|
state.push(v)
|
|
case wasm.OpcodeGlobalSet:
|
|
index := c.readI32u()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v := state.pop()
|
|
c.setWasmGlobalValue(index, v)
|
|
case wasm.OpcodeLocalGet:
|
|
index := c.readI32u()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
variable := c.localVariable(index)
|
|
state.push(builder.MustFindValue(variable))
|
|
|
|
case wasm.OpcodeLocalSet:
|
|
index := c.readI32u()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
variable := c.localVariable(index)
|
|
newValue := state.pop()
|
|
builder.DefineVariableInCurrentBB(variable, newValue)
|
|
|
|
case wasm.OpcodeLocalTee:
|
|
index := c.readI32u()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
variable := c.localVariable(index)
|
|
newValue := state.peek()
|
|
builder.DefineVariableInCurrentBB(variable, newValue)
|
|
|
|
case wasm.OpcodeSelect, wasm.OpcodeTypedSelect:
|
|
if op == wasm.OpcodeTypedSelect {
|
|
state.pc += 2 // ignores the type which is only needed during validation.
|
|
}
|
|
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
cond := state.pop()
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
|
|
sl := builder.AllocateInstruction().
|
|
AsSelect(cond, v1, v2).
|
|
Insert(builder).
|
|
Return()
|
|
state.push(sl)
|
|
|
|
case wasm.OpcodeMemorySize:
|
|
state.pc++ // skips the memory index.
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
var memSizeInBytes ssa.Value
|
|
if c.offset.LocalMemoryBegin < 0 {
|
|
memInstPtr := builder.AllocateInstruction().
|
|
AsLoad(c.moduleCtxPtrValue, c.offset.ImportedMemoryBegin.U32(), ssa.TypeI64).
|
|
Insert(builder).
|
|
Return()
|
|
|
|
memSizeInBytes = builder.AllocateInstruction().
|
|
AsLoad(memInstPtr, memoryInstanceBufSizeOffset, ssa.TypeI32).
|
|
Insert(builder).
|
|
Return()
|
|
} else {
|
|
memSizeInBytes = builder.AllocateInstruction().
|
|
AsLoad(c.moduleCtxPtrValue, c.offset.LocalMemoryLen().U32(), ssa.TypeI32).
|
|
Insert(builder).
|
|
Return()
|
|
}
|
|
|
|
amount := builder.AllocateInstruction()
|
|
amount.AsIconst32(uint32(wasm.MemoryPageSizeInBits))
|
|
builder.InsertInstruction(amount)
|
|
memSize := builder.AllocateInstruction().
|
|
AsUshr(memSizeInBytes, amount.Return()).
|
|
Insert(builder).
|
|
Return()
|
|
state.push(memSize)
|
|
|
|
case wasm.OpcodeMemoryGrow:
|
|
state.pc++ // skips the memory index.
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
c.storeCallerModuleContext()
|
|
|
|
pages := state.pop()
|
|
memoryGrowPtr := builder.AllocateInstruction().
|
|
AsLoad(c.execCtxPtrValue,
|
|
wazevoapi.ExecutionContextOffsetMemoryGrowTrampolineAddress.U32(),
|
|
ssa.TypeI64,
|
|
).Insert(builder).Return()
|
|
|
|
args := c.allocateVarLengthValues(1, c.execCtxPtrValue, pages)
|
|
callGrowRet := builder.
|
|
AllocateInstruction().
|
|
AsCallIndirect(memoryGrowPtr, &c.memoryGrowSig, args).
|
|
Insert(builder).Return()
|
|
state.push(callGrowRet)
|
|
|
|
// After the memory grow, reload the cached memory base and len.
|
|
c.reloadMemoryBaseLen()
|
|
|
|
case wasm.OpcodeI32Store,
|
|
wasm.OpcodeI64Store,
|
|
wasm.OpcodeF32Store,
|
|
wasm.OpcodeF64Store,
|
|
wasm.OpcodeI32Store8,
|
|
wasm.OpcodeI32Store16,
|
|
wasm.OpcodeI64Store8,
|
|
wasm.OpcodeI64Store16,
|
|
wasm.OpcodeI64Store32:
|
|
|
|
_, offset := c.readMemArg()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var opSize uint64
|
|
var opcode ssa.Opcode
|
|
switch op {
|
|
case wasm.OpcodeI32Store, wasm.OpcodeF32Store:
|
|
opcode = ssa.OpcodeStore
|
|
opSize = 4
|
|
case wasm.OpcodeI64Store, wasm.OpcodeF64Store:
|
|
opcode = ssa.OpcodeStore
|
|
opSize = 8
|
|
case wasm.OpcodeI32Store8, wasm.OpcodeI64Store8:
|
|
opcode = ssa.OpcodeIstore8
|
|
opSize = 1
|
|
case wasm.OpcodeI32Store16, wasm.OpcodeI64Store16:
|
|
opcode = ssa.OpcodeIstore16
|
|
opSize = 2
|
|
case wasm.OpcodeI64Store32:
|
|
opcode = ssa.OpcodeIstore32
|
|
opSize = 4
|
|
default:
|
|
panic("BUG")
|
|
}
|
|
|
|
value := state.pop()
|
|
baseAddr := state.pop()
|
|
addr := c.memOpSetup(baseAddr, uint64(offset), opSize)
|
|
builder.AllocateInstruction().
|
|
AsStore(opcode, value, addr, offset).
|
|
Insert(builder)
|
|
|
|
case wasm.OpcodeI32Load,
|
|
wasm.OpcodeI64Load,
|
|
wasm.OpcodeF32Load,
|
|
wasm.OpcodeF64Load,
|
|
wasm.OpcodeI32Load8S,
|
|
wasm.OpcodeI32Load8U,
|
|
wasm.OpcodeI32Load16S,
|
|
wasm.OpcodeI32Load16U,
|
|
wasm.OpcodeI64Load8S,
|
|
wasm.OpcodeI64Load8U,
|
|
wasm.OpcodeI64Load16S,
|
|
wasm.OpcodeI64Load16U,
|
|
wasm.OpcodeI64Load32S,
|
|
wasm.OpcodeI64Load32U:
|
|
_, offset := c.readMemArg()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
var opSize uint64
|
|
switch op {
|
|
case wasm.OpcodeI32Load, wasm.OpcodeF32Load:
|
|
opSize = 4
|
|
case wasm.OpcodeI64Load, wasm.OpcodeF64Load:
|
|
opSize = 8
|
|
case wasm.OpcodeI32Load8S, wasm.OpcodeI32Load8U:
|
|
opSize = 1
|
|
case wasm.OpcodeI32Load16S, wasm.OpcodeI32Load16U:
|
|
opSize = 2
|
|
case wasm.OpcodeI64Load8S, wasm.OpcodeI64Load8U:
|
|
opSize = 1
|
|
case wasm.OpcodeI64Load16S, wasm.OpcodeI64Load16U:
|
|
opSize = 2
|
|
case wasm.OpcodeI64Load32S, wasm.OpcodeI64Load32U:
|
|
opSize = 4
|
|
default:
|
|
panic("BUG")
|
|
}
|
|
|
|
baseAddr := state.pop()
|
|
addr := c.memOpSetup(baseAddr, uint64(offset), opSize)
|
|
load := builder.AllocateInstruction()
|
|
switch op {
|
|
case wasm.OpcodeI32Load:
|
|
load.AsLoad(addr, offset, ssa.TypeI32)
|
|
case wasm.OpcodeI64Load:
|
|
load.AsLoad(addr, offset, ssa.TypeI64)
|
|
case wasm.OpcodeF32Load:
|
|
load.AsLoad(addr, offset, ssa.TypeF32)
|
|
case wasm.OpcodeF64Load:
|
|
load.AsLoad(addr, offset, ssa.TypeF64)
|
|
case wasm.OpcodeI32Load8S:
|
|
load.AsExtLoad(ssa.OpcodeSload8, addr, offset, false)
|
|
case wasm.OpcodeI32Load8U:
|
|
load.AsExtLoad(ssa.OpcodeUload8, addr, offset, false)
|
|
case wasm.OpcodeI32Load16S:
|
|
load.AsExtLoad(ssa.OpcodeSload16, addr, offset, false)
|
|
case wasm.OpcodeI32Load16U:
|
|
load.AsExtLoad(ssa.OpcodeUload16, addr, offset, false)
|
|
case wasm.OpcodeI64Load8S:
|
|
load.AsExtLoad(ssa.OpcodeSload8, addr, offset, true)
|
|
case wasm.OpcodeI64Load8U:
|
|
load.AsExtLoad(ssa.OpcodeUload8, addr, offset, true)
|
|
case wasm.OpcodeI64Load16S:
|
|
load.AsExtLoad(ssa.OpcodeSload16, addr, offset, true)
|
|
case wasm.OpcodeI64Load16U:
|
|
load.AsExtLoad(ssa.OpcodeUload16, addr, offset, true)
|
|
case wasm.OpcodeI64Load32S:
|
|
load.AsExtLoad(ssa.OpcodeSload32, addr, offset, true)
|
|
case wasm.OpcodeI64Load32U:
|
|
load.AsExtLoad(ssa.OpcodeUload32, addr, offset, true)
|
|
default:
|
|
panic("BUG")
|
|
}
|
|
builder.InsertInstruction(load)
|
|
state.push(load.Return())
|
|
case wasm.OpcodeBlock:
|
|
// Note: we do not need to create a BB for this as that would always have only one predecessor
|
|
// which is the current BB, and therefore it's always ok to merge them in any way.
|
|
|
|
bt := c.readBlockType()
|
|
|
|
if state.unreachable {
|
|
state.unreachableDepth++
|
|
break
|
|
}
|
|
|
|
followingBlk := builder.AllocateBasicBlock()
|
|
c.addBlockParamsFromWasmTypes(bt.Results, followingBlk)
|
|
|
|
state.ctrlPush(controlFrame{
|
|
kind: controlFrameKindBlock,
|
|
originalStackLenWithoutParam: len(state.values) - len(bt.Params),
|
|
followingBlock: followingBlk,
|
|
blockType: bt,
|
|
})
|
|
case wasm.OpcodeLoop:
|
|
bt := c.readBlockType()
|
|
|
|
if state.unreachable {
|
|
state.unreachableDepth++
|
|
break
|
|
}
|
|
|
|
loopHeader, afterLoopBlock := builder.AllocateBasicBlock(), builder.AllocateBasicBlock()
|
|
c.addBlockParamsFromWasmTypes(bt.Params, loopHeader)
|
|
c.addBlockParamsFromWasmTypes(bt.Results, afterLoopBlock)
|
|
|
|
originalLen := len(state.values) - len(bt.Params)
|
|
state.ctrlPush(controlFrame{
|
|
originalStackLenWithoutParam: originalLen,
|
|
kind: controlFrameKindLoop,
|
|
blk: loopHeader,
|
|
followingBlock: afterLoopBlock,
|
|
blockType: bt,
|
|
})
|
|
|
|
args := c.allocateVarLengthValues(originalLen)
|
|
args = args.Append(builder.VarLengthPool(), state.values[originalLen:]...)
|
|
|
|
// Insert the jump to the header of loop.
|
|
br := builder.AllocateInstruction()
|
|
br.AsJump(args, loopHeader)
|
|
builder.InsertInstruction(br)
|
|
|
|
c.switchTo(originalLen, loopHeader)
|
|
|
|
if c.ensureTermination {
|
|
checkModuleExitCodePtr := builder.AllocateInstruction().
|
|
AsLoad(c.execCtxPtrValue,
|
|
wazevoapi.ExecutionContextOffsetCheckModuleExitCodeTrampolineAddress.U32(),
|
|
ssa.TypeI64,
|
|
).Insert(builder).Return()
|
|
|
|
args := c.allocateVarLengthValues(1, c.execCtxPtrValue)
|
|
builder.AllocateInstruction().
|
|
AsCallIndirect(checkModuleExitCodePtr, &c.checkModuleExitCodeSig, args).
|
|
Insert(builder)
|
|
}
|
|
case wasm.OpcodeIf:
|
|
bt := c.readBlockType()
|
|
|
|
if state.unreachable {
|
|
state.unreachableDepth++
|
|
break
|
|
}
|
|
|
|
v := state.pop()
|
|
thenBlk, elseBlk, followingBlk := builder.AllocateBasicBlock(), builder.AllocateBasicBlock(), builder.AllocateBasicBlock()
|
|
|
|
// We do not make the Wasm-level block parameters as SSA-level block params for if-else blocks
|
|
// since they won't be PHI and the definition is unique.
|
|
|
|
// On the other hand, the following block after if-else-end will likely have
|
|
// multiple definitions (one in Then and another in Else blocks).
|
|
c.addBlockParamsFromWasmTypes(bt.Results, followingBlk)
|
|
|
|
args := c.allocateVarLengthValues(len(bt.Params))
|
|
args = args.Append(builder.VarLengthPool(), state.values[len(state.values)-len(bt.Params):]...)
|
|
|
|
// Insert the conditional jump to the Else block.
|
|
brz := builder.AllocateInstruction()
|
|
brz.AsBrz(v, ssa.ValuesNil, elseBlk)
|
|
builder.InsertInstruction(brz)
|
|
|
|
// Then, insert the jump to the Then block.
|
|
br := builder.AllocateInstruction()
|
|
br.AsJump(ssa.ValuesNil, thenBlk)
|
|
builder.InsertInstruction(br)
|
|
|
|
state.ctrlPush(controlFrame{
|
|
kind: controlFrameKindIfWithoutElse,
|
|
originalStackLenWithoutParam: len(state.values) - len(bt.Params),
|
|
blk: elseBlk,
|
|
followingBlock: followingBlk,
|
|
blockType: bt,
|
|
clonedArgs: args,
|
|
})
|
|
|
|
builder.SetCurrentBlock(thenBlk)
|
|
|
|
// Then and Else (if exists) have only one predecessor.
|
|
builder.Seal(thenBlk)
|
|
builder.Seal(elseBlk)
|
|
case wasm.OpcodeElse:
|
|
ifctrl := state.ctrlPeekAt(0)
|
|
if unreachable := state.unreachable; unreachable && state.unreachableDepth > 0 {
|
|
// If it is currently in unreachable and is a nested if,
|
|
// we just remove the entire else block.
|
|
break
|
|
}
|
|
|
|
ifctrl.kind = controlFrameKindIfWithElse
|
|
if !state.unreachable {
|
|
// If this Then block is currently reachable, we have to insert the branching to the following BB.
|
|
followingBlk := ifctrl.followingBlock // == the BB after if-then-else.
|
|
args := c.nPeekDup(len(ifctrl.blockType.Results))
|
|
c.insertJumpToBlock(args, followingBlk)
|
|
} else {
|
|
state.unreachable = false
|
|
}
|
|
|
|
// Reset the stack so that we can correctly handle the else block.
|
|
state.values = state.values[:ifctrl.originalStackLenWithoutParam]
|
|
elseBlk := ifctrl.blk
|
|
for _, arg := range ifctrl.clonedArgs.View() {
|
|
state.push(arg)
|
|
}
|
|
|
|
builder.SetCurrentBlock(elseBlk)
|
|
|
|
case wasm.OpcodeEnd:
|
|
if state.unreachableDepth > 0 {
|
|
state.unreachableDepth--
|
|
break
|
|
}
|
|
|
|
ctrl := state.ctrlPop()
|
|
followingBlk := ctrl.followingBlock
|
|
|
|
unreachable := state.unreachable
|
|
if !unreachable {
|
|
// Top n-th args will be used as a result of the current control frame.
|
|
args := c.nPeekDup(len(ctrl.blockType.Results))
|
|
|
|
// Insert the unconditional branch to the target.
|
|
c.insertJumpToBlock(args, followingBlk)
|
|
} else { // recover from the unreachable state.
|
|
state.unreachable = false
|
|
}
|
|
|
|
switch ctrl.kind {
|
|
case controlFrameKindFunction:
|
|
break // This is the very end of function.
|
|
case controlFrameKindLoop:
|
|
// Loop header block can be reached from any br/br_table contained in the loop,
|
|
// so now that we've reached End of it, we can seal it.
|
|
builder.Seal(ctrl.blk)
|
|
case controlFrameKindIfWithoutElse:
|
|
// If this is the end of Then block, we have to emit the empty Else block.
|
|
elseBlk := ctrl.blk
|
|
builder.SetCurrentBlock(elseBlk)
|
|
c.insertJumpToBlock(ctrl.clonedArgs, followingBlk)
|
|
}
|
|
|
|
builder.Seal(followingBlk)
|
|
|
|
// Ready to start translating the following block.
|
|
c.switchTo(ctrl.originalStackLenWithoutParam, followingBlk)
|
|
|
|
case wasm.OpcodeBr:
|
|
labelIndex := c.readI32u()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
targetBlk, argNum := state.brTargetArgNumFor(labelIndex)
|
|
args := c.nPeekDup(argNum)
|
|
c.insertJumpToBlock(args, targetBlk)
|
|
|
|
state.unreachable = true
|
|
|
|
case wasm.OpcodeBrIf:
|
|
labelIndex := c.readI32u()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
v := state.pop()
|
|
|
|
targetBlk, argNum := state.brTargetArgNumFor(labelIndex)
|
|
args := c.nPeekDup(argNum)
|
|
var sealTargetBlk bool
|
|
if c.needListener && targetBlk.ReturnBlock() { // In this case, we have to call the listener before returning.
|
|
// Save the currently active block.
|
|
current := builder.CurrentBlock()
|
|
|
|
// Allocate the trampoline block to the return where we call the listener.
|
|
targetBlk = builder.AllocateBasicBlock()
|
|
builder.SetCurrentBlock(targetBlk)
|
|
sealTargetBlk = true
|
|
|
|
c.callListenerAfter()
|
|
|
|
instr := builder.AllocateInstruction()
|
|
instr.AsReturn(args)
|
|
builder.InsertInstruction(instr)
|
|
|
|
args = ssa.ValuesNil
|
|
|
|
// Revert the current block.
|
|
builder.SetCurrentBlock(current)
|
|
}
|
|
|
|
// Insert the conditional jump to the target block.
|
|
brnz := builder.AllocateInstruction()
|
|
brnz.AsBrnz(v, args, targetBlk)
|
|
builder.InsertInstruction(brnz)
|
|
|
|
if sealTargetBlk {
|
|
builder.Seal(targetBlk)
|
|
}
|
|
|
|
// Insert the unconditional jump to the Else block which corresponds to after br_if.
|
|
elseBlk := builder.AllocateBasicBlock()
|
|
c.insertJumpToBlock(ssa.ValuesNil, elseBlk)
|
|
|
|
// Now start translating the instructions after br_if.
|
|
builder.Seal(elseBlk) // Else of br_if has the current block as the only one successor.
|
|
builder.SetCurrentBlock(elseBlk)
|
|
|
|
case wasm.OpcodeBrTable:
|
|
labels := state.tmpForBrTable
|
|
labels = labels[:0]
|
|
labelCount := c.readI32u()
|
|
for i := 0; i < int(labelCount); i++ {
|
|
labels = append(labels, c.readI32u())
|
|
}
|
|
labels = append(labels, c.readI32u()) // default label.
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
index := state.pop()
|
|
if labelCount == 0 { // If this br_table is empty, we can just emit the unconditional jump.
|
|
targetBlk, argNum := state.brTargetArgNumFor(labels[0])
|
|
args := c.nPeekDup(argNum)
|
|
c.insertJumpToBlock(args, targetBlk)
|
|
} else {
|
|
c.lowerBrTable(labels, index)
|
|
}
|
|
state.unreachable = true
|
|
|
|
case wasm.OpcodeNop:
|
|
case wasm.OpcodeReturn:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
if c.needListener {
|
|
c.callListenerAfter()
|
|
}
|
|
|
|
results := c.nPeekDup(c.results())
|
|
instr := builder.AllocateInstruction()
|
|
|
|
instr.AsReturn(results)
|
|
builder.InsertInstruction(instr)
|
|
state.unreachable = true
|
|
|
|
case wasm.OpcodeUnreachable:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
exit := builder.AllocateInstruction()
|
|
exit.AsExitWithCode(c.execCtxPtrValue, wazevoapi.ExitCodeUnreachable)
|
|
builder.InsertInstruction(exit)
|
|
state.unreachable = true
|
|
|
|
case wasm.OpcodeCallIndirect:
|
|
typeIndex := c.readI32u()
|
|
tableIndex := c.readI32u()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c.lowerCallIndirect(typeIndex, tableIndex)
|
|
|
|
case wasm.OpcodeCall:
|
|
fnIndex := c.readI32u()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
var typIndex wasm.Index
|
|
if fnIndex < c.m.ImportFunctionCount {
|
|
// Before transfer the control to the callee, we have to store the current module's moduleContextPtr
|
|
// into execContext.callerModuleContextPtr in case when the callee is a Go function.
|
|
c.storeCallerModuleContext()
|
|
var fi int
|
|
for i := range c.m.ImportSection {
|
|
imp := &c.m.ImportSection[i]
|
|
if imp.Type == wasm.ExternTypeFunc {
|
|
if fi == int(fnIndex) {
|
|
typIndex = imp.DescFunc
|
|
break
|
|
}
|
|
fi++
|
|
}
|
|
}
|
|
} else {
|
|
typIndex = c.m.FunctionSection[fnIndex-c.m.ImportFunctionCount]
|
|
}
|
|
typ := &c.m.TypeSection[typIndex]
|
|
|
|
argN := len(typ.Params)
|
|
tail := len(state.values) - argN
|
|
vs := state.values[tail:]
|
|
state.values = state.values[:tail]
|
|
args := c.allocateVarLengthValues(2+len(vs), c.execCtxPtrValue)
|
|
|
|
sig := c.signatures[typ]
|
|
call := builder.AllocateInstruction()
|
|
if fnIndex >= c.m.ImportFunctionCount {
|
|
args = args.Append(builder.VarLengthPool(), c.moduleCtxPtrValue) // This case the callee module is itself.
|
|
args = args.Append(builder.VarLengthPool(), vs...)
|
|
call.AsCall(FunctionIndexToFuncRef(fnIndex), sig, args)
|
|
builder.InsertInstruction(call)
|
|
} else {
|
|
// This case we have to read the address of the imported function from the module context.
|
|
moduleCtx := c.moduleCtxPtrValue
|
|
loadFuncPtr, loadModuleCtxPtr := builder.AllocateInstruction(), builder.AllocateInstruction()
|
|
funcPtrOffset, moduleCtxPtrOffset, _ := c.offset.ImportedFunctionOffset(fnIndex)
|
|
loadFuncPtr.AsLoad(moduleCtx, funcPtrOffset.U32(), ssa.TypeI64)
|
|
loadModuleCtxPtr.AsLoad(moduleCtx, moduleCtxPtrOffset.U32(), ssa.TypeI64)
|
|
builder.InsertInstruction(loadFuncPtr)
|
|
builder.InsertInstruction(loadModuleCtxPtr)
|
|
|
|
args = args.Append(builder.VarLengthPool(), loadModuleCtxPtr.Return())
|
|
args = args.Append(builder.VarLengthPool(), vs...)
|
|
call.AsCallIndirect(loadFuncPtr.Return(), sig, args)
|
|
builder.InsertInstruction(call)
|
|
}
|
|
|
|
first, rest := call.Returns()
|
|
if first.Valid() {
|
|
state.push(first)
|
|
}
|
|
for _, v := range rest {
|
|
state.push(v)
|
|
}
|
|
|
|
c.reloadAfterCall()
|
|
|
|
case wasm.OpcodeDrop:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
_ = state.pop()
|
|
case wasm.OpcodeF64ConvertI32S, wasm.OpcodeF64ConvertI64S, wasm.OpcodeF64ConvertI32U, wasm.OpcodeF64ConvertI64U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
result := builder.AllocateInstruction().AsFcvtFromInt(
|
|
state.pop(),
|
|
op == wasm.OpcodeF64ConvertI32S || op == wasm.OpcodeF64ConvertI64S,
|
|
true,
|
|
).Insert(builder).Return()
|
|
state.push(result)
|
|
case wasm.OpcodeF32ConvertI32S, wasm.OpcodeF32ConvertI64S, wasm.OpcodeF32ConvertI32U, wasm.OpcodeF32ConvertI64U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
result := builder.AllocateInstruction().AsFcvtFromInt(
|
|
state.pop(),
|
|
op == wasm.OpcodeF32ConvertI32S || op == wasm.OpcodeF32ConvertI64S,
|
|
false,
|
|
).Insert(builder).Return()
|
|
state.push(result)
|
|
case wasm.OpcodeF32DemoteF64:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
cvt := builder.AllocateInstruction()
|
|
cvt.AsFdemote(state.pop())
|
|
builder.InsertInstruction(cvt)
|
|
state.push(cvt.Return())
|
|
case wasm.OpcodeF64PromoteF32:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
cvt := builder.AllocateInstruction()
|
|
cvt.AsFpromote(state.pop())
|
|
builder.InsertInstruction(cvt)
|
|
state.push(cvt.Return())
|
|
|
|
case wasm.OpcodeVecPrefix:
|
|
state.pc++
|
|
vecOp := c.wasmFunctionBody[state.pc]
|
|
switch vecOp {
|
|
case wasm.OpcodeVecV128Const:
|
|
state.pc++
|
|
lo := binary.LittleEndian.Uint64(c.wasmFunctionBody[state.pc:])
|
|
state.pc += 8
|
|
hi := binary.LittleEndian.Uint64(c.wasmFunctionBody[state.pc:])
|
|
state.pc += 7
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
ret := builder.AllocateInstruction().AsVconst(lo, hi).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecV128Load:
|
|
_, offset := c.readMemArg()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
baseAddr := state.pop()
|
|
addr := c.memOpSetup(baseAddr, uint64(offset), 16)
|
|
load := builder.AllocateInstruction()
|
|
load.AsLoad(addr, offset, ssa.TypeV128)
|
|
builder.InsertInstruction(load)
|
|
state.push(load.Return())
|
|
case wasm.OpcodeVecV128Load8Lane, wasm.OpcodeVecV128Load16Lane, wasm.OpcodeVecV128Load32Lane:
|
|
_, offset := c.readMemArg()
|
|
state.pc++
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
var loadOp ssa.Opcode
|
|
var opSize uint64
|
|
switch vecOp {
|
|
case wasm.OpcodeVecV128Load8Lane:
|
|
loadOp, lane, opSize = ssa.OpcodeUload8, ssa.VecLaneI8x16, 1
|
|
case wasm.OpcodeVecV128Load16Lane:
|
|
loadOp, lane, opSize = ssa.OpcodeUload16, ssa.VecLaneI16x8, 2
|
|
case wasm.OpcodeVecV128Load32Lane:
|
|
loadOp, lane, opSize = ssa.OpcodeUload32, ssa.VecLaneI32x4, 4
|
|
}
|
|
laneIndex := c.wasmFunctionBody[state.pc]
|
|
vector := state.pop()
|
|
baseAddr := state.pop()
|
|
addr := c.memOpSetup(baseAddr, uint64(offset), opSize)
|
|
load := builder.AllocateInstruction().
|
|
AsExtLoad(loadOp, addr, offset, false).
|
|
Insert(builder).Return()
|
|
ret := builder.AllocateInstruction().
|
|
AsInsertlane(vector, load, laneIndex, lane).
|
|
Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecV128Load64Lane:
|
|
_, offset := c.readMemArg()
|
|
state.pc++
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
laneIndex := c.wasmFunctionBody[state.pc]
|
|
vector := state.pop()
|
|
baseAddr := state.pop()
|
|
addr := c.memOpSetup(baseAddr, uint64(offset), 8)
|
|
load := builder.AllocateInstruction().
|
|
AsLoad(addr, offset, ssa.TypeI64).
|
|
Insert(builder).Return()
|
|
ret := builder.AllocateInstruction().
|
|
AsInsertlane(vector, load, laneIndex, ssa.VecLaneI64x2).
|
|
Insert(builder).Return()
|
|
state.push(ret)
|
|
|
|
case wasm.OpcodeVecV128Load32zero, wasm.OpcodeVecV128Load64zero:
|
|
_, offset := c.readMemArg()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
var scalarType ssa.Type
|
|
switch vecOp {
|
|
case wasm.OpcodeVecV128Load32zero:
|
|
scalarType = ssa.TypeF32
|
|
case wasm.OpcodeVecV128Load64zero:
|
|
scalarType = ssa.TypeF64
|
|
}
|
|
|
|
baseAddr := state.pop()
|
|
addr := c.memOpSetup(baseAddr, uint64(offset), uint64(scalarType.Size()))
|
|
|
|
ret := builder.AllocateInstruction().
|
|
AsVZeroExtLoad(addr, offset, scalarType).
|
|
Insert(builder).Return()
|
|
state.push(ret)
|
|
|
|
case wasm.OpcodeVecV128Load8x8u, wasm.OpcodeVecV128Load8x8s,
|
|
wasm.OpcodeVecV128Load16x4u, wasm.OpcodeVecV128Load16x4s,
|
|
wasm.OpcodeVecV128Load32x2u, wasm.OpcodeVecV128Load32x2s:
|
|
_, offset := c.readMemArg()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
var signed bool
|
|
switch vecOp {
|
|
case wasm.OpcodeVecV128Load8x8s:
|
|
signed = true
|
|
fallthrough
|
|
case wasm.OpcodeVecV128Load8x8u:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecV128Load16x4s:
|
|
signed = true
|
|
fallthrough
|
|
case wasm.OpcodeVecV128Load16x4u:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecV128Load32x2s:
|
|
signed = true
|
|
fallthrough
|
|
case wasm.OpcodeVecV128Load32x2u:
|
|
lane = ssa.VecLaneI32x4
|
|
}
|
|
baseAddr := state.pop()
|
|
addr := c.memOpSetup(baseAddr, uint64(offset), 8)
|
|
load := builder.AllocateInstruction().
|
|
AsLoad(addr, offset, ssa.TypeF64).
|
|
Insert(builder).Return()
|
|
ret := builder.AllocateInstruction().
|
|
AsWiden(load, lane, signed, true).
|
|
Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecV128Load8Splat, wasm.OpcodeVecV128Load16Splat,
|
|
wasm.OpcodeVecV128Load32Splat, wasm.OpcodeVecV128Load64Splat:
|
|
_, offset := c.readMemArg()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
var opSize uint64
|
|
switch vecOp {
|
|
case wasm.OpcodeVecV128Load8Splat:
|
|
lane, opSize = ssa.VecLaneI8x16, 1
|
|
case wasm.OpcodeVecV128Load16Splat:
|
|
lane, opSize = ssa.VecLaneI16x8, 2
|
|
case wasm.OpcodeVecV128Load32Splat:
|
|
lane, opSize = ssa.VecLaneI32x4, 4
|
|
case wasm.OpcodeVecV128Load64Splat:
|
|
lane, opSize = ssa.VecLaneI64x2, 8
|
|
}
|
|
baseAddr := state.pop()
|
|
addr := c.memOpSetup(baseAddr, uint64(offset), opSize)
|
|
ret := builder.AllocateInstruction().
|
|
AsLoadSplat(addr, offset, lane).
|
|
Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecV128Store:
|
|
_, offset := c.readMemArg()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
value := state.pop()
|
|
baseAddr := state.pop()
|
|
addr := c.memOpSetup(baseAddr, uint64(offset), 16)
|
|
builder.AllocateInstruction().
|
|
AsStore(ssa.OpcodeStore, value, addr, offset).
|
|
Insert(builder)
|
|
case wasm.OpcodeVecV128Store8Lane, wasm.OpcodeVecV128Store16Lane,
|
|
wasm.OpcodeVecV128Store32Lane, wasm.OpcodeVecV128Store64Lane:
|
|
_, offset := c.readMemArg()
|
|
state.pc++
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
laneIndex := c.wasmFunctionBody[state.pc]
|
|
var storeOp ssa.Opcode
|
|
var lane ssa.VecLane
|
|
var opSize uint64
|
|
switch vecOp {
|
|
case wasm.OpcodeVecV128Store8Lane:
|
|
storeOp, lane, opSize = ssa.OpcodeIstore8, ssa.VecLaneI8x16, 1
|
|
case wasm.OpcodeVecV128Store16Lane:
|
|
storeOp, lane, opSize = ssa.OpcodeIstore16, ssa.VecLaneI16x8, 2
|
|
case wasm.OpcodeVecV128Store32Lane:
|
|
storeOp, lane, opSize = ssa.OpcodeIstore32, ssa.VecLaneI32x4, 4
|
|
case wasm.OpcodeVecV128Store64Lane:
|
|
storeOp, lane, opSize = ssa.OpcodeStore, ssa.VecLaneI64x2, 8
|
|
}
|
|
vector := state.pop()
|
|
baseAddr := state.pop()
|
|
addr := c.memOpSetup(baseAddr, uint64(offset), opSize)
|
|
value := builder.AllocateInstruction().
|
|
AsExtractlane(vector, laneIndex, lane, false).
|
|
Insert(builder).Return()
|
|
builder.AllocateInstruction().
|
|
AsStore(storeOp, value, addr, offset).
|
|
Insert(builder)
|
|
case wasm.OpcodeVecV128Not:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVbnot(v1).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecV128And:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVband(v1, v2).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecV128AndNot:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVbandnot(v1, v2).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecV128Or:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVbor(v1, v2).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecV128Xor:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVbxor(v1, v2).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecV128Bitselect:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
c := state.pop()
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVbitselect(c, v1, v2).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecV128AnyTrue:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVanyTrue(v1).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16AllTrue, wasm.OpcodeVecI16x8AllTrue, wasm.OpcodeVecI32x4AllTrue, wasm.OpcodeVecI64x2AllTrue:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16AllTrue:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8AllTrue:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4AllTrue:
|
|
lane = ssa.VecLaneI32x4
|
|
case wasm.OpcodeVecI64x2AllTrue:
|
|
lane = ssa.VecLaneI64x2
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVallTrue(v1, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16BitMask, wasm.OpcodeVecI16x8BitMask, wasm.OpcodeVecI32x4BitMask, wasm.OpcodeVecI64x2BitMask:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16BitMask:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8BitMask:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4BitMask:
|
|
lane = ssa.VecLaneI32x4
|
|
case wasm.OpcodeVecI64x2BitMask:
|
|
lane = ssa.VecLaneI64x2
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVhighBits(v1, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16Abs, wasm.OpcodeVecI16x8Abs, wasm.OpcodeVecI32x4Abs, wasm.OpcodeVecI64x2Abs:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16Abs:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8Abs:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4Abs:
|
|
lane = ssa.VecLaneI32x4
|
|
case wasm.OpcodeVecI64x2Abs:
|
|
lane = ssa.VecLaneI64x2
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVIabs(v1, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16Neg, wasm.OpcodeVecI16x8Neg, wasm.OpcodeVecI32x4Neg, wasm.OpcodeVecI64x2Neg:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16Neg:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8Neg:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4Neg:
|
|
lane = ssa.VecLaneI32x4
|
|
case wasm.OpcodeVecI64x2Neg:
|
|
lane = ssa.VecLaneI64x2
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVIneg(v1, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16Popcnt:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
lane := ssa.VecLaneI8x16
|
|
v1 := state.pop()
|
|
|
|
ret := builder.AllocateInstruction().AsVIpopcnt(v1, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16Add, wasm.OpcodeVecI16x8Add, wasm.OpcodeVecI32x4Add, wasm.OpcodeVecI64x2Add:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16Add:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8Add:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4Add:
|
|
lane = ssa.VecLaneI32x4
|
|
case wasm.OpcodeVecI64x2Add:
|
|
lane = ssa.VecLaneI64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVIadd(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16AddSatS, wasm.OpcodeVecI16x8AddSatS:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16AddSatS:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8AddSatS:
|
|
lane = ssa.VecLaneI16x8
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVSaddSat(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16AddSatU, wasm.OpcodeVecI16x8AddSatU:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16AddSatU:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8AddSatU:
|
|
lane = ssa.VecLaneI16x8
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVUaddSat(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16SubSatS, wasm.OpcodeVecI16x8SubSatS:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16SubSatS:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8SubSatS:
|
|
lane = ssa.VecLaneI16x8
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVSsubSat(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16SubSatU, wasm.OpcodeVecI16x8SubSatU:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16SubSatU:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8SubSatU:
|
|
lane = ssa.VecLaneI16x8
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVUsubSat(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
|
|
case wasm.OpcodeVecI8x16Sub, wasm.OpcodeVecI16x8Sub, wasm.OpcodeVecI32x4Sub, wasm.OpcodeVecI64x2Sub:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16Sub:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8Sub:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4Sub:
|
|
lane = ssa.VecLaneI32x4
|
|
case wasm.OpcodeVecI64x2Sub:
|
|
lane = ssa.VecLaneI64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVIsub(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16MinS, wasm.OpcodeVecI16x8MinS, wasm.OpcodeVecI32x4MinS:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16MinS:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8MinS:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4MinS:
|
|
lane = ssa.VecLaneI32x4
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVImin(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16MinU, wasm.OpcodeVecI16x8MinU, wasm.OpcodeVecI32x4MinU:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16MinU:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8MinU:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4MinU:
|
|
lane = ssa.VecLaneI32x4
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVUmin(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16MaxS, wasm.OpcodeVecI16x8MaxS, wasm.OpcodeVecI32x4MaxS:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16MaxS:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8MaxS:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4MaxS:
|
|
lane = ssa.VecLaneI32x4
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVImax(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16MaxU, wasm.OpcodeVecI16x8MaxU, wasm.OpcodeVecI32x4MaxU:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16MaxU:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8MaxU:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4MaxU:
|
|
lane = ssa.VecLaneI32x4
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVUmax(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16AvgrU, wasm.OpcodeVecI16x8AvgrU:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16AvgrU:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8AvgrU:
|
|
lane = ssa.VecLaneI16x8
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVAvgRound(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI16x8Mul, wasm.OpcodeVecI32x4Mul, wasm.OpcodeVecI64x2Mul:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI16x8Mul:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4Mul:
|
|
lane = ssa.VecLaneI32x4
|
|
case wasm.OpcodeVecI64x2Mul:
|
|
lane = ssa.VecLaneI64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVImul(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI16x8Q15mulrSatS:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsSqmulRoundSat(v1, v2, ssa.VecLaneI16x8).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16Eq, wasm.OpcodeVecI16x8Eq, wasm.OpcodeVecI32x4Eq, wasm.OpcodeVecI64x2Eq:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16Eq:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8Eq:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4Eq:
|
|
lane = ssa.VecLaneI32x4
|
|
case wasm.OpcodeVecI64x2Eq:
|
|
lane = ssa.VecLaneI64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsVIcmp(v1, v2, ssa.IntegerCmpCondEqual, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16Ne, wasm.OpcodeVecI16x8Ne, wasm.OpcodeVecI32x4Ne, wasm.OpcodeVecI64x2Ne:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16Ne:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8Ne:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4Ne:
|
|
lane = ssa.VecLaneI32x4
|
|
case wasm.OpcodeVecI64x2Ne:
|
|
lane = ssa.VecLaneI64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsVIcmp(v1, v2, ssa.IntegerCmpCondNotEqual, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16LtS, wasm.OpcodeVecI16x8LtS, wasm.OpcodeVecI32x4LtS, wasm.OpcodeVecI64x2LtS:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16LtS:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8LtS:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4LtS:
|
|
lane = ssa.VecLaneI32x4
|
|
case wasm.OpcodeVecI64x2LtS:
|
|
lane = ssa.VecLaneI64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsVIcmp(v1, v2, ssa.IntegerCmpCondSignedLessThan, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16LtU, wasm.OpcodeVecI16x8LtU, wasm.OpcodeVecI32x4LtU:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16LtU:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8LtU:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4LtU:
|
|
lane = ssa.VecLaneI32x4
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsVIcmp(v1, v2, ssa.IntegerCmpCondUnsignedLessThan, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16LeS, wasm.OpcodeVecI16x8LeS, wasm.OpcodeVecI32x4LeS, wasm.OpcodeVecI64x2LeS:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16LeS:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8LeS:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4LeS:
|
|
lane = ssa.VecLaneI32x4
|
|
case wasm.OpcodeVecI64x2LeS:
|
|
lane = ssa.VecLaneI64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsVIcmp(v1, v2, ssa.IntegerCmpCondSignedLessThanOrEqual, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16LeU, wasm.OpcodeVecI16x8LeU, wasm.OpcodeVecI32x4LeU:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16LeU:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8LeU:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4LeU:
|
|
lane = ssa.VecLaneI32x4
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsVIcmp(v1, v2, ssa.IntegerCmpCondUnsignedLessThanOrEqual, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16GtS, wasm.OpcodeVecI16x8GtS, wasm.OpcodeVecI32x4GtS, wasm.OpcodeVecI64x2GtS:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16GtS:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8GtS:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4GtS:
|
|
lane = ssa.VecLaneI32x4
|
|
case wasm.OpcodeVecI64x2GtS:
|
|
lane = ssa.VecLaneI64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsVIcmp(v1, v2, ssa.IntegerCmpCondSignedGreaterThan, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16GtU, wasm.OpcodeVecI16x8GtU, wasm.OpcodeVecI32x4GtU:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16GtU:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8GtU:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4GtU:
|
|
lane = ssa.VecLaneI32x4
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsVIcmp(v1, v2, ssa.IntegerCmpCondUnsignedGreaterThan, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16GeS, wasm.OpcodeVecI16x8GeS, wasm.OpcodeVecI32x4GeS, wasm.OpcodeVecI64x2GeS:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16GeS:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8GeS:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4GeS:
|
|
lane = ssa.VecLaneI32x4
|
|
case wasm.OpcodeVecI64x2GeS:
|
|
lane = ssa.VecLaneI64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsVIcmp(v1, v2, ssa.IntegerCmpCondSignedGreaterThanOrEqual, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16GeU, wasm.OpcodeVecI16x8GeU, wasm.OpcodeVecI32x4GeU:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16GeU:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8GeU:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4GeU:
|
|
lane = ssa.VecLaneI32x4
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsVIcmp(v1, v2, ssa.IntegerCmpCondUnsignedGreaterThanOrEqual, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecF32x4Max, wasm.OpcodeVecF64x2Max:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecF32x4Max:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2Max:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVFmax(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecF32x4Abs, wasm.OpcodeVecF64x2Abs:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecF32x4Abs:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2Abs:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVFabs(v1, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecF32x4Min, wasm.OpcodeVecF64x2Min:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecF32x4Min:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2Min:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVFmin(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecF32x4Neg, wasm.OpcodeVecF64x2Neg:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecF32x4Neg:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2Neg:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVFneg(v1, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecF32x4Sqrt, wasm.OpcodeVecF64x2Sqrt:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecF32x4Sqrt:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2Sqrt:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVSqrt(v1, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
|
|
case wasm.OpcodeVecF32x4Add, wasm.OpcodeVecF64x2Add:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecF32x4Add:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2Add:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVFadd(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecF32x4Sub, wasm.OpcodeVecF64x2Sub:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecF32x4Sub:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2Sub:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVFsub(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecF32x4Mul, wasm.OpcodeVecF64x2Mul:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecF32x4Mul:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2Mul:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVFmul(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecF32x4Div, wasm.OpcodeVecF64x2Div:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecF32x4Div:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2Div:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVFdiv(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
|
|
case wasm.OpcodeVecI16x8ExtaddPairwiseI8x16S, wasm.OpcodeVecI16x8ExtaddPairwiseI8x16U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v := state.pop()
|
|
signed := vecOp == wasm.OpcodeVecI16x8ExtaddPairwiseI8x16S
|
|
ret := builder.AllocateInstruction().AsExtIaddPairwise(v, ssa.VecLaneI8x16, signed).Insert(builder).Return()
|
|
state.push(ret)
|
|
|
|
case wasm.OpcodeVecI32x4ExtaddPairwiseI16x8S, wasm.OpcodeVecI32x4ExtaddPairwiseI16x8U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v := state.pop()
|
|
signed := vecOp == wasm.OpcodeVecI32x4ExtaddPairwiseI16x8S
|
|
ret := builder.AllocateInstruction().AsExtIaddPairwise(v, ssa.VecLaneI16x8, signed).Insert(builder).Return()
|
|
state.push(ret)
|
|
|
|
case wasm.OpcodeVecI16x8ExtMulLowI8x16S, wasm.OpcodeVecI16x8ExtMulLowI8x16U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := c.lowerExtMul(
|
|
v1, v2,
|
|
ssa.VecLaneI8x16, ssa.VecLaneI16x8,
|
|
vecOp == wasm.OpcodeVecI16x8ExtMulLowI8x16S, true)
|
|
state.push(ret)
|
|
|
|
case wasm.OpcodeVecI16x8ExtMulHighI8x16S, wasm.OpcodeVecI16x8ExtMulHighI8x16U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := c.lowerExtMul(
|
|
v1, v2,
|
|
ssa.VecLaneI8x16, ssa.VecLaneI16x8,
|
|
vecOp == wasm.OpcodeVecI16x8ExtMulHighI8x16S, false)
|
|
state.push(ret)
|
|
|
|
case wasm.OpcodeVecI32x4ExtMulLowI16x8S, wasm.OpcodeVecI32x4ExtMulLowI16x8U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := c.lowerExtMul(
|
|
v1, v2,
|
|
ssa.VecLaneI16x8, ssa.VecLaneI32x4,
|
|
vecOp == wasm.OpcodeVecI32x4ExtMulLowI16x8S, true)
|
|
state.push(ret)
|
|
|
|
case wasm.OpcodeVecI32x4ExtMulHighI16x8S, wasm.OpcodeVecI32x4ExtMulHighI16x8U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := c.lowerExtMul(
|
|
v1, v2,
|
|
ssa.VecLaneI16x8, ssa.VecLaneI32x4,
|
|
vecOp == wasm.OpcodeVecI32x4ExtMulHighI16x8S, false)
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI64x2ExtMulLowI32x4S, wasm.OpcodeVecI64x2ExtMulLowI32x4U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := c.lowerExtMul(
|
|
v1, v2,
|
|
ssa.VecLaneI32x4, ssa.VecLaneI64x2,
|
|
vecOp == wasm.OpcodeVecI64x2ExtMulLowI32x4S, true)
|
|
state.push(ret)
|
|
|
|
case wasm.OpcodeVecI64x2ExtMulHighI32x4S, wasm.OpcodeVecI64x2ExtMulHighI32x4U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := c.lowerExtMul(
|
|
v1, v2,
|
|
ssa.VecLaneI32x4, ssa.VecLaneI64x2,
|
|
vecOp == wasm.OpcodeVecI64x2ExtMulHighI32x4S, false)
|
|
state.push(ret)
|
|
|
|
case wasm.OpcodeVecI32x4DotI16x8S:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
|
|
ret := builder.AllocateInstruction().AsWideningPairwiseDotProductS(v1, v2).Insert(builder).Return()
|
|
state.push(ret)
|
|
|
|
case wasm.OpcodeVecF32x4Eq, wasm.OpcodeVecF64x2Eq:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecF32x4Eq:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2Eq:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsVFcmp(v1, v2, ssa.FloatCmpCondEqual, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecF32x4Ne, wasm.OpcodeVecF64x2Ne:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecF32x4Ne:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2Ne:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsVFcmp(v1, v2, ssa.FloatCmpCondNotEqual, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecF32x4Lt, wasm.OpcodeVecF64x2Lt:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecF32x4Lt:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2Lt:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsVFcmp(v1, v2, ssa.FloatCmpCondLessThan, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecF32x4Le, wasm.OpcodeVecF64x2Le:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecF32x4Le:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2Le:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsVFcmp(v1, v2, ssa.FloatCmpCondLessThanOrEqual, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecF32x4Gt, wasm.OpcodeVecF64x2Gt:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecF32x4Gt:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2Gt:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsVFcmp(v1, v2, ssa.FloatCmpCondGreaterThan, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecF32x4Ge, wasm.OpcodeVecF64x2Ge:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecF32x4Ge:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2Ge:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsVFcmp(v1, v2, ssa.FloatCmpCondGreaterThanOrEqual, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecF32x4Ceil, wasm.OpcodeVecF64x2Ceil:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecF32x4Ceil:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2Ceil:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVCeil(v1, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecF32x4Floor, wasm.OpcodeVecF64x2Floor:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecF32x4Floor:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2Floor:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVFloor(v1, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecF32x4Trunc, wasm.OpcodeVecF64x2Trunc:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecF32x4Trunc:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2Trunc:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVTrunc(v1, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecF32x4Nearest, wasm.OpcodeVecF64x2Nearest:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecF32x4Nearest:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2Nearest:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVNearest(v1, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecF32x4Pmin, wasm.OpcodeVecF64x2Pmin:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecF32x4Pmin:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2Pmin:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVMinPseudo(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecF32x4Pmax, wasm.OpcodeVecF64x2Pmax:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecF32x4Pmax:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2Pmax:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVMaxPseudo(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI32x4TruncSatF32x4S, wasm.OpcodeVecI32x4TruncSatF32x4U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsVFcvtToIntSat(v1, ssa.VecLaneF32x4, vecOp == wasm.OpcodeVecI32x4TruncSatF32x4S).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI32x4TruncSatF64x2SZero, wasm.OpcodeVecI32x4TruncSatF64x2UZero:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsVFcvtToIntSat(v1, ssa.VecLaneF64x2, vecOp == wasm.OpcodeVecI32x4TruncSatF64x2SZero).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecF32x4ConvertI32x4S, wasm.OpcodeVecF32x4ConvertI32x4U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsVFcvtFromInt(v1, ssa.VecLaneF32x4, vecOp == wasm.OpcodeVecF32x4ConvertI32x4S).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecF64x2ConvertLowI32x4S, wasm.OpcodeVecF64x2ConvertLowI32x4U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v1 := state.pop()
|
|
if runtime.GOARCH == "arm64" {
|
|
// TODO: this is weird. fix.
|
|
v1 = builder.AllocateInstruction().
|
|
AsWiden(v1, ssa.VecLaneI32x4, vecOp == wasm.OpcodeVecF64x2ConvertLowI32x4S, true).Insert(builder).Return()
|
|
}
|
|
ret := builder.AllocateInstruction().
|
|
AsVFcvtFromInt(v1, ssa.VecLaneF64x2, vecOp == wasm.OpcodeVecF64x2ConvertLowI32x4S).
|
|
Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16NarrowI16x8S, wasm.OpcodeVecI8x16NarrowI16x8U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsNarrow(v1, v2, ssa.VecLaneI16x8, vecOp == wasm.OpcodeVecI8x16NarrowI16x8S).
|
|
Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI16x8NarrowI32x4S, wasm.OpcodeVecI16x8NarrowI32x4U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsNarrow(v1, v2, ssa.VecLaneI32x4, vecOp == wasm.OpcodeVecI16x8NarrowI32x4S).
|
|
Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI16x8ExtendLowI8x16S, wasm.OpcodeVecI16x8ExtendLowI8x16U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsWiden(v1, ssa.VecLaneI8x16, vecOp == wasm.OpcodeVecI16x8ExtendLowI8x16S, true).
|
|
Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI16x8ExtendHighI8x16S, wasm.OpcodeVecI16x8ExtendHighI8x16U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsWiden(v1, ssa.VecLaneI8x16, vecOp == wasm.OpcodeVecI16x8ExtendHighI8x16S, false).
|
|
Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI32x4ExtendLowI16x8S, wasm.OpcodeVecI32x4ExtendLowI16x8U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsWiden(v1, ssa.VecLaneI16x8, vecOp == wasm.OpcodeVecI32x4ExtendLowI16x8S, true).
|
|
Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI32x4ExtendHighI16x8S, wasm.OpcodeVecI32x4ExtendHighI16x8U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsWiden(v1, ssa.VecLaneI16x8, vecOp == wasm.OpcodeVecI32x4ExtendHighI16x8S, false).
|
|
Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI64x2ExtendLowI32x4S, wasm.OpcodeVecI64x2ExtendLowI32x4U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsWiden(v1, ssa.VecLaneI32x4, vecOp == wasm.OpcodeVecI64x2ExtendLowI32x4S, true).
|
|
Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI64x2ExtendHighI32x4S, wasm.OpcodeVecI64x2ExtendHighI32x4U:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsWiden(v1, ssa.VecLaneI32x4, vecOp == wasm.OpcodeVecI64x2ExtendHighI32x4S, false).
|
|
Insert(builder).Return()
|
|
state.push(ret)
|
|
|
|
case wasm.OpcodeVecF64x2PromoteLowF32x4Zero:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsFvpromoteLow(v1, ssa.VecLaneF32x4).
|
|
Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecF32x4DemoteF64x2Zero:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().
|
|
AsFvdemote(v1, ssa.VecLaneF64x2).
|
|
Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16Shl, wasm.OpcodeVecI16x8Shl, wasm.OpcodeVecI32x4Shl, wasm.OpcodeVecI64x2Shl:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16Shl:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8Shl:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4Shl:
|
|
lane = ssa.VecLaneI32x4
|
|
case wasm.OpcodeVecI64x2Shl:
|
|
lane = ssa.VecLaneI64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVIshl(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16ShrS, wasm.OpcodeVecI16x8ShrS, wasm.OpcodeVecI32x4ShrS, wasm.OpcodeVecI64x2ShrS:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16ShrS:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8ShrS:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4ShrS:
|
|
lane = ssa.VecLaneI32x4
|
|
case wasm.OpcodeVecI64x2ShrS:
|
|
lane = ssa.VecLaneI64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVSshr(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16ShrU, wasm.OpcodeVecI16x8ShrU, wasm.OpcodeVecI32x4ShrU, wasm.OpcodeVecI64x2ShrU:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16ShrU:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8ShrU:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4ShrU:
|
|
lane = ssa.VecLaneI32x4
|
|
case wasm.OpcodeVecI64x2ShrU:
|
|
lane = ssa.VecLaneI64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsVUshr(v1, v2, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecI8x16ExtractLaneS, wasm.OpcodeVecI16x8ExtractLaneS:
|
|
state.pc++
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16ExtractLaneS:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8ExtractLaneS:
|
|
lane = ssa.VecLaneI16x8
|
|
}
|
|
v1 := state.pop()
|
|
index := c.wasmFunctionBody[state.pc]
|
|
ext := builder.AllocateInstruction().AsExtractlane(v1, index, lane, true).Insert(builder).Return()
|
|
state.push(ext)
|
|
case wasm.OpcodeVecI8x16ExtractLaneU, wasm.OpcodeVecI16x8ExtractLaneU,
|
|
wasm.OpcodeVecI32x4ExtractLane, wasm.OpcodeVecI64x2ExtractLane,
|
|
wasm.OpcodeVecF32x4ExtractLane, wasm.OpcodeVecF64x2ExtractLane:
|
|
state.pc++ // Skip the immediate value.
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16ExtractLaneU:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8ExtractLaneU:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4ExtractLane:
|
|
lane = ssa.VecLaneI32x4
|
|
case wasm.OpcodeVecI64x2ExtractLane:
|
|
lane = ssa.VecLaneI64x2
|
|
case wasm.OpcodeVecF32x4ExtractLane:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2ExtractLane:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v1 := state.pop()
|
|
index := c.wasmFunctionBody[state.pc]
|
|
ext := builder.AllocateInstruction().AsExtractlane(v1, index, lane, false).Insert(builder).Return()
|
|
state.push(ext)
|
|
case wasm.OpcodeVecI8x16ReplaceLane, wasm.OpcodeVecI16x8ReplaceLane,
|
|
wasm.OpcodeVecI32x4ReplaceLane, wasm.OpcodeVecI64x2ReplaceLane,
|
|
wasm.OpcodeVecF32x4ReplaceLane, wasm.OpcodeVecF64x2ReplaceLane:
|
|
state.pc++
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16ReplaceLane:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8ReplaceLane:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4ReplaceLane:
|
|
lane = ssa.VecLaneI32x4
|
|
case wasm.OpcodeVecI64x2ReplaceLane:
|
|
lane = ssa.VecLaneI64x2
|
|
case wasm.OpcodeVecF32x4ReplaceLane:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2ReplaceLane:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
index := c.wasmFunctionBody[state.pc]
|
|
ret := builder.AllocateInstruction().AsInsertlane(v1, v2, index, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeVecV128i8x16Shuffle:
|
|
state.pc++
|
|
laneIndexes := c.wasmFunctionBody[state.pc : state.pc+16]
|
|
state.pc += 15
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsShuffle(v1, v2, laneIndexes).Insert(builder).Return()
|
|
state.push(ret)
|
|
|
|
case wasm.OpcodeVecI8x16Swizzle:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
v2 := state.pop()
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsSwizzle(v1, v2, ssa.VecLaneI8x16).Insert(builder).Return()
|
|
state.push(ret)
|
|
|
|
case wasm.OpcodeVecI8x16Splat,
|
|
wasm.OpcodeVecI16x8Splat,
|
|
wasm.OpcodeVecI32x4Splat,
|
|
wasm.OpcodeVecI64x2Splat,
|
|
wasm.OpcodeVecF32x4Splat,
|
|
wasm.OpcodeVecF64x2Splat:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
var lane ssa.VecLane
|
|
switch vecOp {
|
|
case wasm.OpcodeVecI8x16Splat:
|
|
lane = ssa.VecLaneI8x16
|
|
case wasm.OpcodeVecI16x8Splat:
|
|
lane = ssa.VecLaneI16x8
|
|
case wasm.OpcodeVecI32x4Splat:
|
|
lane = ssa.VecLaneI32x4
|
|
case wasm.OpcodeVecI64x2Splat:
|
|
lane = ssa.VecLaneI64x2
|
|
case wasm.OpcodeVecF32x4Splat:
|
|
lane = ssa.VecLaneF32x4
|
|
case wasm.OpcodeVecF64x2Splat:
|
|
lane = ssa.VecLaneF64x2
|
|
}
|
|
v1 := state.pop()
|
|
ret := builder.AllocateInstruction().AsSplat(v1, lane).Insert(builder).Return()
|
|
state.push(ret)
|
|
|
|
default:
|
|
panic("TODO: unsupported vector instruction: " + wasm.VectorInstructionName(vecOp))
|
|
}
|
|
case wasm.OpcodeAtomicPrefix:
|
|
state.pc++
|
|
atomicOp := c.wasmFunctionBody[state.pc]
|
|
switch atomicOp {
|
|
case wasm.OpcodeAtomicMemoryWait32, wasm.OpcodeAtomicMemoryWait64:
|
|
_, offset := c.readMemArg()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
c.storeCallerModuleContext()
|
|
|
|
var opSize uint64
|
|
var trampoline wazevoapi.Offset
|
|
var sig *ssa.Signature
|
|
switch atomicOp {
|
|
case wasm.OpcodeAtomicMemoryWait32:
|
|
opSize = 4
|
|
trampoline = wazevoapi.ExecutionContextOffsetMemoryWait32TrampolineAddress
|
|
sig = &c.memoryWait32Sig
|
|
case wasm.OpcodeAtomicMemoryWait64:
|
|
opSize = 8
|
|
trampoline = wazevoapi.ExecutionContextOffsetMemoryWait64TrampolineAddress
|
|
sig = &c.memoryWait64Sig
|
|
}
|
|
|
|
timeout := state.pop()
|
|
exp := state.pop()
|
|
baseAddr := state.pop()
|
|
addr := c.atomicMemOpSetup(baseAddr, uint64(offset), opSize)
|
|
|
|
memoryWaitPtr := builder.AllocateInstruction().
|
|
AsLoad(c.execCtxPtrValue,
|
|
trampoline.U32(),
|
|
ssa.TypeI64,
|
|
).Insert(builder).Return()
|
|
|
|
args := c.allocateVarLengthValues(3, c.execCtxPtrValue, timeout, exp, addr)
|
|
memoryWaitRet := builder.AllocateInstruction().
|
|
AsCallIndirect(memoryWaitPtr, sig, args).
|
|
Insert(builder).Return()
|
|
state.push(memoryWaitRet)
|
|
case wasm.OpcodeAtomicMemoryNotify:
|
|
_, offset := c.readMemArg()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
c.storeCallerModuleContext()
|
|
count := state.pop()
|
|
baseAddr := state.pop()
|
|
addr := c.atomicMemOpSetup(baseAddr, uint64(offset), 4)
|
|
|
|
memoryNotifyPtr := builder.AllocateInstruction().
|
|
AsLoad(c.execCtxPtrValue,
|
|
wazevoapi.ExecutionContextOffsetMemoryNotifyTrampolineAddress.U32(),
|
|
ssa.TypeI64,
|
|
).Insert(builder).Return()
|
|
args := c.allocateVarLengthValues(2, c.execCtxPtrValue, count, addr)
|
|
memoryNotifyRet := builder.AllocateInstruction().
|
|
AsCallIndirect(memoryNotifyPtr, &c.memoryNotifySig, args).
|
|
Insert(builder).Return()
|
|
state.push(memoryNotifyRet)
|
|
case wasm.OpcodeAtomicI32Load, wasm.OpcodeAtomicI64Load, wasm.OpcodeAtomicI32Load8U, wasm.OpcodeAtomicI32Load16U, wasm.OpcodeAtomicI64Load8U, wasm.OpcodeAtomicI64Load16U, wasm.OpcodeAtomicI64Load32U:
|
|
_, offset := c.readMemArg()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
baseAddr := state.pop()
|
|
|
|
var size uint64
|
|
switch atomicOp {
|
|
case wasm.OpcodeAtomicI64Load:
|
|
size = 8
|
|
case wasm.OpcodeAtomicI32Load, wasm.OpcodeAtomicI64Load32U:
|
|
size = 4
|
|
case wasm.OpcodeAtomicI32Load16U, wasm.OpcodeAtomicI64Load16U:
|
|
size = 2
|
|
case wasm.OpcodeAtomicI32Load8U, wasm.OpcodeAtomicI64Load8U:
|
|
size = 1
|
|
}
|
|
|
|
var typ ssa.Type
|
|
switch atomicOp {
|
|
case wasm.OpcodeAtomicI64Load, wasm.OpcodeAtomicI64Load32U, wasm.OpcodeAtomicI64Load16U, wasm.OpcodeAtomicI64Load8U:
|
|
typ = ssa.TypeI64
|
|
case wasm.OpcodeAtomicI32Load, wasm.OpcodeAtomicI32Load16U, wasm.OpcodeAtomicI32Load8U:
|
|
typ = ssa.TypeI32
|
|
}
|
|
|
|
addr := c.atomicMemOpSetup(baseAddr, uint64(offset), size)
|
|
res := builder.AllocateInstruction().AsAtomicLoad(addr, size, typ).Insert(builder).Return()
|
|
state.push(res)
|
|
case wasm.OpcodeAtomicI32Store, wasm.OpcodeAtomicI64Store, wasm.OpcodeAtomicI32Store8, wasm.OpcodeAtomicI32Store16, wasm.OpcodeAtomicI64Store8, wasm.OpcodeAtomicI64Store16, wasm.OpcodeAtomicI64Store32:
|
|
_, offset := c.readMemArg()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
val := state.pop()
|
|
baseAddr := state.pop()
|
|
|
|
var size uint64
|
|
switch atomicOp {
|
|
case wasm.OpcodeAtomicI64Store:
|
|
size = 8
|
|
case wasm.OpcodeAtomicI32Store, wasm.OpcodeAtomicI64Store32:
|
|
size = 4
|
|
case wasm.OpcodeAtomicI32Store16, wasm.OpcodeAtomicI64Store16:
|
|
size = 2
|
|
case wasm.OpcodeAtomicI32Store8, wasm.OpcodeAtomicI64Store8:
|
|
size = 1
|
|
}
|
|
|
|
addr := c.atomicMemOpSetup(baseAddr, uint64(offset), size)
|
|
builder.AllocateInstruction().AsAtomicStore(addr, val, size).Insert(builder)
|
|
case wasm.OpcodeAtomicI32RmwAdd, wasm.OpcodeAtomicI64RmwAdd, wasm.OpcodeAtomicI32Rmw8AddU, wasm.OpcodeAtomicI32Rmw16AddU, wasm.OpcodeAtomicI64Rmw8AddU, wasm.OpcodeAtomicI64Rmw16AddU, wasm.OpcodeAtomicI64Rmw32AddU,
|
|
wasm.OpcodeAtomicI32RmwSub, wasm.OpcodeAtomicI64RmwSub, wasm.OpcodeAtomicI32Rmw8SubU, wasm.OpcodeAtomicI32Rmw16SubU, wasm.OpcodeAtomicI64Rmw8SubU, wasm.OpcodeAtomicI64Rmw16SubU, wasm.OpcodeAtomicI64Rmw32SubU,
|
|
wasm.OpcodeAtomicI32RmwAnd, wasm.OpcodeAtomicI64RmwAnd, wasm.OpcodeAtomicI32Rmw8AndU, wasm.OpcodeAtomicI32Rmw16AndU, wasm.OpcodeAtomicI64Rmw8AndU, wasm.OpcodeAtomicI64Rmw16AndU, wasm.OpcodeAtomicI64Rmw32AndU,
|
|
wasm.OpcodeAtomicI32RmwOr, wasm.OpcodeAtomicI64RmwOr, wasm.OpcodeAtomicI32Rmw8OrU, wasm.OpcodeAtomicI32Rmw16OrU, wasm.OpcodeAtomicI64Rmw8OrU, wasm.OpcodeAtomicI64Rmw16OrU, wasm.OpcodeAtomicI64Rmw32OrU,
|
|
wasm.OpcodeAtomicI32RmwXor, wasm.OpcodeAtomicI64RmwXor, wasm.OpcodeAtomicI32Rmw8XorU, wasm.OpcodeAtomicI32Rmw16XorU, wasm.OpcodeAtomicI64Rmw8XorU, wasm.OpcodeAtomicI64Rmw16XorU, wasm.OpcodeAtomicI64Rmw32XorU,
|
|
wasm.OpcodeAtomicI32RmwXchg, wasm.OpcodeAtomicI64RmwXchg, wasm.OpcodeAtomicI32Rmw8XchgU, wasm.OpcodeAtomicI32Rmw16XchgU, wasm.OpcodeAtomicI64Rmw8XchgU, wasm.OpcodeAtomicI64Rmw16XchgU, wasm.OpcodeAtomicI64Rmw32XchgU:
|
|
_, offset := c.readMemArg()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
val := state.pop()
|
|
baseAddr := state.pop()
|
|
|
|
var rmwOp ssa.AtomicRmwOp
|
|
var size uint64
|
|
switch atomicOp {
|
|
case wasm.OpcodeAtomicI32RmwAdd, wasm.OpcodeAtomicI64RmwAdd, wasm.OpcodeAtomicI32Rmw8AddU, wasm.OpcodeAtomicI32Rmw16AddU, wasm.OpcodeAtomicI64Rmw8AddU, wasm.OpcodeAtomicI64Rmw16AddU, wasm.OpcodeAtomicI64Rmw32AddU:
|
|
rmwOp = ssa.AtomicRmwOpAdd
|
|
switch atomicOp {
|
|
case wasm.OpcodeAtomicI64RmwAdd:
|
|
size = 8
|
|
case wasm.OpcodeAtomicI32RmwAdd, wasm.OpcodeAtomicI64Rmw32AddU:
|
|
size = 4
|
|
case wasm.OpcodeAtomicI32Rmw16AddU, wasm.OpcodeAtomicI64Rmw16AddU:
|
|
size = 2
|
|
case wasm.OpcodeAtomicI32Rmw8AddU, wasm.OpcodeAtomicI64Rmw8AddU:
|
|
size = 1
|
|
}
|
|
case wasm.OpcodeAtomicI32RmwSub, wasm.OpcodeAtomicI64RmwSub, wasm.OpcodeAtomicI32Rmw8SubU, wasm.OpcodeAtomicI32Rmw16SubU, wasm.OpcodeAtomicI64Rmw8SubU, wasm.OpcodeAtomicI64Rmw16SubU, wasm.OpcodeAtomicI64Rmw32SubU:
|
|
rmwOp = ssa.AtomicRmwOpSub
|
|
switch atomicOp {
|
|
case wasm.OpcodeAtomicI64RmwSub:
|
|
size = 8
|
|
case wasm.OpcodeAtomicI32RmwSub, wasm.OpcodeAtomicI64Rmw32SubU:
|
|
size = 4
|
|
case wasm.OpcodeAtomicI32Rmw16SubU, wasm.OpcodeAtomicI64Rmw16SubU:
|
|
size = 2
|
|
case wasm.OpcodeAtomicI32Rmw8SubU, wasm.OpcodeAtomicI64Rmw8SubU:
|
|
size = 1
|
|
}
|
|
case wasm.OpcodeAtomicI32RmwAnd, wasm.OpcodeAtomicI64RmwAnd, wasm.OpcodeAtomicI32Rmw8AndU, wasm.OpcodeAtomicI32Rmw16AndU, wasm.OpcodeAtomicI64Rmw8AndU, wasm.OpcodeAtomicI64Rmw16AndU, wasm.OpcodeAtomicI64Rmw32AndU:
|
|
rmwOp = ssa.AtomicRmwOpAnd
|
|
switch atomicOp {
|
|
case wasm.OpcodeAtomicI64RmwAnd:
|
|
size = 8
|
|
case wasm.OpcodeAtomicI32RmwAnd, wasm.OpcodeAtomicI64Rmw32AndU:
|
|
size = 4
|
|
case wasm.OpcodeAtomicI32Rmw16AndU, wasm.OpcodeAtomicI64Rmw16AndU:
|
|
size = 2
|
|
case wasm.OpcodeAtomicI32Rmw8AndU, wasm.OpcodeAtomicI64Rmw8AndU:
|
|
size = 1
|
|
}
|
|
case wasm.OpcodeAtomicI32RmwOr, wasm.OpcodeAtomicI64RmwOr, wasm.OpcodeAtomicI32Rmw8OrU, wasm.OpcodeAtomicI32Rmw16OrU, wasm.OpcodeAtomicI64Rmw8OrU, wasm.OpcodeAtomicI64Rmw16OrU, wasm.OpcodeAtomicI64Rmw32OrU:
|
|
rmwOp = ssa.AtomicRmwOpOr
|
|
switch atomicOp {
|
|
case wasm.OpcodeAtomicI64RmwOr:
|
|
size = 8
|
|
case wasm.OpcodeAtomicI32RmwOr, wasm.OpcodeAtomicI64Rmw32OrU:
|
|
size = 4
|
|
case wasm.OpcodeAtomicI32Rmw16OrU, wasm.OpcodeAtomicI64Rmw16OrU:
|
|
size = 2
|
|
case wasm.OpcodeAtomicI32Rmw8OrU, wasm.OpcodeAtomicI64Rmw8OrU:
|
|
size = 1
|
|
}
|
|
case wasm.OpcodeAtomicI32RmwXor, wasm.OpcodeAtomicI64RmwXor, wasm.OpcodeAtomicI32Rmw8XorU, wasm.OpcodeAtomicI32Rmw16XorU, wasm.OpcodeAtomicI64Rmw8XorU, wasm.OpcodeAtomicI64Rmw16XorU, wasm.OpcodeAtomicI64Rmw32XorU:
|
|
rmwOp = ssa.AtomicRmwOpXor
|
|
switch atomicOp {
|
|
case wasm.OpcodeAtomicI64RmwXor:
|
|
size = 8
|
|
case wasm.OpcodeAtomicI32RmwXor, wasm.OpcodeAtomicI64Rmw32XorU:
|
|
size = 4
|
|
case wasm.OpcodeAtomicI32Rmw16XorU, wasm.OpcodeAtomicI64Rmw16XorU:
|
|
size = 2
|
|
case wasm.OpcodeAtomicI32Rmw8XorU, wasm.OpcodeAtomicI64Rmw8XorU:
|
|
size = 1
|
|
}
|
|
case wasm.OpcodeAtomicI32RmwXchg, wasm.OpcodeAtomicI64RmwXchg, wasm.OpcodeAtomicI32Rmw8XchgU, wasm.OpcodeAtomicI32Rmw16XchgU, wasm.OpcodeAtomicI64Rmw8XchgU, wasm.OpcodeAtomicI64Rmw16XchgU, wasm.OpcodeAtomicI64Rmw32XchgU:
|
|
rmwOp = ssa.AtomicRmwOpXchg
|
|
switch atomicOp {
|
|
case wasm.OpcodeAtomicI64RmwXchg:
|
|
size = 8
|
|
case wasm.OpcodeAtomicI32RmwXchg, wasm.OpcodeAtomicI64Rmw32XchgU:
|
|
size = 4
|
|
case wasm.OpcodeAtomicI32Rmw16XchgU, wasm.OpcodeAtomicI64Rmw16XchgU:
|
|
size = 2
|
|
case wasm.OpcodeAtomicI32Rmw8XchgU, wasm.OpcodeAtomicI64Rmw8XchgU:
|
|
size = 1
|
|
}
|
|
}
|
|
|
|
addr := c.atomicMemOpSetup(baseAddr, uint64(offset), size)
|
|
res := builder.AllocateInstruction().AsAtomicRmw(rmwOp, addr, val, size).Insert(builder).Return()
|
|
state.push(res)
|
|
case wasm.OpcodeAtomicI32RmwCmpxchg, wasm.OpcodeAtomicI64RmwCmpxchg, wasm.OpcodeAtomicI32Rmw8CmpxchgU, wasm.OpcodeAtomicI32Rmw16CmpxchgU, wasm.OpcodeAtomicI64Rmw8CmpxchgU, wasm.OpcodeAtomicI64Rmw16CmpxchgU, wasm.OpcodeAtomicI64Rmw32CmpxchgU:
|
|
_, offset := c.readMemArg()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
repl := state.pop()
|
|
exp := state.pop()
|
|
baseAddr := state.pop()
|
|
|
|
var size uint64
|
|
switch atomicOp {
|
|
case wasm.OpcodeAtomicI64RmwCmpxchg:
|
|
size = 8
|
|
case wasm.OpcodeAtomicI32RmwCmpxchg, wasm.OpcodeAtomicI64Rmw32CmpxchgU:
|
|
size = 4
|
|
case wasm.OpcodeAtomicI32Rmw16CmpxchgU, wasm.OpcodeAtomicI64Rmw16CmpxchgU:
|
|
size = 2
|
|
case wasm.OpcodeAtomicI32Rmw8CmpxchgU, wasm.OpcodeAtomicI64Rmw8CmpxchgU:
|
|
size = 1
|
|
}
|
|
addr := c.atomicMemOpSetup(baseAddr, uint64(offset), size)
|
|
res := builder.AllocateInstruction().AsAtomicCas(addr, exp, repl, size).Insert(builder).Return()
|
|
state.push(res)
|
|
case wasm.OpcodeAtomicFence:
|
|
order := c.readByte()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
if c.needMemory {
|
|
builder.AllocateInstruction().AsFence(order).Insert(builder)
|
|
}
|
|
default:
|
|
panic("TODO: unsupported atomic instruction: " + wasm.AtomicInstructionName(atomicOp))
|
|
}
|
|
case wasm.OpcodeRefFunc:
|
|
funcIndex := c.readI32u()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
|
|
c.storeCallerModuleContext()
|
|
|
|
funcIndexVal := builder.AllocateInstruction().AsIconst32(funcIndex).Insert(builder).Return()
|
|
|
|
refFuncPtr := builder.AllocateInstruction().
|
|
AsLoad(c.execCtxPtrValue,
|
|
wazevoapi.ExecutionContextOffsetRefFuncTrampolineAddress.U32(),
|
|
ssa.TypeI64,
|
|
).Insert(builder).Return()
|
|
|
|
args := c.allocateVarLengthValues(2, c.execCtxPtrValue, funcIndexVal)
|
|
refFuncRet := builder.
|
|
AllocateInstruction().
|
|
AsCallIndirect(refFuncPtr, &c.refFuncSig, args).
|
|
Insert(builder).Return()
|
|
state.push(refFuncRet)
|
|
|
|
case wasm.OpcodeRefNull:
|
|
c.loweringState.pc++ // skips the reference type as we treat both of them as i64(0).
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
ret := builder.AllocateInstruction().AsIconst64(0).Insert(builder).Return()
|
|
state.push(ret)
|
|
case wasm.OpcodeRefIsNull:
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
r := state.pop()
|
|
zero := builder.AllocateInstruction().AsIconst64(0).Insert(builder)
|
|
icmp := builder.AllocateInstruction().
|
|
AsIcmp(r, zero.Return(), ssa.IntegerCmpCondEqual).
|
|
Insert(builder).
|
|
Return()
|
|
state.push(icmp)
|
|
case wasm.OpcodeTableSet:
|
|
tableIndex := c.readI32u()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
r := state.pop()
|
|
targetOffsetInTable := state.pop()
|
|
|
|
elementAddr := c.lowerAccessTableWithBoundsCheck(tableIndex, targetOffsetInTable)
|
|
builder.AllocateInstruction().AsStore(ssa.OpcodeStore, r, elementAddr, 0).Insert(builder)
|
|
|
|
case wasm.OpcodeTableGet:
|
|
tableIndex := c.readI32u()
|
|
if state.unreachable {
|
|
break
|
|
}
|
|
targetOffsetInTable := state.pop()
|
|
elementAddr := c.lowerAccessTableWithBoundsCheck(tableIndex, targetOffsetInTable)
|
|
loaded := builder.AllocateInstruction().AsLoad(elementAddr, 0, ssa.TypeI64).Insert(builder).Return()
|
|
state.push(loaded)
|
|
default:
|
|
panic("TODO: unsupported in wazevo yet: " + wasm.InstructionName(op))
|
|
}
|
|
|
|
if wazevoapi.FrontEndLoggingEnabled {
|
|
fmt.Println("--------- Translated " + wasm.InstructionName(op) + " --------")
|
|
fmt.Println("state: " + c.loweringState.String())
|
|
fmt.Println(c.formatBuilder())
|
|
fmt.Println("--------------------------")
|
|
}
|
|
c.loweringState.pc++
|
|
}
|
|
|
|
func (c *Compiler) lowerExtMul(v1, v2 ssa.Value, from, to ssa.VecLane, signed, low bool) ssa.Value {
|
|
// TODO: The sequence `Widen; Widen; VIMul` can be substituted for a single instruction on some ISAs.
|
|
builder := c.ssaBuilder
|
|
|
|
v1lo := builder.AllocateInstruction().AsWiden(v1, from, signed, low).Insert(builder).Return()
|
|
v2lo := builder.AllocateInstruction().AsWiden(v2, from, signed, low).Insert(builder).Return()
|
|
|
|
return builder.AllocateInstruction().AsVImul(v1lo, v2lo, to).Insert(builder).Return()
|
|
}
|
|
|
|
const (
|
|
tableInstanceBaseAddressOffset = 0
|
|
tableInstanceLenOffset = tableInstanceBaseAddressOffset + 8
|
|
)
|
|
|
|
func (c *Compiler) lowerAccessTableWithBoundsCheck(tableIndex uint32, elementOffsetInTable ssa.Value) (elementAddress ssa.Value) {
|
|
builder := c.ssaBuilder
|
|
|
|
// Load the table.
|
|
loadTableInstancePtr := builder.AllocateInstruction()
|
|
loadTableInstancePtr.AsLoad(c.moduleCtxPtrValue, c.offset.TableOffset(int(tableIndex)).U32(), ssa.TypeI64)
|
|
builder.InsertInstruction(loadTableInstancePtr)
|
|
tableInstancePtr := loadTableInstancePtr.Return()
|
|
|
|
// Load the table's length.
|
|
loadTableLen := builder.AllocateInstruction()
|
|
loadTableLen.AsLoad(tableInstancePtr, tableInstanceLenOffset, ssa.TypeI32)
|
|
builder.InsertInstruction(loadTableLen)
|
|
tableLen := loadTableLen.Return()
|
|
|
|
// Compare the length and the target, and trap if out of bounds.
|
|
checkOOB := builder.AllocateInstruction()
|
|
checkOOB.AsIcmp(elementOffsetInTable, tableLen, ssa.IntegerCmpCondUnsignedGreaterThanOrEqual)
|
|
builder.InsertInstruction(checkOOB)
|
|
exitIfOOB := builder.AllocateInstruction()
|
|
exitIfOOB.AsExitIfTrueWithCode(c.execCtxPtrValue, checkOOB.Return(), wazevoapi.ExitCodeTableOutOfBounds)
|
|
builder.InsertInstruction(exitIfOOB)
|
|
|
|
// Get the base address of wasm.TableInstance.References.
|
|
loadTableBaseAddress := builder.AllocateInstruction()
|
|
loadTableBaseAddress.AsLoad(tableInstancePtr, tableInstanceBaseAddressOffset, ssa.TypeI64)
|
|
builder.InsertInstruction(loadTableBaseAddress)
|
|
tableBase := loadTableBaseAddress.Return()
|
|
|
|
// Calculate the address of the target function. First we need to multiply targetOffsetInTable by 8 (pointer size).
|
|
multiplyBy8 := builder.AllocateInstruction()
|
|
three := builder.AllocateInstruction()
|
|
three.AsIconst64(3)
|
|
builder.InsertInstruction(three)
|
|
multiplyBy8.AsIshl(elementOffsetInTable, three.Return())
|
|
builder.InsertInstruction(multiplyBy8)
|
|
targetOffsetInTableMultipliedBy8 := multiplyBy8.Return()
|
|
|
|
// Then add the multiplied value to the base which results in the address of the target function (*wazevo.functionInstance)
|
|
calcElementAddressInTable := builder.AllocateInstruction()
|
|
calcElementAddressInTable.AsIadd(tableBase, targetOffsetInTableMultipliedBy8)
|
|
builder.InsertInstruction(calcElementAddressInTable)
|
|
return calcElementAddressInTable.Return()
|
|
}
|
|
|
|
func (c *Compiler) lowerCallIndirect(typeIndex, tableIndex uint32) {
|
|
builder := c.ssaBuilder
|
|
state := c.state()
|
|
|
|
elementOffsetInTable := state.pop()
|
|
functionInstancePtrAddress := c.lowerAccessTableWithBoundsCheck(tableIndex, elementOffsetInTable)
|
|
loadFunctionInstancePtr := builder.AllocateInstruction()
|
|
loadFunctionInstancePtr.AsLoad(functionInstancePtrAddress, 0, ssa.TypeI64)
|
|
builder.InsertInstruction(loadFunctionInstancePtr)
|
|
functionInstancePtr := loadFunctionInstancePtr.Return()
|
|
|
|
// Check if it is not the null pointer.
|
|
zero := builder.AllocateInstruction()
|
|
zero.AsIconst64(0)
|
|
builder.InsertInstruction(zero)
|
|
checkNull := builder.AllocateInstruction()
|
|
checkNull.AsIcmp(functionInstancePtr, zero.Return(), ssa.IntegerCmpCondEqual)
|
|
builder.InsertInstruction(checkNull)
|
|
exitIfNull := builder.AllocateInstruction()
|
|
exitIfNull.AsExitIfTrueWithCode(c.execCtxPtrValue, checkNull.Return(), wazevoapi.ExitCodeIndirectCallNullPointer)
|
|
builder.InsertInstruction(exitIfNull)
|
|
|
|
// We need to do the type check. First, load the target function instance's typeID.
|
|
loadTypeID := builder.AllocateInstruction()
|
|
loadTypeID.AsLoad(functionInstancePtr, wazevoapi.FunctionInstanceTypeIDOffset, ssa.TypeI32)
|
|
builder.InsertInstruction(loadTypeID)
|
|
actualTypeID := loadTypeID.Return()
|
|
|
|
// Next, we load the expected TypeID:
|
|
loadTypeIDsBegin := builder.AllocateInstruction()
|
|
loadTypeIDsBegin.AsLoad(c.moduleCtxPtrValue, c.offset.TypeIDs1stElement.U32(), ssa.TypeI64)
|
|
builder.InsertInstruction(loadTypeIDsBegin)
|
|
typeIDsBegin := loadTypeIDsBegin.Return()
|
|
|
|
loadExpectedTypeID := builder.AllocateInstruction()
|
|
loadExpectedTypeID.AsLoad(typeIDsBegin, uint32(typeIndex)*4 /* size of wasm.FunctionTypeID */, ssa.TypeI32)
|
|
builder.InsertInstruction(loadExpectedTypeID)
|
|
expectedTypeID := loadExpectedTypeID.Return()
|
|
|
|
// Check if the type ID matches.
|
|
checkTypeID := builder.AllocateInstruction()
|
|
checkTypeID.AsIcmp(actualTypeID, expectedTypeID, ssa.IntegerCmpCondNotEqual)
|
|
builder.InsertInstruction(checkTypeID)
|
|
exitIfNotMatch := builder.AllocateInstruction()
|
|
exitIfNotMatch.AsExitIfTrueWithCode(c.execCtxPtrValue, checkTypeID.Return(), wazevoapi.ExitCodeIndirectCallTypeMismatch)
|
|
builder.InsertInstruction(exitIfNotMatch)
|
|
|
|
// Now ready to call the function. Load the executable and moduleContextOpaquePtr from the function instance.
|
|
loadExecutablePtr := builder.AllocateInstruction()
|
|
loadExecutablePtr.AsLoad(functionInstancePtr, wazevoapi.FunctionInstanceExecutableOffset, ssa.TypeI64)
|
|
builder.InsertInstruction(loadExecutablePtr)
|
|
executablePtr := loadExecutablePtr.Return()
|
|
loadModuleContextOpaquePtr := builder.AllocateInstruction()
|
|
loadModuleContextOpaquePtr.AsLoad(functionInstancePtr, wazevoapi.FunctionInstanceModuleContextOpaquePtrOffset, ssa.TypeI64)
|
|
builder.InsertInstruction(loadModuleContextOpaquePtr)
|
|
moduleContextOpaquePtr := loadModuleContextOpaquePtr.Return()
|
|
|
|
typ := &c.m.TypeSection[typeIndex]
|
|
tail := len(state.values) - len(typ.Params)
|
|
vs := state.values[tail:]
|
|
state.values = state.values[:tail]
|
|
args := c.allocateVarLengthValues(2+len(vs), c.execCtxPtrValue, moduleContextOpaquePtr)
|
|
args = args.Append(builder.VarLengthPool(), vs...)
|
|
|
|
// Before transfer the control to the callee, we have to store the current module's moduleContextPtr
|
|
// into execContext.callerModuleContextPtr in case when the callee is a Go function.
|
|
c.storeCallerModuleContext()
|
|
|
|
call := builder.AllocateInstruction()
|
|
call.AsCallIndirect(executablePtr, c.signatures[typ], args)
|
|
builder.InsertInstruction(call)
|
|
|
|
first, rest := call.Returns()
|
|
if first.Valid() {
|
|
state.push(first)
|
|
}
|
|
for _, v := range rest {
|
|
state.push(v)
|
|
}
|
|
|
|
c.reloadAfterCall()
|
|
}
|
|
|
|
// memOpSetup inserts the bounds check and calculates the address of the memory operation (loads/stores).
|
|
func (c *Compiler) memOpSetup(baseAddr ssa.Value, constOffset, operationSizeInBytes uint64) (address ssa.Value) {
|
|
address = ssa.ValueInvalid
|
|
builder := c.ssaBuilder
|
|
|
|
baseAddrID := baseAddr.ID()
|
|
ceil := constOffset + operationSizeInBytes
|
|
if known := c.getKnownSafeBound(baseAddrID); known.valid() {
|
|
// We reuse the calculated absolute address even if the bound is not known to be safe.
|
|
address = known.absoluteAddr
|
|
if ceil <= known.bound {
|
|
if !address.Valid() {
|
|
// This means that, the bound is known to be safe, but the memory base might have changed.
|
|
// So, we re-calculate the address.
|
|
memBase := c.getMemoryBaseValue(false)
|
|
extBaseAddr := builder.AllocateInstruction().
|
|
AsUExtend(baseAddr, 32, 64).
|
|
Insert(builder).
|
|
Return()
|
|
address = builder.AllocateInstruction().
|
|
AsIadd(memBase, extBaseAddr).Insert(builder).Return()
|
|
known.absoluteAddr = address // Update the absolute address for the subsequent memory access.
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
ceilConst := builder.AllocateInstruction()
|
|
ceilConst.AsIconst64(ceil)
|
|
builder.InsertInstruction(ceilConst)
|
|
|
|
// We calculate the offset in 64-bit space.
|
|
extBaseAddr := builder.AllocateInstruction().
|
|
AsUExtend(baseAddr, 32, 64).
|
|
Insert(builder).
|
|
Return()
|
|
|
|
// Note: memLen is already zero extended to 64-bit space at the load time.
|
|
memLen := c.getMemoryLenValue(false)
|
|
|
|
// baseAddrPlusCeil = baseAddr + ceil
|
|
baseAddrPlusCeil := builder.AllocateInstruction()
|
|
baseAddrPlusCeil.AsIadd(extBaseAddr, ceilConst.Return())
|
|
builder.InsertInstruction(baseAddrPlusCeil)
|
|
|
|
// Check for out of bounds memory access: `memLen >= baseAddrPlusCeil`.
|
|
cmp := builder.AllocateInstruction()
|
|
cmp.AsIcmp(memLen, baseAddrPlusCeil.Return(), ssa.IntegerCmpCondUnsignedLessThan)
|
|
builder.InsertInstruction(cmp)
|
|
exitIfNZ := builder.AllocateInstruction()
|
|
exitIfNZ.AsExitIfTrueWithCode(c.execCtxPtrValue, cmp.Return(), wazevoapi.ExitCodeMemoryOutOfBounds)
|
|
builder.InsertInstruction(exitIfNZ)
|
|
|
|
// Load the value from memBase + extBaseAddr.
|
|
if address == ssa.ValueInvalid { // Reuse the value if the memBase is already calculated at this point.
|
|
memBase := c.getMemoryBaseValue(false)
|
|
address = builder.AllocateInstruction().
|
|
AsIadd(memBase, extBaseAddr).Insert(builder).Return()
|
|
}
|
|
|
|
// Record the bound ceil for this baseAddr is known to be safe for the subsequent memory access in the same block.
|
|
c.recordKnownSafeBound(baseAddrID, ceil, address)
|
|
return
|
|
}
|
|
|
|
// atomicMemOpSetup inserts the bounds check and calculates the address of the memory operation (loads/stores), including
|
|
// the constant offset and performs an alignment check on the final address.
|
|
func (c *Compiler) atomicMemOpSetup(baseAddr ssa.Value, constOffset, operationSizeInBytes uint64) (address ssa.Value) {
|
|
builder := c.ssaBuilder
|
|
|
|
addrWithoutOffset := c.memOpSetup(baseAddr, constOffset, operationSizeInBytes)
|
|
var addr ssa.Value
|
|
if constOffset == 0 {
|
|
addr = addrWithoutOffset
|
|
} else {
|
|
offset := builder.AllocateInstruction().AsIconst64(constOffset).Insert(builder).Return()
|
|
addr = builder.AllocateInstruction().AsIadd(addrWithoutOffset, offset).Insert(builder).Return()
|
|
}
|
|
|
|
c.memAlignmentCheck(addr, operationSizeInBytes)
|
|
|
|
return addr
|
|
}
|
|
|
|
func (c *Compiler) memAlignmentCheck(addr ssa.Value, operationSizeInBytes uint64) {
|
|
if operationSizeInBytes == 1 {
|
|
return // No alignment restrictions when accessing a byte
|
|
}
|
|
var checkBits uint64
|
|
switch operationSizeInBytes {
|
|
case 2:
|
|
checkBits = 0b1
|
|
case 4:
|
|
checkBits = 0b11
|
|
case 8:
|
|
checkBits = 0b111
|
|
}
|
|
|
|
builder := c.ssaBuilder
|
|
|
|
mask := builder.AllocateInstruction().AsIconst64(checkBits).Insert(builder).Return()
|
|
masked := builder.AllocateInstruction().AsBand(addr, mask).Insert(builder).Return()
|
|
zero := builder.AllocateInstruction().AsIconst64(0).Insert(builder).Return()
|
|
cmp := builder.AllocateInstruction().AsIcmp(masked, zero, ssa.IntegerCmpCondNotEqual).Insert(builder).Return()
|
|
builder.AllocateInstruction().AsExitIfTrueWithCode(c.execCtxPtrValue, cmp, wazevoapi.ExitCodeUnalignedAtomic).Insert(builder)
|
|
}
|
|
|
|
func (c *Compiler) callMemmove(dst, src, size ssa.Value) {
|
|
args := c.allocateVarLengthValues(3, dst, src, size)
|
|
if size.Type() != ssa.TypeI64 {
|
|
panic("TODO: memmove size must be i64")
|
|
}
|
|
|
|
builder := c.ssaBuilder
|
|
memmovePtr := builder.AllocateInstruction().
|
|
AsLoad(c.execCtxPtrValue,
|
|
wazevoapi.ExecutionContextOffsetMemmoveAddress.U32(),
|
|
ssa.TypeI64,
|
|
).Insert(builder).Return()
|
|
builder.AllocateInstruction().AsCallGoRuntimeMemmove(memmovePtr, &c.memmoveSig, args).Insert(builder)
|
|
}
|
|
|
|
func (c *Compiler) reloadAfterCall() {
|
|
// Note that when these are not used in the following instructions, they will be optimized out.
|
|
// So in any ways, we define them!
|
|
|
|
// After calling any function, memory buffer might have changed. So we need to re-define the variable.
|
|
// However, if the memory is shared, we don't need to reload the memory base and length as the base will never change.
|
|
if c.needMemory && !c.memoryShared {
|
|
c.reloadMemoryBaseLen()
|
|
}
|
|
|
|
// Also, any mutable Global can change.
|
|
for _, index := range c.mutableGlobalVariablesIndexes {
|
|
_ = c.getWasmGlobalValue(index, true)
|
|
}
|
|
}
|
|
|
|
func (c *Compiler) reloadMemoryBaseLen() {
|
|
_ = c.getMemoryBaseValue(true)
|
|
_ = c.getMemoryLenValue(true)
|
|
|
|
// This function being called means that the memory base might have changed.
|
|
// Therefore, we need to clear the absolute addresses recorded in the known safe bounds
|
|
// because we cache the absolute address of the memory access per each base offset.
|
|
c.resetAbsoluteAddressInSafeBounds()
|
|
}
|
|
|
|
func (c *Compiler) setWasmGlobalValue(index wasm.Index, v ssa.Value) {
|
|
variable := c.globalVariables[index]
|
|
opaqueOffset := c.offset.GlobalInstanceOffset(index)
|
|
|
|
builder := c.ssaBuilder
|
|
if index < c.m.ImportGlobalCount {
|
|
loadGlobalInstPtr := builder.AllocateInstruction()
|
|
loadGlobalInstPtr.AsLoad(c.moduleCtxPtrValue, uint32(opaqueOffset), ssa.TypeI64)
|
|
builder.InsertInstruction(loadGlobalInstPtr)
|
|
|
|
store := builder.AllocateInstruction()
|
|
store.AsStore(ssa.OpcodeStore, v, loadGlobalInstPtr.Return(), uint32(0))
|
|
builder.InsertInstruction(store)
|
|
|
|
} else {
|
|
store := builder.AllocateInstruction()
|
|
store.AsStore(ssa.OpcodeStore, v, c.moduleCtxPtrValue, uint32(opaqueOffset))
|
|
builder.InsertInstruction(store)
|
|
}
|
|
|
|
// The value has changed to `v`, so we record it.
|
|
builder.DefineVariableInCurrentBB(variable, v)
|
|
}
|
|
|
|
func (c *Compiler) getWasmGlobalValue(index wasm.Index, forceLoad bool) ssa.Value {
|
|
variable := c.globalVariables[index]
|
|
typ := c.globalVariablesTypes[index]
|
|
opaqueOffset := c.offset.GlobalInstanceOffset(index)
|
|
|
|
builder := c.ssaBuilder
|
|
if !forceLoad {
|
|
if v := builder.FindValueInLinearPath(variable); v.Valid() {
|
|
return v
|
|
}
|
|
}
|
|
|
|
var load *ssa.Instruction
|
|
if index < c.m.ImportGlobalCount {
|
|
loadGlobalInstPtr := builder.AllocateInstruction()
|
|
loadGlobalInstPtr.AsLoad(c.moduleCtxPtrValue, uint32(opaqueOffset), ssa.TypeI64)
|
|
builder.InsertInstruction(loadGlobalInstPtr)
|
|
load = builder.AllocateInstruction().
|
|
AsLoad(loadGlobalInstPtr.Return(), uint32(0), typ)
|
|
} else {
|
|
load = builder.AllocateInstruction().
|
|
AsLoad(c.moduleCtxPtrValue, uint32(opaqueOffset), typ)
|
|
}
|
|
|
|
v := load.Insert(builder).Return()
|
|
builder.DefineVariableInCurrentBB(variable, v)
|
|
return v
|
|
}
|
|
|
|
const (
|
|
memoryInstanceBufOffset = 0
|
|
memoryInstanceBufSizeOffset = memoryInstanceBufOffset + 8
|
|
)
|
|
|
|
func (c *Compiler) getMemoryBaseValue(forceReload bool) ssa.Value {
|
|
builder := c.ssaBuilder
|
|
variable := c.memoryBaseVariable
|
|
if !forceReload {
|
|
if v := builder.FindValueInLinearPath(variable); v.Valid() {
|
|
return v
|
|
}
|
|
}
|
|
|
|
var ret ssa.Value
|
|
if c.offset.LocalMemoryBegin < 0 {
|
|
loadMemInstPtr := builder.AllocateInstruction()
|
|
loadMemInstPtr.AsLoad(c.moduleCtxPtrValue, c.offset.ImportedMemoryBegin.U32(), ssa.TypeI64)
|
|
builder.InsertInstruction(loadMemInstPtr)
|
|
memInstPtr := loadMemInstPtr.Return()
|
|
|
|
loadBufPtr := builder.AllocateInstruction()
|
|
loadBufPtr.AsLoad(memInstPtr, memoryInstanceBufOffset, ssa.TypeI64)
|
|
builder.InsertInstruction(loadBufPtr)
|
|
ret = loadBufPtr.Return()
|
|
} else {
|
|
load := builder.AllocateInstruction()
|
|
load.AsLoad(c.moduleCtxPtrValue, c.offset.LocalMemoryBase().U32(), ssa.TypeI64)
|
|
builder.InsertInstruction(load)
|
|
ret = load.Return()
|
|
}
|
|
|
|
builder.DefineVariableInCurrentBB(variable, ret)
|
|
return ret
|
|
}
|
|
|
|
func (c *Compiler) getMemoryLenValue(forceReload bool) ssa.Value {
|
|
variable := c.memoryLenVariable
|
|
builder := c.ssaBuilder
|
|
if !forceReload && !c.memoryShared {
|
|
if v := builder.FindValueInLinearPath(variable); v.Valid() {
|
|
return v
|
|
}
|
|
}
|
|
|
|
var ret ssa.Value
|
|
if c.offset.LocalMemoryBegin < 0 {
|
|
loadMemInstPtr := builder.AllocateInstruction()
|
|
loadMemInstPtr.AsLoad(c.moduleCtxPtrValue, c.offset.ImportedMemoryBegin.U32(), ssa.TypeI64)
|
|
builder.InsertInstruction(loadMemInstPtr)
|
|
memInstPtr := loadMemInstPtr.Return()
|
|
|
|
loadBufSizePtr := builder.AllocateInstruction()
|
|
if c.memoryShared {
|
|
sizeOffset := builder.AllocateInstruction().AsIconst64(memoryInstanceBufSizeOffset).Insert(builder).Return()
|
|
addr := builder.AllocateInstruction().AsIadd(memInstPtr, sizeOffset).Insert(builder).Return()
|
|
loadBufSizePtr.AsAtomicLoad(addr, 8, ssa.TypeI64)
|
|
} else {
|
|
loadBufSizePtr.AsLoad(memInstPtr, memoryInstanceBufSizeOffset, ssa.TypeI64)
|
|
}
|
|
builder.InsertInstruction(loadBufSizePtr)
|
|
|
|
ret = loadBufSizePtr.Return()
|
|
} else {
|
|
load := builder.AllocateInstruction()
|
|
if c.memoryShared {
|
|
lenOffset := builder.AllocateInstruction().AsIconst64(c.offset.LocalMemoryLen().U64()).Insert(builder).Return()
|
|
addr := builder.AllocateInstruction().AsIadd(c.moduleCtxPtrValue, lenOffset).Insert(builder).Return()
|
|
load.AsAtomicLoad(addr, 8, ssa.TypeI64)
|
|
} else {
|
|
load.AsExtLoad(ssa.OpcodeUload32, c.moduleCtxPtrValue, c.offset.LocalMemoryLen().U32(), true)
|
|
}
|
|
builder.InsertInstruction(load)
|
|
ret = load.Return()
|
|
}
|
|
|
|
builder.DefineVariableInCurrentBB(variable, ret)
|
|
return ret
|
|
}
|
|
|
|
func (c *Compiler) insertIcmp(cond ssa.IntegerCmpCond) {
|
|
state, builder := c.state(), c.ssaBuilder
|
|
y, x := state.pop(), state.pop()
|
|
cmp := builder.AllocateInstruction()
|
|
cmp.AsIcmp(x, y, cond)
|
|
builder.InsertInstruction(cmp)
|
|
value := cmp.Return()
|
|
state.push(value)
|
|
}
|
|
|
|
func (c *Compiler) insertFcmp(cond ssa.FloatCmpCond) {
|
|
state, builder := c.state(), c.ssaBuilder
|
|
y, x := state.pop(), state.pop()
|
|
cmp := builder.AllocateInstruction()
|
|
cmp.AsFcmp(x, y, cond)
|
|
builder.InsertInstruction(cmp)
|
|
value := cmp.Return()
|
|
state.push(value)
|
|
}
|
|
|
|
// storeCallerModuleContext stores the current module's moduleContextPtr into execContext.callerModuleContextPtr.
|
|
func (c *Compiler) storeCallerModuleContext() {
|
|
builder := c.ssaBuilder
|
|
execCtx := c.execCtxPtrValue
|
|
store := builder.AllocateInstruction()
|
|
store.AsStore(ssa.OpcodeStore,
|
|
c.moduleCtxPtrValue, execCtx, wazevoapi.ExecutionContextOffsetCallerModuleContextPtr.U32())
|
|
builder.InsertInstruction(store)
|
|
}
|
|
|
|
func (c *Compiler) readByte() byte {
|
|
v := c.wasmFunctionBody[c.loweringState.pc+1]
|
|
c.loweringState.pc++
|
|
return v
|
|
}
|
|
|
|
func (c *Compiler) readI32u() uint32 {
|
|
v, n, err := leb128.LoadUint32(c.wasmFunctionBody[c.loweringState.pc+1:])
|
|
if err != nil {
|
|
panic(err) // shouldn't be reached since compilation comes after validation.
|
|
}
|
|
c.loweringState.pc += int(n)
|
|
return v
|
|
}
|
|
|
|
func (c *Compiler) readI32s() int32 {
|
|
v, n, err := leb128.LoadInt32(c.wasmFunctionBody[c.loweringState.pc+1:])
|
|
if err != nil {
|
|
panic(err) // shouldn't be reached since compilation comes after validation.
|
|
}
|
|
c.loweringState.pc += int(n)
|
|
return v
|
|
}
|
|
|
|
func (c *Compiler) readI64s() int64 {
|
|
v, n, err := leb128.LoadInt64(c.wasmFunctionBody[c.loweringState.pc+1:])
|
|
if err != nil {
|
|
panic(err) // shouldn't be reached since compilation comes after validation.
|
|
}
|
|
c.loweringState.pc += int(n)
|
|
return v
|
|
}
|
|
|
|
func (c *Compiler) readF32() float32 {
|
|
v := math.Float32frombits(binary.LittleEndian.Uint32(c.wasmFunctionBody[c.loweringState.pc+1:]))
|
|
c.loweringState.pc += 4
|
|
return v
|
|
}
|
|
|
|
func (c *Compiler) readF64() float64 {
|
|
v := math.Float64frombits(binary.LittleEndian.Uint64(c.wasmFunctionBody[c.loweringState.pc+1:]))
|
|
c.loweringState.pc += 8
|
|
return v
|
|
}
|
|
|
|
// readBlockType reads the block type from the current position of the bytecode reader.
|
|
func (c *Compiler) readBlockType() *wasm.FunctionType {
|
|
state := c.state()
|
|
|
|
c.br.Reset(c.wasmFunctionBody[state.pc+1:])
|
|
bt, num, err := wasm.DecodeBlockType(c.m.TypeSection, c.br, api.CoreFeaturesV2)
|
|
if err != nil {
|
|
panic(err) // shouldn't be reached since compilation comes after validation.
|
|
}
|
|
state.pc += int(num)
|
|
|
|
return bt
|
|
}
|
|
|
|
func (c *Compiler) readMemArg() (align, offset uint32) {
|
|
state := c.state()
|
|
|
|
align, num, err := leb128.LoadUint32(c.wasmFunctionBody[state.pc+1:])
|
|
if err != nil {
|
|
panic(fmt.Errorf("read memory align: %v", err))
|
|
}
|
|
|
|
state.pc += int(num)
|
|
offset, num, err = leb128.LoadUint32(c.wasmFunctionBody[state.pc+1:])
|
|
if err != nil {
|
|
panic(fmt.Errorf("read memory offset: %v", err))
|
|
}
|
|
|
|
state.pc += int(num)
|
|
return align, offset
|
|
}
|
|
|
|
// insertJumpToBlock inserts a jump instruction to the given block in the current block.
|
|
func (c *Compiler) insertJumpToBlock(args ssa.Values, targetBlk ssa.BasicBlock) {
|
|
if targetBlk.ReturnBlock() {
|
|
if c.needListener {
|
|
c.callListenerAfter()
|
|
}
|
|
}
|
|
|
|
builder := c.ssaBuilder
|
|
jmp := builder.AllocateInstruction()
|
|
jmp.AsJump(args, targetBlk)
|
|
builder.InsertInstruction(jmp)
|
|
}
|
|
|
|
func (c *Compiler) insertIntegerExtend(signed bool, from, to byte) {
|
|
state := c.state()
|
|
builder := c.ssaBuilder
|
|
v := state.pop()
|
|
extend := builder.AllocateInstruction()
|
|
if signed {
|
|
extend.AsSExtend(v, from, to)
|
|
} else {
|
|
extend.AsUExtend(v, from, to)
|
|
}
|
|
builder.InsertInstruction(extend)
|
|
value := extend.Return()
|
|
state.push(value)
|
|
}
|
|
|
|
func (c *Compiler) switchTo(originalStackLen int, targetBlk ssa.BasicBlock) {
|
|
if targetBlk.Preds() == 0 {
|
|
c.loweringState.unreachable = true
|
|
}
|
|
|
|
// Now we should adjust the stack and start translating the continuation block.
|
|
c.loweringState.values = c.loweringState.values[:originalStackLen]
|
|
|
|
c.ssaBuilder.SetCurrentBlock(targetBlk)
|
|
|
|
// At this point, blocks params consist only of the Wasm-level parameters,
|
|
// (since it's added only when we are trying to resolve variable *inside* this block).
|
|
for i := 0; i < targetBlk.Params(); i++ {
|
|
value := targetBlk.Param(i)
|
|
c.loweringState.push(value)
|
|
}
|
|
}
|
|
|
|
// results returns the number of results of the current function.
|
|
func (c *Compiler) results() int {
|
|
return len(c.wasmFunctionTyp.Results)
|
|
}
|
|
|
|
func (c *Compiler) lowerBrTable(labels []uint32, index ssa.Value) {
|
|
state := c.state()
|
|
builder := c.ssaBuilder
|
|
|
|
f := state.ctrlPeekAt(int(labels[0]))
|
|
var numArgs int
|
|
if f.isLoop() {
|
|
numArgs = len(f.blockType.Params)
|
|
} else {
|
|
numArgs = len(f.blockType.Results)
|
|
}
|
|
|
|
targets := make([]ssa.BasicBlock, len(labels))
|
|
|
|
// We need trampoline blocks since depending on the target block structure, we might end up inserting moves before jumps,
|
|
// which cannot be done with br_table. Instead, we can do such per-block moves in the trampoline blocks.
|
|
// At the linking phase (very end of the backend), we can remove the unnecessary jumps, and therefore no runtime overhead.
|
|
currentBlk := builder.CurrentBlock()
|
|
for i, l := range labels {
|
|
// Args are always on the top of the stack. Note that we should not share the args slice
|
|
// among the jump instructions since the args are modified during passes (e.g. redundant phi elimination).
|
|
args := c.nPeekDup(numArgs)
|
|
targetBlk, _ := state.brTargetArgNumFor(l)
|
|
trampoline := builder.AllocateBasicBlock()
|
|
builder.SetCurrentBlock(trampoline)
|
|
c.insertJumpToBlock(args, targetBlk)
|
|
targets[i] = trampoline
|
|
}
|
|
builder.SetCurrentBlock(currentBlk)
|
|
|
|
// If the target block has no arguments, we can just jump to the target block.
|
|
brTable := builder.AllocateInstruction()
|
|
brTable.AsBrTable(index, targets)
|
|
builder.InsertInstruction(brTable)
|
|
|
|
for _, trampoline := range targets {
|
|
builder.Seal(trampoline)
|
|
}
|
|
}
|
|
|
|
func (l *loweringState) brTargetArgNumFor(labelIndex uint32) (targetBlk ssa.BasicBlock, argNum int) {
|
|
targetFrame := l.ctrlPeekAt(int(labelIndex))
|
|
if targetFrame.isLoop() {
|
|
targetBlk, argNum = targetFrame.blk, len(targetFrame.blockType.Params)
|
|
} else {
|
|
targetBlk, argNum = targetFrame.followingBlock, len(targetFrame.blockType.Results)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *Compiler) callListenerBefore() {
|
|
c.storeCallerModuleContext()
|
|
|
|
builder := c.ssaBuilder
|
|
beforeListeners1stElement := builder.AllocateInstruction().
|
|
AsLoad(c.moduleCtxPtrValue,
|
|
c.offset.BeforeListenerTrampolines1stElement.U32(),
|
|
ssa.TypeI64,
|
|
).Insert(builder).Return()
|
|
|
|
beforeListenerPtr := builder.AllocateInstruction().
|
|
AsLoad(beforeListeners1stElement, uint32(c.wasmFunctionTypeIndex)*8 /* 8 bytes per index */, ssa.TypeI64).Insert(builder).Return()
|
|
|
|
entry := builder.EntryBlock()
|
|
ps := entry.Params()
|
|
|
|
args := c.allocateVarLengthValues(ps, c.execCtxPtrValue,
|
|
builder.AllocateInstruction().AsIconst32(c.wasmLocalFunctionIndex).Insert(builder).Return())
|
|
for i := 2; i < ps; i++ {
|
|
args = args.Append(builder.VarLengthPool(), entry.Param(i))
|
|
}
|
|
|
|
beforeSig := c.listenerSignatures[c.wasmFunctionTyp][0]
|
|
builder.AllocateInstruction().
|
|
AsCallIndirect(beforeListenerPtr, beforeSig, args).
|
|
Insert(builder)
|
|
}
|
|
|
|
func (c *Compiler) callListenerAfter() {
|
|
c.storeCallerModuleContext()
|
|
|
|
builder := c.ssaBuilder
|
|
afterListeners1stElement := builder.AllocateInstruction().
|
|
AsLoad(c.moduleCtxPtrValue,
|
|
c.offset.AfterListenerTrampolines1stElement.U32(),
|
|
ssa.TypeI64,
|
|
).Insert(builder).Return()
|
|
|
|
afterListenerPtr := builder.AllocateInstruction().
|
|
AsLoad(afterListeners1stElement,
|
|
uint32(c.wasmFunctionTypeIndex)*8 /* 8 bytes per index */, ssa.TypeI64).
|
|
Insert(builder).
|
|
Return()
|
|
|
|
afterSig := c.listenerSignatures[c.wasmFunctionTyp][1]
|
|
args := c.allocateVarLengthValues(
|
|
c.results()+2,
|
|
c.execCtxPtrValue,
|
|
builder.AllocateInstruction().AsIconst32(c.wasmLocalFunctionIndex).Insert(builder).Return(),
|
|
)
|
|
|
|
l := c.state()
|
|
tail := len(l.values)
|
|
args = args.Append(c.ssaBuilder.VarLengthPool(), l.values[tail-c.results():tail]...)
|
|
builder.AllocateInstruction().
|
|
AsCallIndirect(afterListenerPtr, afterSig, args).
|
|
Insert(builder)
|
|
}
|
|
|
|
const (
|
|
elementOrDataInstanceLenOffset = 8
|
|
elementOrDataInstanceSize = 24
|
|
)
|
|
|
|
// dropInstance inserts instructions to drop the element/data instance specified by the given index.
|
|
func (c *Compiler) dropDataOrElementInstance(index uint32, firstItemOffset wazevoapi.Offset) {
|
|
builder := c.ssaBuilder
|
|
instPtr := c.dataOrElementInstanceAddr(index, firstItemOffset)
|
|
|
|
zero := builder.AllocateInstruction().AsIconst64(0).Insert(builder).Return()
|
|
|
|
// Clear the instance.
|
|
builder.AllocateInstruction().AsStore(ssa.OpcodeStore, zero, instPtr, 0).Insert(builder)
|
|
builder.AllocateInstruction().AsStore(ssa.OpcodeStore, zero, instPtr, elementOrDataInstanceLenOffset).Insert(builder)
|
|
builder.AllocateInstruction().AsStore(ssa.OpcodeStore, zero, instPtr, elementOrDataInstanceLenOffset+8).Insert(builder)
|
|
}
|
|
|
|
func (c *Compiler) dataOrElementInstanceAddr(index uint32, firstItemOffset wazevoapi.Offset) ssa.Value {
|
|
builder := c.ssaBuilder
|
|
|
|
_1stItemPtr := builder.
|
|
AllocateInstruction().
|
|
AsLoad(c.moduleCtxPtrValue, firstItemOffset.U32(), ssa.TypeI64).
|
|
Insert(builder).Return()
|
|
|
|
// Each data/element instance is a slice, so we need to multiply index by 16 to get the offset of the target instance.
|
|
index = index * elementOrDataInstanceSize
|
|
indexExt := builder.AllocateInstruction().AsIconst64(uint64(index)).Insert(builder).Return()
|
|
// Then, add the offset to the address of the instance.
|
|
instPtr := builder.AllocateInstruction().AsIadd(_1stItemPtr, indexExt).Insert(builder).Return()
|
|
return instPtr
|
|
}
|
|
|
|
func (c *Compiler) boundsCheckInDataOrElementInstance(instPtr, offsetInInstance, copySize ssa.Value, exitCode wazevoapi.ExitCode) {
|
|
builder := c.ssaBuilder
|
|
dataInstLen := builder.AllocateInstruction().
|
|
AsLoad(instPtr, elementOrDataInstanceLenOffset, ssa.TypeI64).
|
|
Insert(builder).Return()
|
|
ceil := builder.AllocateInstruction().AsIadd(offsetInInstance, copySize).Insert(builder).Return()
|
|
cmp := builder.AllocateInstruction().
|
|
AsIcmp(dataInstLen, ceil, ssa.IntegerCmpCondUnsignedLessThan).
|
|
Insert(builder).
|
|
Return()
|
|
builder.AllocateInstruction().
|
|
AsExitIfTrueWithCode(c.execCtxPtrValue, cmp, exitCode).
|
|
Insert(builder)
|
|
}
|
|
|
|
func (c *Compiler) boundsCheckInTable(tableIndex uint32, offset, size ssa.Value) (tableInstancePtr ssa.Value) {
|
|
builder := c.ssaBuilder
|
|
dstCeil := builder.AllocateInstruction().AsIadd(offset, size).Insert(builder).Return()
|
|
|
|
// Load the table.
|
|
tableInstancePtr = builder.AllocateInstruction().
|
|
AsLoad(c.moduleCtxPtrValue, c.offset.TableOffset(int(tableIndex)).U32(), ssa.TypeI64).
|
|
Insert(builder).Return()
|
|
|
|
// Load the table's length.
|
|
tableLen := builder.AllocateInstruction().
|
|
AsLoad(tableInstancePtr, tableInstanceLenOffset, ssa.TypeI32).Insert(builder).Return()
|
|
tableLenExt := builder.AllocateInstruction().AsUExtend(tableLen, 32, 64).Insert(builder).Return()
|
|
|
|
// Compare the length and the target, and trap if out of bounds.
|
|
checkOOB := builder.AllocateInstruction()
|
|
checkOOB.AsIcmp(tableLenExt, dstCeil, ssa.IntegerCmpCondUnsignedLessThan)
|
|
builder.InsertInstruction(checkOOB)
|
|
exitIfOOB := builder.AllocateInstruction()
|
|
exitIfOOB.AsExitIfTrueWithCode(c.execCtxPtrValue, checkOOB.Return(), wazevoapi.ExitCodeTableOutOfBounds)
|
|
builder.InsertInstruction(exitIfOOB)
|
|
return
|
|
}
|
|
|
|
func (c *Compiler) loadTableBaseAddr(tableInstancePtr ssa.Value) ssa.Value {
|
|
builder := c.ssaBuilder
|
|
loadTableBaseAddress := builder.
|
|
AllocateInstruction().
|
|
AsLoad(tableInstancePtr, tableInstanceBaseAddressOffset, ssa.TypeI64).
|
|
Insert(builder)
|
|
return loadTableBaseAddress.Return()
|
|
}
|
|
|
|
func (c *Compiler) boundsCheckInMemory(memLen, offset, size ssa.Value) {
|
|
builder := c.ssaBuilder
|
|
ceil := builder.AllocateInstruction().AsIadd(offset, size).Insert(builder).Return()
|
|
cmp := builder.AllocateInstruction().
|
|
AsIcmp(memLen, ceil, ssa.IntegerCmpCondUnsignedLessThan).
|
|
Insert(builder).
|
|
Return()
|
|
builder.AllocateInstruction().
|
|
AsExitIfTrueWithCode(c.execCtxPtrValue, cmp, wazevoapi.ExitCodeMemoryOutOfBounds).
|
|
Insert(builder)
|
|
}
|