Constant folding
Tolk compiler evaluates constant variables and conditions at compile-time:- If an
ifcondition is statically known to befalse, only theelsebody remains. - If an
assertis statically proven to fail, the correspondingthrowremains.
IF construct — both the condition evaluation and its bodies — when the branch is provably unreachable.
During compile-time evaluation, arithmetic operations are emulated as they would be at runtime. The compiler also tracks flags such as “this value is even or non-positive”, which allows it to remove unreachable code.
This applies not only to plain variables but also to struct fields, tensor items, and across inlining. It runs after the high-level syntax tree is transformed to a low-level intermediate representation.
Merging constant builder.storeInt
When building cells manually, there is no need to group the constantstoreUint into a single number.
builder.storeInt are merged automatically:
IF-ELSE and STU, but during compile-time analysis, these instructions resolve to constants because all types are known at compile time. The resulting code flattens into PUSHINT and STSLICECONST.
Auto-inline functions
Tolk inlines functions at the compiler level:How does auto-inline work?
- Simple, small functions are always inlined.
- Functions called only once are always inlined.
- if
weight < THRESHOLD, the function is always inlined. - if
usages == 1, the function is always inlined. - otherwise, an empirical formula determines inlining.
return in the middle.
Utility methods can be created without affecting gas consumption, they are zero-cost.
How to control inlining manually?
@inlineforces inlining for large functions.@noinlineprevents inlining.@inline_refpreserves an inline reference, suitable for rarely executed paths.
What cannot be auto-inlined?
A function is NOT inlined, even if marked with@inline, if:
- contains
returnin the middle; multiple return points are unsupported; - participates in a recursive call chain, e.g.,
f -> g -> f; - is used as a non-call; e.g., as a reference
val callback = f.
return in the middle:
Peephole and stack optimizations
After the code is analyzed and transformed into IR, the compiler repeatedly replaces some assembler combinations with equivalent, cheaper ones. Examples include:- stack permutations:
DUP + DUP->2DUP,SWAP + OVER->TUCK; N LDU + NIP->N PLDU;SWAP + N STU->N STUR,SWAP + STSLICE->STSLICER;SWAP + EQUAL->EQUALand other symmetric likeMUL,OR;0 EQINT + N THROWIF->N THROWIFNOTand vice versa;N EQINT + NOT->N NEQINTand otherxxx + NOT.
- replace a ternary operator to
CONDSEL; - evaluate arguments of
asmfunctions in the desired stack order; - evaluate struct fields of a shuffled object literal to fit stack order.
Lazy loading
Thelazy keyword loads only the required fields from a cell or slice:
Manual optimizations
The compiler does substantial work automatically, but the gas usage can be reduced. To do it, change the evaluation order to minimize stack manipulations. The compiler does not reorder code blocks unless they’re constant expressions or pure calls. Example:(v1 v2 v3). Since v1 is used first, the stack must be rearranged with SWAP, ROT, XCPU, etc. Reordering assignments or usages—for example, moving assert(v3) upper—will pop the topmost element. Automatic reordering is unsafe and prohibited, but in some cases business logic might be still valid.
Another option is using bitwise & and | instead of logical && and ||. Logical operators are short-circuit: the right operand is evaluated only if required. They are implemented using runtime conditional branches. In some cases, evaluating both operands directly uses fewer runtime instructions than a dynamic IF.
The last option is using low-level Fift code for certain independent tasks that cannot be expressed imperatively. This includes using TVM instructions such as NULLROTRIFNOT or IFBITJMP, and overriding the top-level Fift dictionary for method_id routing. These techniques are applicable only in a limited set of scenarios, primarily for specialized exercises rather than for real-world use.
Avoid micro-optimizations. Small manual attempts to reduce gas typically yield minimal gains and can reduce code readability. Use Tolk as intended.
Fift assembler
The Tolk compiler outputs the Fift assembler. Fift generates the bitcode. Projects built on Blueprint usetolk-js, which invokes Tolk and then Fift.
- For command-line users, the Fift assembler is the compiler output.
-
For Blueprint users, it is an intermediate result that can be accessed in the build directory.
To view Fift assembler in Blueprint, run
npx blueprint buildin the project. After compilation, thebuild/directory is created, containing a folderbuild/ContractName/with a.fiffile.