Locals
There are two usages of locals in zink.
- The parameters of functions are loaded as locals.
- local defined variables in functions.
fn params | local variables | |
---|---|---|
stack | locals[..n] | locals[n..] |
Function Parameters
Let’s go through the add-two
example:
(module
(func (param (;0;) i32) (param (;1;) i32) (result i32)
(local.get 0)
(local.get 1)
(i32.add)
)
)
(param (;0;) i32)
and (param (;1;) i32)
will be pushed to the function
locals with index 0
and index 1
with their type i32
recorded.
zinkc
gets the defined local at index 0
when reaching (local.get 0)
,
at index 1
for (local.get 1)
, for example, for (local.get 0)
, it will
be translated to:
push1 0x00
calldataload
for (local.get 1)
, that would be
push1 0x20
calldataload
You may have problem why we PUSH1 0x20
while getting local at index 1
, the
answer is that this offset is calculated by the size of the parameters.
The CALLDATALOAD
operator has stack input i
and output data[i]
while data[i]
is a 32-byte
value starting from the given offset of the calldata, so the minimal
size of our types will be 32-byte
, therefore, we align all types sizes to 32-byte
in zinkc
.
WARN: We don’t care about the originals offset of the parameters in WASM bcz we will serialize them into our locals and calculate the offsets on our own when need anyway.
type | size | aligned size |
---|---|---|
i32 | [u8;4] | [u8;32] |
i64 | [u8;8] | [u8;32] |
It is a waste of resources but sadly this is also how EVM works ))
Local Variables
The locals variables will take the stack items right after the function parameters, for example:
(func (result i32)
(local i32)
i32.const 42
local.set 0
local.get 0)
In the program above, we set and get 42
to local variable 0
and returns it.
While compiling this function, zinkc
will push local variable 0
on the stack
with an initializing value 0
first, getting with dup
and setting with swap
and drop
.
PUSH1 0x00 // initializing value 0 for local 0
//
//
PUSH1 0x28 // push value 42 on stack, the current stack is [0, 42]
//
//
SWAP1 // swap the value on the top of the stack to local 0 and
DROP // drop the previous value of it for cleaning stack, `swapn`
// is calculated by `zinkc`. current stack: [42]
//
//
DUP1 // dup the value of local 0 and push it on the top of the
// stack, `dupn` is calculated by `zinkc`.
//
//
DROP // clean the stack before returning results.
As we can see, the usages of get
and set
is verbose with swap
and dup
,
it is for adapting any usages but not necessary for all of them, however, we
will introduce optimizer for this in v0.4.0
!