From af6faf8d50a4020fddc306ea93d3ba4d00038926 Mon Sep 17 00:00:00 2001 From: aigerimu <89766357+aigerimu@users.noreply.github.com> Date: Tue, 2 Dec 2025 17:21:06 +0700 Subject: [PATCH 1/4] feat: tolk asm-functions --- languages/tolk/features/asm-functions.mdx | 235 ++++++++++++++++++ .../tolk/features/compiler-optimizations.mdx | 7 + languages/tolk/features/standard-library.mdx | 8 + languages/tolk/syntax/mutability.mdx | 7 + 4 files changed, 257 insertions(+) create mode 100644 languages/tolk/features/asm-functions.mdx create mode 100644 languages/tolk/features/compiler-optimizations.mdx create mode 100644 languages/tolk/features/standard-library.mdx create mode 100644 languages/tolk/syntax/mutability.mdx diff --git a/languages/tolk/features/asm-functions.mdx b/languages/tolk/features/asm-functions.mdx new file mode 100644 index 000000000..2a7d460b6 --- /dev/null +++ b/languages/tolk/features/asm-functions.mdx @@ -0,0 +1,235 @@ +--- +title: "Assembler functions" +--- + +import { Aside } from '/snippets/aside.jsx'; + +Functions in Tolk may be defined using assembler code. +It's a low-level feature that requires deep understanding of stack layout, [Fift](/languages/fift/overview), and [TVM](/tvm/overview). + +## Standard functions are actually `asm` wrappers + +Many functions from [stdlib](/languages/tolk/features/standard-library) 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: + +```tolk +@pure +fun cell.hash(self): uint256 + asm "HASHCU" +``` + +The type system guarantees that when this method is invoked, a TVM `CELL` will be the topmost element (`self`). + +## Custom functions are declared in the same way + +```tolk +@pure +fun incThenNegate(v: int): int + asm "INC" "NEGATE" +``` + +A call `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). + + + +## Multi-line asm + +To embed a multi-line command, use triple quotes: + +```tolk +fun hashStateInit(code: cell, data: cell): uint256 asm """ + DUP2 + HASHCU + ... + ONE HASHEXT_SHA256 +""" +``` + +It is treated as a single string and inserted as-is into Fift output. +In particular, it may contain `//` 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 function `abs2` that calculates `abs()` for two values at once: `abs2(-5, -10)` = `(5, 10)`. +Stack layout (the right is the top) is written in comments. + +```tolk +fun abs2(v1: int, v2: int): (int, int) + asm // v1 v2 + "ABS" // v1 v2_abs + "SWAP" // v2_abs v1 + "ABS" // v2_abs v1_abs + "SWAP" // v1_abs v2_abs +``` + +## 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: + +```tolk +fun calculateStorageFee(workchain: int8, seconds: int, bits: int, cells: int): coins + asm(cells bits seconds workchain) "GETSTORAGEFEE" +``` + +Similarly for return values. If multiple slots are returned, and they must be reordered to match typing, +use `asm(-> ...)` syntax: + +```tolk +fun asmLoadCoins(s: slice): (slice, int) + asm(-> 1 0) "LDVARUINT16" +``` + +Both the input and output sides may be combined: `asm(... -> ...)`. +Reordering is mostly used with `mutate` variables. + +## `mutate` and `self` in assembler functions + +The `mutate` keyword (see [mutability](/languages/tolk/syntax/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: + +```tolk +// transformed to: "returns (int, void)" +fun increment(mutate x: int): void { + x += 1; + // a hidden "return x" is inserted +} + +fun demo() { + // transformed to: (newX, _) = increment(x); x = newX + increment(mutate x); +} +``` + +How to implement `increment()` via asm? + +```tolk +fun increment(mutate x: int): void + asm "INC" +``` + +The function still returns `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: + +```tolk +// "TPUSH" pops (tuple) and pushes (newTuple); +// so, newSelf = newTuple, and return `void` (syn. "unit") +fun tuple.push(mutate self, value: X): void + asm "TPUSH" + +// "LDU" pops (slice) and pushes (int, newSlice); +// with `asm(-> 1 0)`, we make it (newSlice, int); +// so, newSelf = newSlice, and return `int` +fun slice.loadMessageFlags(mutate self): int + asm(-> 1 0) "4 LDU" +``` + +To return `self` for chaining, just specify a return type: + +```tolk +// "STU" pops (int, builder) and pushes (newBuilder); +// with `asm(op self)`, we put arguments to correct order; +// so, newSelf = newBuilder, and return `void`; +// but to make it chainable, `self` instead of `void` +fun builder.storeMessageOp(mutate self, op: int): self + asm(op self) "32 STU" +``` + +## `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. + +```tolk +struct MyCell { + private c: cell +} + +@pure +fun MyCell.hash(self): uint256 + asm "HASHCU" +``` + +Similarly, a structure may be used instead of tensors for returns. +This is widely practiced in `map` methods over TVM dictionaries: + +```tolk +struct MapLookupResult { + private readonly rawSlice: slice? + isFound: bool +} + +@pure +fun map.get(self, key: K): MapLookupResult + builtin +// it produces `DICTGET` and similar, which push +// (slice -1) or (null 0) — the shape of MapLookupResult +``` + +## 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. + +```tolk +fun tuple.push(mutate self, value: T): void + asm "TPUSH" +``` + +A reasonable question: how should `t.push(somePoint)` work? +The stack would be misaligned, because `Point { x, y }` is not a single slot. +The answer: this would not compile. + +```ansi +dev.tolk:6:5: error: can not call `tuple.push` with T=Point, because it occupies 2 stack slots in TVM, not 1 + + // in function `main` + 6 | t.push(somePoint); + | ^^^^^^ +``` + +Only regular and built-in generics may be instantiated with variadic type arguments, `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: + +```tolk +fun builder.storeFlags(mutate self, flags: int): self { + return self.storeUint(32, flags); +} +``` + +The function above is better than "manually optimized" as `32 STU`. Because: + +- it is inlined automatically +- for constant `flags`, it's merged with subsequent stores into `STSLICECONST` + +See [compiler optimizations](/languages/tolk/features/compiler-optimizations). diff --git a/languages/tolk/features/compiler-optimizations.mdx b/languages/tolk/features/compiler-optimizations.mdx new file mode 100644 index 000000000..48c8c0234 --- /dev/null +++ b/languages/tolk/features/compiler-optimizations.mdx @@ -0,0 +1,7 @@ +--- +title: "Compiler optimizations" +--- + +import { Stub } from '/snippets/stub.jsx'; + + diff --git a/languages/tolk/features/standard-library.mdx b/languages/tolk/features/standard-library.mdx new file mode 100644 index 000000000..e381d9aff --- /dev/null +++ b/languages/tolk/features/standard-library.mdx @@ -0,0 +1,8 @@ +--- +title: "Standard library of Tolk" +sidebarTitle: "Standard library" +--- + +import { Stub } from '/snippets/stub.jsx'; + + diff --git a/languages/tolk/syntax/mutability.mdx b/languages/tolk/syntax/mutability.mdx new file mode 100644 index 000000000..1c3dd823b --- /dev/null +++ b/languages/tolk/syntax/mutability.mdx @@ -0,0 +1,7 @@ +--- +title: "Mutability" +--- + +import { Stub } from '/snippets/stub.jsx'; + + From 1a0037b7eee09d07aa6aafa78f221158cd2d80a9 Mon Sep 17 00:00:00 2001 From: aigerimu <89766357+aigerimu@users.noreply.github.com> Date: Tue, 2 Dec 2025 17:42:05 +0700 Subject: [PATCH 2/4] format & navigation --- languages/tolk/features/compiler-optimizations.mdx | 4 +++- languages/tolk/features/standard-library.mdx | 4 +++- languages/tolk/syntax/mutability.mdx | 4 +++- scripts/check-navigation.mjs | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/languages/tolk/features/compiler-optimizations.mdx b/languages/tolk/features/compiler-optimizations.mdx index 48c8c0234..3b121a271 100644 --- a/languages/tolk/features/compiler-optimizations.mdx +++ b/languages/tolk/features/compiler-optimizations.mdx @@ -4,4 +4,6 @@ title: "Compiler optimizations" import { Stub } from '/snippets/stub.jsx'; - + diff --git a/languages/tolk/features/standard-library.mdx b/languages/tolk/features/standard-library.mdx index e381d9aff..d9c02e534 100644 --- a/languages/tolk/features/standard-library.mdx +++ b/languages/tolk/features/standard-library.mdx @@ -5,4 +5,6 @@ sidebarTitle: "Standard library" import { Stub } from '/snippets/stub.jsx'; - + diff --git a/languages/tolk/syntax/mutability.mdx b/languages/tolk/syntax/mutability.mdx index 1c3dd823b..80c93343b 100644 --- a/languages/tolk/syntax/mutability.mdx +++ b/languages/tolk/syntax/mutability.mdx @@ -4,4 +4,6 @@ title: "Mutability" import { Stub } from '/snippets/stub.jsx'; - + diff --git a/scripts/check-navigation.mjs b/scripts/check-navigation.mjs index 3d46eddde..5cbb40d55 100644 --- a/scripts/check-navigation.mjs +++ b/scripts/check-navigation.mjs @@ -67,7 +67,7 @@ const checkUnique = (config) => { * @return {CheckResult} */ const checkExist = (config) => { - const uniqPages = [...getNavLinksSet(config)]; + const uniqPages = [...getNavLinksSet(config)].filter(x=>x.includes("/tolk/")); const missingPages = uniqPages.filter((it) => { const rel = it.replace(/^\/+/, '').replace(/#.*$/, '') + '.mdx'; return !(existsSync(rel) && statSync(rel).isFile()); From 38e1a868cd61d55a188a18f1f7e8d9373cf2fb3a Mon Sep 17 00:00:00 2001 From: aigerimu <89766357+aigerimu@users.noreply.github.com> Date: Tue, 2 Dec 2025 17:43:35 +0700 Subject: [PATCH 3/4] format & navigation --- scripts/check-navigation.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check-navigation.mjs b/scripts/check-navigation.mjs index 5cbb40d55..8daa8b8db 100644 --- a/scripts/check-navigation.mjs +++ b/scripts/check-navigation.mjs @@ -67,7 +67,7 @@ const checkUnique = (config) => { * @return {CheckResult} */ const checkExist = (config) => { - const uniqPages = [...getNavLinksSet(config)].filter(x=>x.includes("/tolk/")); + const uniqPages = [...getNavLinksSet(config)].filter(x=>!x.includes("/tolk/")); const missingPages = uniqPages.filter((it) => { const rel = it.replace(/^\/+/, '').replace(/#.*$/, '') + '.mdx'; return !(existsSync(rel) && statSync(rel).isFile()); From 71dd2a258e670b08197b365a52ce1a9e6dbc0751 Mon Sep 17 00:00:00 2001 From: aigerimu <89766357+aigerimu@users.noreply.github.com> Date: Wed, 3 Dec 2025 16:42:25 +0700 Subject: [PATCH 4/4] format & navigation --- languages/tolk/features/asm-functions.mdx | 71 ++++++------------- .../tolk/features/compiler-optimizations.mdx | 2 - languages/tolk/features/standard-library.mdx | 2 - languages/tolk/syntax/mutability.mdx | 2 - scripts/check-navigation.mjs | 2 +- 5 files changed, 22 insertions(+), 57 deletions(-) diff --git a/languages/tolk/features/asm-functions.mdx b/languages/tolk/features/asm-functions.mdx index 2a7d460b6..93d12f90e 100644 --- a/languages/tolk/features/asm-functions.mdx +++ b/languages/tolk/features/asm-functions.mdx @@ -2,18 +2,13 @@ title: "Assembler functions" --- -import { Aside } from '/snippets/aside.jsx'; +Functions in Tolk may be defined using assembler code. It's a low-level feature that requires understanding of stack layout, [Fift](/languages/fift/overview), and [TVM](/tvm/overview). -Functions in Tolk may be defined using assembler code. -It's a low-level feature that requires deep understanding of stack layout, [Fift](/languages/fift/overview), and [TVM](/tvm/overview). +## Standard functions are `asm` wrappers -## Standard functions are actually `asm` wrappers +Many functions from [standard library](/languages/tolk/features/standard-library) are translated to Fift assembler directly. -Many functions from [stdlib](/languages/tolk/features/standard-library) 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: +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 2256 − 1. Therefore, the method `cell.hash` is defined this way: ```tolk @pure @@ -35,11 +30,7 @@ A call `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 return type for `asm` functions is mandatory. For regular functions, it's auto-inferred from `return` statements. ## Multi-line asm @@ -54,18 +45,15 @@ fun hashStateInit(code: cell, data: cell): uint256 asm """ """ ``` -It is treated as a single string and inserted as-is into Fift output. -In particular, it may contain `//` comments inside (valid comments for Fift). +It is treated as a single string and inserted as-is into Fift output. In particular, it may contain `//` 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. +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 function `abs2` that calculates `abs()` for two values at once: `abs2(-5, -10)` = `(5, 10)`. -Stack layout (the right is the top) is written in comments. +For example, write a function `abs2` that calculates `abs()` for two values at once: `abs2(-5, -10)` = `(5, 10)`. Stack layout, the right is the top, is written in comments. ```tolk fun abs2(v1: int, v2: int): (int, int) @@ -78,34 +66,27 @@ fun abs2(v1: int, v2: int): (int, int) ## 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: +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: ```tolk fun calculateStorageFee(workchain: int8, seconds: int, bits: int, cells: int): coins asm(cells bits seconds workchain) "GETSTORAGEFEE" ``` -Similarly for return values. If multiple slots are returned, and they must be reordered to match typing, -use `asm(-> ...)` syntax: +Similarly, for return values. If multiple slots are returned, and they must be reordered to match typing, use `asm(-> ...)` syntax: ```tolk fun asmLoadCoins(s: slice): (slice, int) asm(-> 1 0) "LDVARUINT16" ``` -Both the input and output sides may be combined: `asm(... -> ...)`. -Reordering is mostly used with `mutate` variables. +Both the input and output sides may be combined: `asm(... -> ...)`. Reordering is mostly used with `mutate` variables. ## `mutate` and `self` in assembler functions -The `mutate` keyword (see [mutability](/languages/tolk/syntax/mutability)) works -by implicitly returning new values via the stack — both for regular and `asm` functions. +The `mutate` keyword (see [mutability](/languages/tolk/syntax/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: +For better understanding, let's look at regular functions first. The compiler does all transformations automatically: ```tolk // transformed to: "returns (int, void)" @@ -127,11 +108,9 @@ fun increment(mutate x: int): void asm "INC" ``` -The function still returns `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. +The function still returns `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: +Similarly, it works for `mutate self`. An `asm` function should place `newSelf` onto the stack before the actual result: ```tolk // "TPUSH" pops (tuple) and pushes (newTuple); @@ -159,8 +138,7 @@ fun builder.storeMessageOp(mutate self, op: int): self ## `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. +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. ```tolk struct MyCell { @@ -172,8 +150,7 @@ fun MyCell.hash(self): uint256 asm "HASHCU" ``` -Similarly, a structure may be used instead of tensors for returns. -This is widely practiced in `map` methods over TVM dictionaries: +Similarly, a structure may be used instead of tensors for returns. This is widely practiced in `map` methods over TVM dictionaries: ```tolk struct MapLookupResult { @@ -190,17 +167,14 @@ fun map.get(self, key: K): MapLookupResult ## 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. +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. ```tolk fun tuple.push(mutate self, value: T): void asm "TPUSH" ``` -A reasonable question: how should `t.push(somePoint)` work? -The stack would be misaligned, because `Point { x, y }` is not a single slot. -The answer: this would not compile. +A reasonable question: how should `t.push(somePoint)` work? The stack would be misaligned, because `Point { x, y }` is not a single slot. The answer: this would not compile. ```ansi dev.tolk:6:5: error: can not call `tuple.push` with T=Point, because it occupies 2 stack slots in TVM, not 1 @@ -214,12 +188,9 @@ Only regular and built-in generics may be instantiated with variadic type argume ## 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. +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: +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: ```tolk fun builder.storeFlags(mutate self, flags: int): self { diff --git a/languages/tolk/features/compiler-optimizations.mdx b/languages/tolk/features/compiler-optimizations.mdx index 3b121a271..dd84e237b 100644 --- a/languages/tolk/features/compiler-optimizations.mdx +++ b/languages/tolk/features/compiler-optimizations.mdx @@ -2,8 +2,6 @@ title: "Compiler optimizations" --- -import { Stub } from '/snippets/stub.jsx'; - diff --git a/languages/tolk/features/standard-library.mdx b/languages/tolk/features/standard-library.mdx index d9c02e534..d812718ef 100644 --- a/languages/tolk/features/standard-library.mdx +++ b/languages/tolk/features/standard-library.mdx @@ -3,8 +3,6 @@ title: "Standard library of Tolk" sidebarTitle: "Standard library" --- -import { Stub } from '/snippets/stub.jsx'; - diff --git a/languages/tolk/syntax/mutability.mdx b/languages/tolk/syntax/mutability.mdx index 80c93343b..fd16da7c6 100644 --- a/languages/tolk/syntax/mutability.mdx +++ b/languages/tolk/syntax/mutability.mdx @@ -2,8 +2,6 @@ title: "Mutability" --- -import { Stub } from '/snippets/stub.jsx'; - diff --git a/scripts/check-navigation.mjs b/scripts/check-navigation.mjs index 8daa8b8db..3d46eddde 100644 --- a/scripts/check-navigation.mjs +++ b/scripts/check-navigation.mjs @@ -67,7 +67,7 @@ const checkUnique = (config) => { * @return {CheckResult} */ const checkExist = (config) => { - const uniqPages = [...getNavLinksSet(config)].filter(x=>!x.includes("/tolk/")); + const uniqPages = [...getNavLinksSet(config)]; const missingPages = uniqPages.filter((it) => { const rel = it.replace(/^\/+/, '').replace(/#.*$/, '') + '.mdx'; return !(existsSync(rel) && statSync(rel).isFile());