pub enum Opcode {
Show 88 variants
Nop,
Add {
dest: Reg,
src1: Reg,
src2: Reg,
},
Sub {
dest: Reg,
src1: Reg,
src2: Reg,
},
Mul {
dest: Reg,
src1: Reg,
src2: Reg,
},
DivS {
dest: Reg,
src1: Reg,
src2: Reg,
},
DivU {
dest: Reg,
src1: Reg,
src2: Reg,
},
RemS {
dest: Reg,
src1: Reg,
src2: Reg,
},
RemU {
dest: Reg,
src1: Reg,
src2: Reg,
},
And {
dest: Reg,
src1: Reg,
src2: Reg,
},
Or {
dest: Reg,
src1: Reg,
src2: Reg,
},
Xor {
dest: Reg,
src1: Reg,
src2: Reg,
},
Shl {
dest: Reg,
src1: Reg,
src2: Reg,
},
ShrS {
dest: Reg,
src1: Reg,
src2: Reg,
},
ShrU {
dest: Reg,
src1: Reg,
src2: Reg,
},
Rotl {
dest: Reg,
src1: Reg,
src2: Reg,
},
Rotr {
dest: Reg,
src1: Reg,
src2: Reg,
},
Clz {
dest: Reg,
src: Reg,
},
Ctz {
dest: Reg,
src: Reg,
},
Popcnt {
dest: Reg,
src: Reg,
},
Extend8S {
dest: Reg,
src: Reg,
},
Extend16S {
dest: Reg,
src: Reg,
},
Eqz {
dest: Reg,
src: Reg,
},
Eq {
dest: Reg,
src1: Reg,
src2: Reg,
},
Ne {
dest: Reg,
src1: Reg,
src2: Reg,
},
LtS {
dest: Reg,
src1: Reg,
src2: Reg,
},
LtU {
dest: Reg,
src1: Reg,
src2: Reg,
},
LeS {
dest: Reg,
src1: Reg,
src2: Reg,
},
LeU {
dest: Reg,
src1: Reg,
src2: Reg,
},
GtS {
dest: Reg,
src1: Reg,
src2: Reg,
},
GtU {
dest: Reg,
src1: Reg,
src2: Reg,
},
GeS {
dest: Reg,
src1: Reg,
src2: Reg,
},
GeU {
dest: Reg,
src1: Reg,
src2: Reg,
},
Load {
dest: Reg,
addr: u32,
},
Store {
src: Reg,
addr: u32,
},
I64Load {
dest_lo: Reg,
dest_hi: Reg,
addr: u32,
},
MemLoad {
dest: Reg,
addr: Reg,
offset: u32,
},
MemStore {
src: Reg,
addr: Reg,
offset: u32,
},
MemLoadSubword {
dest: Reg,
addr: Reg,
offset: u32,
width: u32,
signed: bool,
},
MemStoreSubword {
src: Reg,
addr: Reg,
offset: u32,
width: u32,
},
GlobalGet {
dest: Reg,
idx: u32,
},
GlobalSet {
src: Reg,
idx: u32,
},
MemorySize {
dest: Reg,
},
MemoryGrow {
dest: Reg,
delta: Reg,
},
Branch {
target: BlockId,
},
CondBranch {
cond: Reg,
target: BlockId,
},
Call {
dest: Reg,
func_idx: u32,
},
Return {
value: Option<Reg>,
},
Label {
id: BlockId,
},
Copy {
dest: Reg,
src: Reg,
},
TeeStore {
dest: Reg,
src: Reg,
addr: u32,
},
Select {
dest: Reg,
val_true: Reg,
val_false: Reg,
cond: Reg,
},
Const {
dest: Reg,
value: i32,
},
I64Add {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64Sub {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64And {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64Or {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64Xor {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64Const {
dest_lo: Reg,
dest_hi: Reg,
value: i64,
},
I64Eq {
dest: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64Ne {
dest: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64LtS {
dest: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64GtS {
dest: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64LeS {
dest: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64GeS {
dest: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64LtU {
dest: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64GtU {
dest: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64LeU {
dest: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64GeU {
dest: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64Eqz {
dest: Reg,
src_lo: Reg,
src_hi: Reg,
},
I64Clz {
dest: Reg,
src_lo: Reg,
src_hi: Reg,
},
I64Ctz {
dest: Reg,
src_lo: Reg,
src_hi: Reg,
},
I64Popcnt {
dest: Reg,
src_lo: Reg,
src_hi: Reg,
},
I64Extend8S {
dest_lo: Reg,
dest_hi: Reg,
src_lo: Reg,
},
I64Extend16S {
dest_lo: Reg,
dest_hi: Reg,
src_lo: Reg,
},
I64Extend32S {
dest_lo: Reg,
dest_hi: Reg,
src_lo: Reg,
},
I64ExtendI32U {
dest_lo: Reg,
dest_hi: Reg,
src: Reg,
},
I64ExtendI32S {
dest_lo: Reg,
dest_hi: Reg,
src: Reg,
},
I32WrapI64 {
dest: Reg,
src_lo: Reg,
},
I64Mul {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64DivS {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64DivU {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64RemS {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64RemU {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64Shl {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64ShrS {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64ShrU {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64Rotl {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64Rotr {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
}Variants§
Nop
Add
Sub
Mul
DivS
DivU
RemS
RemU
And
Or
Xor
Shl
ShrS
ShrU
Rotl
Rotr
Clz
Ctz
Popcnt
Extend8S
Extend16S
Eqz
Eq
Ne
LtS
LtU
LeS
LeU
GtS
GtU
GeS
GeU
Load
Store
I64Load
Load 64-bit value from local into register pair (for i64 on 32-bit ARM)
MemLoad
Load 32-bit value from linear memory: dest = mem[addr + offset]
MemStore
Store 32-bit value to linear memory: mem[addr + offset] = src
MemLoadSubword
Sub-word load: i32.load8_s/u, i32.load16_s/u and the i64 equivalents
after extension. width is 1 or 2 bytes; signed is true for the
_s variants. The dest is a single i32 register (sub-word loads on
ARM expand to a single LDRB/LDRH ± an SXTB/SXTH).
MemStoreSubword
Sub-word store: i32.store8 / i32.store16 / i64.store8/16/32.
width is 1, 2, or 4. For 4-byte store of an i64’s low word we
just take the lo register.
GlobalGet
global.get — load a global into a fresh vreg. The actual ARM
lowering is LDR rd, [R9, #offset] (R9 is the globals base, by
convention). The vreg MUST be allocated to a non-AAPCS-param
register at lowering time — otherwise the LDR would clobber a
live caller arg.
GlobalSet
global.set — store a vreg to a global slot. Emits STR src, [R9, #offset]. No vreg is written.
MemorySize
memory.size — returns the current memory size in pages as an
i32. By convention R10 holds the memory-size word; we emit
MOV dest, R10. dest must be allocated to a non-param scratch.
MemoryGrow
memory.grow — pops the grow-by amount, pushes the previous
page count (or -1 on failure). Embedded targets generally have
fixed memory, so this is a stub returning -1 in dest.
Branch
CondBranch
Call
Direct function call. Lowered to BL func_<func_idx> (or to an import
dispatch for func_idx < num_imports). The AAPCS return value lands in
R0, so ir_to_arm binds dest to R0 after the BL.
Prior to this opcode, WasmOp::Call fell through to Opcode::Nop in
wasm_to_ir, leaving the call result’s vreg unmapped. Any downstream
consumer (e.g., i32.add of two call results, as in the recursive
fib example) then triggered the PR #101 defensive panic — or, with
the silent R0 fallback, silently miscompiled. See the issue-#93 family
of bugs.
NOTE: this opcode does NOT model the call’s clobber of R0..R3 — that’s a separate (deeper) issue around correct AAPCS lowering of calls in the optimized path. This fix only closes the unmapped-vreg gap so the compiler stops panicking on lawful WASM modules.
Return
Label
Label marker for branch targets (loop start, block end)
Copy
Copy a value (for local.tee semantics: store value and keep it on stack)
TeeStore
TeeStore: Store to local AND keep value on stack (local.tee) Combines Store + Copy: stores src to local addr, dest gets same value
Select
Const
I64Add
I64Sub
I64And
I64Or
I64Xor
I64Const
I64Eq
I64Ne
I64LtS
I64GtS
I64LeS
I64GeS
I64LtU
I64GtU
I64LeU
I64GeU
I64Eqz
I64Clz
I64Ctz
I64Popcnt
I64Extend8S
I64Extend16S
I64Extend32S
I64ExtendI32U
i32 -> i64 zero-extension. dest_lo aliases src (same vreg by IR
convention — see wasm_to_ir); dest_hi is freshly allocated and
holds the constant 0. Lowering MUST move src’s ARM reg into a
non-AAPCS-param register before treating it as the i64 lo half — the
downstream I64Shl/ShrU/ShrS emitters use rm_lo/rm_hi as scratch
and clobber them, so we must not place a live param register there.
This was the proximate cause of issue #93 (memset infinite loop on
silicon) before this opcode existed and the bridge silently fell
through to Opcode::Nop + R0-fallback in get_arm_reg.
I64ExtendI32S
i32 -> i64 sign-extension. Same constraints as I64ExtendI32U; the
dest_hi is filled by an arithmetic shift right by 31 of the src.
I32WrapI64
i64 -> i32 wrap (truncate to low 32 bits). dest aliases the i64’s
src_lo vreg (same value); the high half is simply discarded.