Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(docs): asm functions #1061

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- `asm` functions now support full range of Fift-asm syntax: PR [#855](https://github.com/tact-lang/tact/pull/855)
- `asm` functions now support full range of Fift-asm syntax: PR [#855](https://github.com/tact-lang/tact/pull/855), PR [#1061](https://github.com/tact-lang/tact/pull/1061)

- Fix `npm` installations of Tact compiler or any of the packages depending on it by hiding unnecessary post-install runs of `husky`: PR [#870](https://github.com/tact-lang/tact/pull/870)

Expand Down
41 changes: 1 addition & 40 deletions docs/cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@
],
"dictionaries": ["fift-words", "tvm-instructions"],
"words": [
"ADDRAND",
"BBITS",
"BREFS",
"Brujin",
"bocchi",
"Cheatsheet",
"Cheatsheets",
"Comptime",
Expand All @@ -36,65 +34,28 @@
"Komarov",
"Korshakov",
"Laika",
"MYADDR",
"Masterchain",
"Merkle",
"NEWC",
"Neovim",
"Nonterminal",
"Novus",
"Offchain",
"Offchain",
"PLDDICT",
"PLDIX",
"PLDREF",
"PLDSLICEX",
"PLDUX",
"POSIX",
"PUSHINT",
"PUSHREF",
"PUSHSLICE",
"Parens",
"RANDU",
"RAWRESERVE",
"RAWRESERVE",
"REWRITESTDADDR",
"REWRITEVARADDR",
"SBITS",
"SDBEGINSQ",
"SDEMPTY",
"SDSKIPFIRST",
"SEMPTY",
"SENDMSG",
"SENDRAWMSG",
"SETCONTARGS",
"SETINDEXVARQ",
"SETNUMARGS",
"SREFS",
"SREMPTY",
"STBR",
"STDICT",
"STIX",
"STON.fi",
"STOPTREF",
"STREF",
"STSLICER",
"STUX",
"STVARUINT",
"Satoshi",
"Seamus",
"Sedov",
"Stateinit",
"Sánchez",
"THROWIFNOT",
"TIMELOCK",
"Tarjan",
"Timeouted",
"Toncoin",
"Toncoins",
"Topup",
"Trunov",
"UBITSIZE",
"Uninit",
"alnum",
"assgn",
Expand Down
207 changes: 190 additions & 17 deletions docs/src/content/docs/book/functions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ Functions in Tact could be defined in different ways:

* Global static function
* Extension functions
* Mutable functions
* Mutation functions
* Native functions
* [Assembly functions](#asm)
* [Internal functions](/book/contracts#internal-functions)
* Receiver functions
* Getter functions

Expand Down Expand Up @@ -85,9 +87,9 @@ extends fun customPow(self: Int, c: Int): Int {
}
```

## Mutable functions
## Mutation functions

Mutable functions are performing mutation of a value replacing it with an execution result. To perform mutation, the function must change the `self` value.
Mutation functions are performing mutation of a value replacing it with an execution result. To perform mutation, the function must change the `self` value.

```tact
extends mutates fun customPow(self: Int, c: Int) {
Expand All @@ -104,7 +106,7 @@ extends mutates fun customPow(self: Int, c: Int) {
Native functions are direct bindings of FunC functions:

> **Note**
> Native functions could be also mutable and extension ones.
> Native functions could also be mutation and extension ones.

```tact
@name(store_uint)
Expand All @@ -114,6 +116,175 @@ native storeUint(s: Builder, value: Int, bits: Int): Builder;
extends mutates native loadInt(self: Slice, l: Int): Int;
```

## Assembly functions, `asm` {#asm}

<Badge text="Available since Tact 1.5" variant="tip" size="medium"/><p/>

:::caution

These are very advanced functions that require experience and vigilance in both definitions and usage. The logical errors in them are extremely hard to spot, the error messages are abysmal, and type checking isn't currently provided by Tact.

That said, if you know what you're doing, they can offer you the smallest possible gas usage, the best performance and the most control over [TVM][tvm] execution. Remember — with great power comes great responsibility.

:::

Assembly functions (or `asm{:tact}` functions for short) are module-level functions that allow writing [Fift and TVM assembly](https://docs.ton.org/v3/documentation/smart-contracts/fift/fift-and-tvm-assembly) directly in Tact. Unlike all other functions, their bodies consist only of [Fift words][fift] and [TVM instructions][tvm-instructions], and don't use any [Tact statements](/book/statements).
novusnota marked this conversation as resolved.
Show resolved Hide resolved

```tact
// all assembly functions must start with "asm" keyword
// ↓
asm fun answer(): Int { 42 INT }
// ------
// Notice, that the body contains
// only Fift words or TVM instructions
```

### Caveats {#asm-caveats}

The difference between Fift words and TVM instructions is that words are processed and computed at [compile-time](/ref/core-comptime), while instructions are converted to opcodes and embedded into the contract as part of its code. They are also visually different: TVM instructions are almost all uppercase.

```tact
/// Defines ++ word to be used elsewhere
novusnota marked this conversation as resolved.
Show resolved Hide resolved
asm fun definePlusPlus() {
{ // Fift word, opens the definition of a new word
INC // TVM instruction, increment
} // Fift word, closes the definition of a new word
: // Fift word, assigns the resulting definition
++ // A name for the new Fift word
}

/// Increments an Int and requires the `definePlusPlus()` to be called
/// at least once prior to calling this function!
asm fun inc(x: Int): Int { ++ }
// --
// Fift word, that is substituted with
// INC instruction at compile-time,
// which is then executed at run-time
// with the "x" as a parameter

/// Computes the Answer to Ultimate Question of Life, Universe and Everything
fun answer(): Int {
// Since Fift allows silent shadowing,
// defining ++ multiple times over is possible,
// but beware — you can totally break anything
// that way if not careful enough with the names
definePlusPlus();
definePlusPlus();
definePlusPlus();

// Now, let's use the ++
return inc(41); // 42
}
```

:::caution

Prefer to use _only_ [TVM instructions][tvm-instructions] only and _never_ define new Fift words or use existing ones, since it's very easy to confuse compile-time and run-time execution and forget which stack is used where and when — Fift's or TVM's.

:::

It is not necessary to enclose TVM instructions in double quotes. On the contrary, they are interpreted by Fift as strings, which is probably _not_ what you want:

```tact
// Puts the string "MYCODE" on Fift's stack at compile-time,
// where it gets discarded even before the compute phase starts
asm fun wrongMyCode() { "MYCODE" }

// Invokes the TVM instruction MYCODE during the compute phase,
// which returns the contract code as a Cell
asm fun myCode(): Cell { MYCODE }
```

The syntax for parameters and return values is the same as for other function kinds, but there is one caveat — argument values are pushed to the stack before the function body is executed, and return values are what's left on the stack afterward.

Since the bodies of `asm{:tact}` functions do not contain Tact statements, any direct references to parameters in function bodies will be recognized as Fift words, which can easily lead to very obscure error messages.

```tact
/// Simply returns back the value of `x`
asm fun identity(x: Int): Int { }

/// COMPILATION ERROR!
/// You could've thought that you're providing the value of `boc` to the function
/// and receiving it back, but actually there's a builtin Fift word `boc` which is
/// being used instead.
/// The `boc` would not be recognized as a parameter
/// even if such word didn't exist in Fift!
asm fun bocchiThe(boc: Cell): Cell { boc }

/// Loads a signed `l`-bit integer from Slice `s`,
novusnota marked this conversation as resolved.
Show resolved Hide resolved
/// and returns it with the remainder of `s`
asm fun sliceLoadInt(s: Slice, l: Int): IntSlice { LDIX }
// ↑ ↑
// | Placed on the stack last
novusnota marked this conversation as resolved.
Show resolved Hide resolved
// Placed on the stack first

// Used to map onto values placed by LDIX on the stack
struct IntSlice { a: Int; b: Slice }
```

### Arrangements {#asm-arrangements}

Sometimes it's useful to change the order of arguments pushed to the stack or the order of return values. You can do that with `asm{:tact}` arrangements in the following manner:

```tact
// Changing the order of arguments to match the STDICT signature
asm(c self) extends fun asmStoreDict(self: Builder, c: Cell?): Builder { STDICT }
novusnota marked this conversation as resolved.
Show resolved Hide resolved

// Changing the order of return values of LDVARUINT16,
// capturing only the 2nd one as the return value of the whole function
asm(-> 1 0) extends mutates fun asmLoadCoins(self: Slice): Int { LDVARUINT16 }
novusnota marked this conversation as resolved.
Show resolved Hide resolved

// Changing the order of argument and return values
asm(self len -> 1 0) extends fun asmLoadInt(self: Slice, l: Int): SliceInt { LDIX }
novusnota marked this conversation as resolved.
Show resolved Hide resolved

// Used to map onto values placed by LDIX on the stack in reverse order
struct SliceInt { a: Slice; b: Int }

fun showcase() {
let b = beginCell()
.storeCoins(42)
.storeInt(27, 10)
.asmStoreDict(emptyMap());

let s = b.asSlice();
let coins = s.asmLoadCoins(); // 42
let sliceInt = s.asmLoadInt(10); // Slice remainder and 27
}
```

### Attributes {#asm-attributes}

The following attributes can be specified:

* `inline{:tact}` — does nothing, since assembly functions cannot be inlined yet.
* [`extends{:tact}`](#extension-function) — makes it the [extension function](#extension-function).
novusnota marked this conversation as resolved.
Show resolved Hide resolved
* [`mutates{:tact}`](#mutation-functions) (along with [`extends{:tact}`](#extension-function)) — makes it the [extension mutation function](#mutation-functions).
novusnota marked this conversation as resolved.
Show resolved Hide resolved

Those attributes _cannot_ be specified:

* `abstract{:tact}` — assembly functions must have a body defined.
* `virtual{:tact}` and `override{:tact}` — assembly functions cannot be defined within a contract or a trait.
* [`get{:tact}`](#getter-functions) — assembly functions cannot be [getters](#getter-functions).

```tact
/// `Builder.storeCoins()` extension function
asm extends fun storeCoins(self: Builder, value: Int): Builder {
STVARUINT16
}

/// `Slice.skipBits()` extension mutation function
asm extends mutates fun skipBits(self: Slice, l: Int) {
SDSKIPFIRST
}
```

:::note[Useful links:]

[Fift language overview in TON Docs][fift]\
[List of TVM instructions in TON Docs][tvm-instructions]

:::

## Receiver functions

Receiver functions are special functions that are responsible for receiving messages in contracts and could be defined only within a contract or trait.
Expand Down Expand Up @@ -144,32 +315,34 @@ contract Treasure {

<Badge text="Available since Tact 1.6" variant="tip" size="medium"/><p/>

As other functions in TVM contracts, getters have their *unique* associated function selectors which are some integers ids (called *method IDs*).
Some of those integers are reserved for internal purposes, e.g. -4, -3, -2, -1, 0 are reserved IDs and
regular functions (internal to a contract and not callable from outside) are usually numbered by subsequent (small) integers starting from 1.
By default, getters have associated method IDs that are derived from their names using the [CRC16](https://en.wikipedia.org/wiki/Cyclic_redundancy_check) algorithm as follows:
`crc16(<function_name>) & 0xffff) | 0x10000`.
Sometimes this can get you the same method ID for getters with different names.
If this happens, you can either rename some of the contract's getters or
specify the getter's method ID manually as a compile-time expression like so:
Like other functions in TON contracts, getters have their _unique_ associated function selectors, which are $19$-bit signed integer identifiers commonly called _method IDs_.

Method IDs of getters are derived from their names using the [CRC16](https://en.wikipedia.org/wiki/Cyclic_redundancy_check) algorithm as follows: `(crc16(<function_name>) & 0xffff) | 0x10000`. In addition, Tact compiler conditionally reserves some method IDs for use in [getters of supported interfaces](/book/contracts#interfaces), namely: $113617$ for `supported_interfaces`, $115390$ for `lazy_deployment_completed`, and $121275$ for `get_abi_ipfs`.

Sometimes, getters with different names end up with the same method ID. If this happens, you can either rename some of the getters or manually specify the method ID as a [compile-time](/ref/core-comptime) expression like so:

```tact
contract ManualMethodId {
const methodId: Int = 16384 + 42;

get(self.methodId) fun methodId1(): Int {
get(self.methodId)
fun methodId1(): Int {
return self.methodId;
}

get(crc32("crc32") + 42 & 0x3ffff | 0x4000)
fun methodId2(): Int {
return 0;
return crc32("crc32") + 42 & 0x3ffff | 0x4000;
}
}
```

Note that you *cannot* use method IDs that are reserved by TVM and you cannot use some initial positive integers because those will be used as function selectors by the compiler.
Unlike getters, method IDs for [internal functions](/book/contracts#internal-functions) and some special functions are obtained sequentially: integers in the inclusive range from $-4$ to $0$ are given to [certain message handlers](https://docs.ton.org/v3/documentation/smart-contracts/func/docs/functions#special-function-names), while internal functions are numbered with method IDs starting at $1$ and going up to $2^{14} - 1$ inclusive.

Since method IDs are $19$-bit signed integers and some of them are reserved, only the inclusive ranges from $-2^{18}$ to $-5$ and from $2^{14}$ to $2^{18} - 1$ are free to be used by users. To avoid collisions, it's recommended to specify method IDs only in these ranges, avoiding the method IDs of Tact-specific getters mentioned above.

User-specified method IDs are 19-bit signed integers, so you can use integers from $-2^{18}$ to $-5$ and from $2^{14}$ to $2^{18} - 1$.
[slice]: /book/cells#slices

Also, a few method IDs are reserved for the usage by the getters the Tact compiler can insert during compilation, those are 113617, 115390, 121275.
[tvm]: https://docs.ton.org/learn/tvm-instructions/tvm-overview
[tvm-instructions]: https://docs.ton.org/v3/documentation/tvm/instructions
[fift]: https://docs.ton.org/v3/documentation/smart-contracts/fift/overview
novusnota marked this conversation as resolved.
Show resolved Hide resolved
12 changes: 10 additions & 2 deletions docs/src/content/docs/book/import.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Additionally, Tact compiler has a versatile set of standard libraries, which com

:::caution

NOTE: All imported code is combined together with yours, so it's important to avoid name collisions and always double-check the sources!
All imported code is combined together with yours, so it's important to avoid name collisions and always double-check the sources!

:::

Expand Down Expand Up @@ -39,7 +39,7 @@ import "./relative/path/to/the/target/func/file.fc";
import "../subfolder/imported/func/file.fc";
```

But in order to use functions from such file, one has to declare them as `native` functions first. For example, when standard library [@stdlib/dns](/ref/stdlib-dns) uses a `dns.fc` FunC file, it maps FunC functions to Tact ones like so:
But in order to use functions from such file, one has to declare them as `native` functions first. For example, when standard library [`@stdlib/dns`](/ref/stdlib-dns) uses a `dns.fc` FunC file, it maps FunC functions to Tact ones like so:

```tact
// FunC code located in a file right next to the current Tact one:
Expand All @@ -50,6 +50,14 @@ import "./dns.fc";
native dnsStringToInternal(str: String): Slice?;
```

## Import Fift code

Tact does not allow you to import Fift code using the `import{:tact}` keyword. However, you can write almost any Fift code in [`asm{:tact}` functions](/book/functions#asm). The only limitations compared to writing `.fif` files come from trying to redefine the right brace `}`, using it out of place or otherwise messing with it.

Note, that the `Fift.fif` and `Asm.fif` libraries are imported and accessible by default, so you must not include those yourself.

Read more about `asm{:tact}` functions in their dedicated section: [Assembly functions](/book/functions#asm).

novusnota marked this conversation as resolved.
Show resolved Hide resolved
## Standard libraries

See [Standard libraries overview](/ref/standard-libraries).
4 changes: 2 additions & 2 deletions docs/src/content/docs/ref/core-cells.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -942,7 +942,7 @@ fun cautiousParse(payload: Cell): GuessCoin? {
## Struct.fromSlice

```tact
extends fun fromSlice(self: Struct, cell: Slice): Struct;
extends fun fromSlice(self: Struct, slice: Slice): Struct;
```

Extension function for any structure type [Struct][struct].
Expand Down Expand Up @@ -1066,7 +1066,7 @@ fun cautiousParse(payload: Cell): TripleAxe? {


```tact
extends fun fromSlice(self: Message, cell: Slice): Message;
extends fun fromSlice(self: Message, slice: Slice): Message;
```

Extension function for any message type [Message][message].
Expand Down
Loading