diff --git a/languages/tolk/features/compiler-optimizations.mdx b/languages/tolk/features/compiler-optimizations.mdx
index 5aa31be36..f8d476589 100644
--- a/languages/tolk/features/compiler-optimizations.mdx
+++ b/languages/tolk/features/compiler-optimizations.mdx
@@ -4,15 +4,7 @@ title: "Compiler optimizations"
import { Aside } from '/snippets/aside.jsx';
-Tolk compiler is smart enough to generate optimal bytecode from a clear, idiomatic code.
-The ideal target is "zero overhead": extracting variables and simple methods should not increase gas consumption.
-
-
+The Tolk compiler generates bitcode from clear, idiomatic code. Extracting variables or simple methods should not increase gas consumption.
## Constant folding
@@ -26,15 +18,16 @@ fun calcSecondsInAYear() {
}
```
-All these computations are done statically, resulting in
+All these computations are done statically, resulting in:
```fift
31536000 PUSHINT
```
It works for conditions as well.
-For example, when `if`'s condition is guaranteed to be false, only `else` body is left.
-If an `assert` is proven statically, only the corresponding `throw` remains.
+
+- If an `if` condition is statically known to be `false`, only the `else` body remains.
+- If an `assert` is statically proven to fail, the corresponding `throw` remains.
```tolk
fun demo(s: slice) {
@@ -46,23 +39,22 @@ fun demo(s: slice) {
}
```
-The compiler drops `IF` at all (both body and condition evaluation), because it can never be reached.
+The compiler removes the entire `IF` construct — both the condition evaluation and its bodies — when the branch is provably unreachable.
-While calculating compile-time values, all mathematical operators are emulated as they would have run at runtime.
-Additional flags like "this value is even / non-positive" are also tracked, leading to more aggressive code elimination.
-It works not only for plain variables, but also for struct fields, tensor items, across inlining, etc.
-(because it happens after transforming a high-level syntax tree to low-level intermediate representation).
+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.
-## Merge constant builder.storeInt
+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.
-When building cells manually, there is no need to group constant `storeUint` into a single number.
+## Merging constant builder.storeInt
+
+When building cells manually, there is no need to group the constant `storeUint` into a single number.
```tolk
// no need for manual grouping anymore
b.storeUint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1);
```
-Successive `builder.storeInt` are merged automatically:
+`builder.storeInt` are merged automatically:
```tolk
b.storeUint(0, 1) // prefix
@@ -72,13 +64,13 @@ b.storeUint(0, 1) // prefix
.storeUint(0, 2) // addr_none
```
-is translated to just
+Compiles to:
```fift
b{011000} STSLICECONST
```
-It works together with constant folding — even with variables and conditions, when they turn out to be constant:
+It works together with constant folding — with variables and conditions — when they turn out to be constant:
```tolk
fun demo() {
@@ -94,14 +86,14 @@ fun demo() {
}
```
-is translated to just
+Compiles to:
```fift
NEWC
x{01a} STSLICECONST
```
-It works even for structures — including their fields:
+The same applies to structures and their fields:
```tolk
struct Point {
@@ -115,7 +107,7 @@ fun demo() {
}
```
-becomes
+Compiles to:
```fift
NEWC
@@ -123,12 +115,7 @@ x{0000000a00000014} STSLICECONST
ENDC
```
-_(in the future, Tolk will be able to emit a constant cell here)_
-
-That's the reason why [createMessage](/languages/tolk/features/message-sending) for unions is so lightweight.
-The compiler really generates all IF-ELSE and STU, but in a later analysis,
-they become constant (since types are compile-time known),
-and everything flattens into simple PUSHINT / STSLICECONST.
+For unions, [createMessage](/languages/tolk/features/message-sending) is lightweight. The compiler generates all `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
@@ -153,7 +140,7 @@ fun main() {
}
```
-is compiled just to
+Compiles to:
```fift
main PROC:<{
@@ -161,39 +148,38 @@ main PROC:<{
}>
```
-The compiler **automatically determines which functions to inline** and also gives manual control.
+The compiler automatically determines which functions to inline and also provides manual control.
### How does auto-inline work?
-- simple, small functions are always inlined
-- functions called only once are always inlined
+- Simple, small functions are always inlined.
+- Functions called only once are always inlined.
-For every function, the compiler calculates some "weight" and the usages count.
+For every function, the compiler calculates a "weight" and the number of usages:
-- if `weight < THRESHOLD`, the function is always inlined
-- if `usages == 1`, the function is always inlined
-- otherwise, an empirical formula determines inlining
+- if `weight < THRESHOLD`, the function is always inlined.
+- if `usages == 1`, the function is always inlined.
+- otherwise, an empirical formula determines inlining.
-Inlining is efficient in terms of stack manipulations.
-It works with arguments of any stack width, any functions and methods, except recursive or having "return" in the middle.
+Inlining works with stack operations and supports arguments of any width. It applies to functions and methods, except recursive functions or functions with `return` in the middle.
-As a conclusion, create utility methods without worrying about gas consumption, they are absolutely zero-cost.
+Utility methods can be created without affecting gas consumption, they are zero-cost.
### How to control inlining manually?
-- `@inline` forces inlining even for large functions
-- `@noinline` prevents from being inlined
-- `@inline_ref` preserves an inline reference, suitable for rarely executed paths
+- `@inline` forces inlining for large functions.
+- `@noinline` prevents inlining.
+- `@inline_ref` preserves an inline reference, suitable for rarely executed paths.
-### What can NOT be auto-inlined?
+### What cannot be auto-inlined?
A function is NOT inlined, even if marked with `@inline`, if:
-- contains `return` in the middle; multiple return points are unsupported
-- participates in a recursive call chain `f -> g -> f`
-- is used as a non-call; e.g., as a reference `val callback = f`
+- contains `return` in 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`.
-For example, this function cannot be inlined due to `return` in the middle:
+Example of function that cannot be inlined due to `return` in the middle:
```tolk
fun executeForPositive(userId: int) {
@@ -204,30 +190,28 @@ fun executeForPositive(userId: int) {
}
```
-The advice is to check pre-conditions out of the function and keep body linear.
+Check preconditions out of the function and keep body linear.
## Peephole and stack optimizations
-After the code has been analyzed and transformed to IR, the compiler repeatedly replaces some assembler combinations to equal ones, but cheaper.
-Some examples are:
+After the code is analyzed and transformed into [IR](https://en.wikipedia.org/wiki/Intermediate_representation), the compiler repeatedly replaces some assembler combinations with equivalent, cheaper ones. Examples include:
-- stack permutations: DUP + DUP => 2DUP, SWAP + OVER => TUCK, etc.
-- N LDU + NIP => N PLDU
-- SWAP + N STU => N STUR, SWAP + STSLICE => STSLICER, etc.
-- SWAP + EQUAL => EQUAL and other symmetric like MUL, OR, etc.
-- 0 EQINT + N THROWIF => N THROWIFNOT and vice versa
-- N EQINT + NOT => N NEQINT and other xxx + NOT
-- ...
+- stack permutations: `DUP + DUP` -> `2DUP`, `SWAP + OVER` -> `TUCK`;
+- `N LDU + NIP` -> `N PLDU`;
+- `SWAP + N STU` -> `N STUR`, `SWAP + STSLICE` -> `STSLICER`;
+- `SWAP + EQUAL` -> `EQUAL` and other symmetric like `MUL`, `OR`;
+- `0 EQINT + N THROWIF` -> `N THROWIFNOT` and vice versa;
+- `N EQINT + NOT` -> `N NEQINT` and other `xxx + NOT`.
-Some others are done semantically in advance when it's safe:
+Other transformations occur semantically in advance when safe:
-- replace a ternary operator to `CONDSEL`
-- evaluate arguments of `asm` functions in a desired stack order
-- evaluate struct fields of a shuffled object literal to fit stack order
+- replace a ternary operator to `CONDSEL`;
+- evaluate arguments of `asm` functions in the desired stack order;
+- evaluate struct fields of a shuffled object literal to fit stack order.
## Lazy loading
-The magic `lazy` keyword loads only required fields from a cell/slice:
+The [`lazy` keyword](/languages/tolk/features/lazy-loading) loads only the required fields from a cell or slice:
```tolk
struct Storage {
@@ -236,22 +220,20 @@ struct Storage {
get fun publicKey() {
val st = lazy Storage.load();
- // <-- fields before are skipped, publicKey preloaded
+ // fields before are skipped; publicKey preloaded
return st.publicKey
}
```
-The compiler tracks exactly which fields are accessed, and unpacks only those fields, skipping the rest.
+The compiler tracks exactly which fields are accessed and unpacks only those fields, skipping the rest.
-Read [lazy loading](/languages/tolk/features/lazy-loading).
+## Manual optimizations
-## Suggestions for manual optimizations
+The compiler does substantial work automatically, but the gas usage can be reduced.
-Although the compiler performs substantial work in the background, there are still cases when a developer can gain a few gas units.
+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.
-The primary aspect is **changing evaluation order** to target fewer stack manipulations.
-The compiler does not reorder blocks of code unless they are constant expressions or pure calls.
-But a developer knows the context better. Generally, it looks like this:
+Example:
```tolk
fun demo() {
@@ -267,37 +249,24 @@ fun demo() {
}
```
-After the first block, the stack is `(v1 v2 v3)`.
-But v1 is used at first, so the stack must be shuffled with `SWAP` / `ROT` / `XCPU` / etc.
-If to rearrange assignments or usages — say, move `assert(v3)` upper — it will naturally pop the topmost element.
-Of course, automatic reordering is unsafe and prohibited, but in exact cases business logic might be still valid.
-
-Another option is **using bitwise `& |` instead of logical `&& ||`**.
-Logical operators are short-circuit: the right operand is evaluated only if required to.
-It's implemented via conditional branches at runtime.
-But in some cases, evaluating both operands is less expensive than a dynamic `IF`.
-
-The last possibility is **using low-level Fift code** for certain independent tasks that cannot be expressed imperatively.
-Usage of exotic TVM instructions like `NULLROTRIFNOT` / `IFBITJMP` / etc.
-Overriding how top-level Fift dictionary works for routing method\_id. And similar.
-Old residents call it "deep fifting".
-Anyway, it's applicable only to a very limited set of goals, mostly as exercises, not as real-world usage.
-
-
+After the first block, the stack is `(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`.
-## How to explore Fift assembler
+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.
+
+
-Tolk compiler outputs Fift assembler. The bytecode (bag of cells) is generated by Fift, actually.
-Projects built on blueprint rely on `tolk-js` under the hood, which invokes Tolk and then Fift.
+## Fift assembler
-As a result:
+The Tolk compiler outputs the Fift assembler. Fift generates the bitcode. Projects built on [Blueprint](/contract-dev/blueprint/overview) use `tolk-js`, which invokes Tolk and then Fift.
-- for command-line users, fift assembler is the compiler's output
-- for blueprint users, it's an intermediate result, but can easily be found
+- 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 `npm build` or `blueprint build` in a project.
-After successful compilation, a directory `build/` is created, and a folder `build/ContractName/`
-contains a `.fif` file.
+ To view Fift assembler in Blueprint, run `npx blueprint build` in the project.
+ After compilation, the `build/` directory is created, containing a folder `build/ContractName/` with a `.fif` file.