Standard functions are actually asm wrappers
Many functions from stdlib are translated to Fift assembler directly.
For example, TVM has a HASHCU instruction: “calculate hash of a cell”.
It pops a cell from the stack and pushes an integer in the range 0 to 2^256-1.
Therefore, the method cell.hash is defined this way:
self).
Custom functions are declared in the same way
incThenNegate(10) will be translated into those commands.
A good practice is to specify @pure if the body does not modify TVM state or throw exceptions.
The return type for asm functions is mandatory (for regular functions, it’s auto-inferred from return statements).
The list of assembler commands can be found here: TVM instructions.
Multi-line asm
To embed a multi-line command, use triple quotes:// comments inside (valid comments for Fift).
Stack order for multiple slots
When calling a function, arguments are pushed in a declared order. The last parameter becomes the topmost stack element. If an instruction results in several slots, the resulting type should be a tensor or a struct. For example, write a functionabs2 that calculates abs() for two values at once: abs2(-5, -10) = (5, 10).
Stack layout (the right is the top) is written in comments.
Rearranging arguments on the stack
Sometimes a function accepts parameters in an order different from what a TVM instruction expects. For example,GETSTORAGEFEE expects the order “cells bits seconds workchain”.
But for more clear API, workchain should be passed first.
Stack positions can be reordered via the asm(...) syntax:
asm(-> ...) syntax:
asm(... -> ...).
Reordering is mostly used with mutate variables.
mutate and self in assembler functions
The mutate keyword (see mutability) works
by implicitly returning new values via the stack — both for regular and asm functions.
For better understanding, let’s look at regular functions first.
The compiler does all transformations automatically:
increment() via asm?
void (from the type system’s perspective it does not return a value),
but INC leaves a number on the stack — that’s a hidden “return x” from a manual variant.
Similarly, it works for mutate self.
An asm function should place newSelf onto the stack before the actual result:
self for chaining, just specify a return type:
asm is compatible with structures
Methods for structures may also be declared as assembler ones knowing the layout: fields are placed sequentially.
For instance, a struct with one field is identical to this field.
map<K, V> methods over TVM dictionaries:
Generics in asm should be single-slot
Take tuple.push as an example. The TPUSH instruction pops (tuple, someVal) and pushes (newTuple).
It should work with any T: int, int8, slice, etc.
t.push(somePoint) work?
The stack would be misaligned, because Point { x, y } is not a single slot.
The answer: this would not compile.
asm cannot.
Do not use asm for micro-optimizations
Introduce assembler functions only for rarely-used TVM instructions that are not covered by stdlib.
For example, when manually parsing merkle proofs or calculating extended hashes.
However, attempting to micro-optimize with asm instead of writing straightforward code is not desired.
The compiler is smart enough to generate optimal bytecode from consistent logic.
For instance, it automatically inlines simple functions, so create one-liner methods without any worries about gas:
32 STU. Because:
- it is inlined automatically
- for constant
flags, it’s merged with subsequent stores intoSTSLICECONST