Control Flow

EVM doesn’t have instructions for the custom control flows, however zink implements them with JUMPI and JUMP, which includes:

  • if
  • block
  • loop
  • else
  • select
  • br
  • br_if
  • br_table

If-Else

The beginning of an if construct with an implicit then block, plus and else block.

The basic logic is, if non-zero, enter the if block, otherwise jump to the else block or the end of the if condition.

(if (result i32)
  (local.get 0)
  (then (i32.const 7))
  (else (i32.const 8)))

The expected result is

InputResult
17
08

so in the compiled bytecode, the code snippet above will be

PUSH1 0x00       // Load the params at 0x00
calldataload

iszero           // if is zero, jump to 0x0c, the else block.
PUSH1 0x0c
jumpi

push1 0x07       // if is non-zero, enters the if block.
                 // push 0x07 on stack.

PUSH1 0x0f       // jump to the end of the else block.
jump

jumpdest         // destination of the else block, push 0x08
push1 0x08       // on stack.


jumpdest         // the end of the else block.


PUSH1 0x00       // pack the result and return...
mstore
PUSH1 0x20
PUSH1 0x00
return

Select

The select (0x1B) instruction comes from WebAssembly, it selects one of its first two operands based on whether its third operand is zero or not.

Simple rust conditions in rust will be compiled to select.

pub extern "C" fn if_else(x: u64, y: u64) -> u64 {
    if x > y {
        x
    } else {
        y
    }
}

As we can see in the example above, we simply returns the bigger number from the 2 parameters, the logic in the two blocks of if-else is explicit direct, that will be compiled to select.

(module
  (type (;0;) (func (param i64 i64) (result i64)))
  (func $if_else (type 0) (param i64 i64) (result i64)
    local.get 0
    local.get 1
    local.get 0
    local.get 1
    i64.gt_u
    select))

Since EVM doesn’t have instruction like select, we need to provide it ourselves in our implementation like an external function, if zero pop the value on the top of the stack.

const SELECT: [OpCode; 6] = [
    OpCode::JUMPDEST,
    OpCode::POP,
    OpCode::PUSH1,
    OpCode::Data(0x06),
    OpCode::ADD,
    OpCode::JUMP,
];

In the compiled code, we need to combine this function select with jumpi in EVM.

PUSH1 0x00      // Load the parameters.
calldataload

PUSH1 0x20
calldataload

PUSH1 0x00
calldataload

PUSH1 0x20
calldataload

lt               // Compiled to `lt` because of the result of this
                 // instruction is oppsited between EVM and WASM.

pc
swap2            // shift
swap1
PUSH1 0x1c
jumpi            // `jumpi` for the if condition.
JUMPDEST

PUSH1 0x00       // Returns the value.
mstore
PUSH1 0x20
PUSH1 0x00
return

JUMPDEST         // Function select starts here.
pop
PUSH1 0x06
add
jump