From ebbab54cdad8c5e11f6b810781f63ff88dfd9b62 Mon Sep 17 00:00:00 2001 From: tolk-vm Date: Thu, 31 Oct 2024 10:54:05 +0400 Subject: [PATCH] [Tolk] Tolk v0.5.0 as FunC v0.5.0 could have been like All changes from PR "FunC v0.5.0": https://github.com/ton-blockchain/ton/pull/1026 Instead of developing FunC, we decided to fork it. BTW, the first Tolk release will be v0.6, a metaphor of FunC v0.5 that missed a chance to occur. --- crypto/smartcont/mathlib.tolk | 16 +- crypto/smartcont/stdlib.tolk | 905 ++++++++++++++++++---------------- lite-client/lite-client.cpp | 2 +- tolk/CMakeLists.txt | 4 + tolk/abscode.cpp | 17 +- tolk/analyzer.cpp | 49 +- tolk/asmops.cpp | 3 + tolk/builtins.cpp | 52 +- tolk/codegen.cpp | 36 +- tolk/gen-abscode.cpp | 112 +++-- tolk/keywords.cpp | 3 + tolk/lexer.cpp | 61 ++- tolk/lexer.h | 13 +- tolk/parse-tolk.cpp | 413 +++++++++++++--- tolk/symtable.cpp | 10 +- tolk/symtable.h | 3 + tolk/tolk-wasm.cpp | 1 + tolk/tolk.cpp | 155 +++++- tolk/tolk.h | 242 ++++----- tolk/unify-types.cpp | 35 +- tonlib/tonlib/tonlib-cli.cpp | 2 +- 21 files changed, 1345 insertions(+), 789 deletions(-) diff --git a/crypto/smartcont/mathlib.tolk b/crypto/smartcont/mathlib.tolk index d4fea609..6a5b2d1b 100644 --- a/crypto/smartcont/mathlib.tolk +++ b/crypto/smartcont/mathlib.tolk @@ -81,14 +81,14 @@ int fixed248::acot(int x) inline_ref; ;; random number uniformly distributed in [0..1) ;; fixed248 random(); -int fixed248::random() impure inline; +int fixed248::random() inline; ;; random number with standard normal distribution (2100 gas on average) ;; fixed248 nrand(); -int fixed248::nrand() impure inline; +int fixed248::nrand() inline; ;; generates a random number approximately distributed according to the standard normal distribution (1200 gas) ;; (fails chi-squared test, but it is shorter and faster than fixed248::nrand()) ;; fixed248 nrand_fast(); -int fixed248::nrand_fast() impure inline; +int fixed248::nrand_fast() inline; -} ;; end (declarations) @@ -880,7 +880,7 @@ int fixed248::acot(int x) inline_ref { ;; generated by Kinderman--Monahan ratio method modified by J.Leva ;; spends ~ 2k..3k gas on average ;; fixed252 nrand(); -int nrand_f252() impure inline_ref { +int nrand_f252() inline_ref { var (x, s, t, A, B, r0) = (nan(), touch(29483) << 236, touch(-3167) << 239, 12845, 16693, 9043); ;; 4/sqrt(e*Pi) = 1.369 loop iterations on average do { @@ -910,7 +910,7 @@ int nrand_f252() impure inline_ref { ;; generates a random number approximately distributed according to the standard normal distribution ;; much faster than nrand_f252(), should be suitable for most purposes when only several random numbers are needed ;; fixed252 nrand_fast(); -int nrand_fast_f252() impure inline_ref { +int nrand_fast_f252() inline_ref { int t = touch(-3) << 253; ;; -6. as fixed252 repeat (12) { t += random() / 16; ;; add together 12 uniformly random numbers @@ -920,18 +920,18 @@ int nrand_fast_f252() impure inline_ref { ;; random number uniformly distributed in [0..1) ;; fixed248 random(); -int fixed248::random() impure inline { +int fixed248::random() inline { return random() >> 8; } ;; random number with standard normal distribution ;; fixed248 nrand(); -int fixed248::nrand() impure inline { +int fixed248::nrand() inline { return nrand_f252() ~>> 4; } ;; generates a random number approximately distributed according to the standard normal distribution ;; fixed248 nrand_fast(); -int fixed248::nrand_fast() impure inline { +int fixed248::nrand_fast() inline { return nrand_fast_f252() ~>> 4; } diff --git a/crypto/smartcont/stdlib.tolk b/crypto/smartcont/stdlib.tolk index 344d9031..8545601b 100644 --- a/crypto/smartcont/stdlib.tolk +++ b/crypto/smartcont/stdlib.tolk @@ -1,8 +1,8 @@ -;; Standard library for Tolk -;; (initially copied from stdlib.fc) -;; +// Standard library for Tolk +// (initially copied from stdlib.fc) +// -{- +/* This file is part of TON Tolk Standard Library. Tolk Standard Library is free software: you can redistribute it and/or modify @@ -15,268 +15,324 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. --} +*/ -{- +/* # Tuple manipulation primitives The names and the types are mostly self-explaining. Note that currently values of atomic type `tuple` can't be cast to composite tuple type (e.g. `[int, cell]`) and vise versa. --} +*/ -{- +/*** # Lisp-style lists Lists can be represented as nested 2-elements tuples. Empty list is conventionally represented as TVM `null` value (it can be obtained by calling [null()]). For example, tuple `(1, (2, (3, null)))` represents list `[1, 2, 3]`. Elements of a list can be of different types. --} +*/ -;;; Adds an element to the beginning of lisp-style list. -forall X -> tuple cons(X head, tuple tail) asm "CONS"; +/// Adds an element to the beginning of lisp-style list. +forall X -> tuple cons(X head, tuple tail) pure asm "CONS"; -;;; Extracts the head and the tail of lisp-style list. -forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; +/// Extracts the head and the tail of lisp-style list. +forall X -> (X, tuple) uncons(tuple list) pure asm "UNCONS"; -;;; Extracts the tail and the head of lisp-style list. -forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +/// Extracts the tail and the head of lisp-style list. +forall X -> (tuple, X) list_next(tuple list) pure asm( -> 1 0) "UNCONS"; -;;; Returns the head of lisp-style list. -forall X -> X car(tuple list) asm "CAR"; +/// Returns the head of lisp-style list. +forall X -> X car(tuple list) pure asm "CAR"; -;;; Returns the tail of lisp-style list. -tuple cdr(tuple list) asm "CDR"; +/// Returns the tail of lisp-style list. +tuple cdr(tuple list) pure asm "CDR"; -;;; Creates tuple with zero elements. -tuple empty_tuple() asm "NIL"; +/// Creates tuple with zero elements. +tuple empty_tuple() pure asm "NIL"; -;;; Appends a value `x` to a `Tuple t = (x1, ..., xn)`, but only if the resulting `Tuple t' = (x1, ..., xn, x)` -;;; is of length at most 255. Otherwise throws a type check exception. -forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; -forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; +/// Appends a value `x` to a `Tuple t = (x1, ..., xn)`, but only if the resulting `Tuple t' = (x1, ..., xn, x)` +/// is of length at most 255. Otherwise throws a type check exception. +forall X -> tuple tpush(tuple t, X value) pure asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) pure asm "TPUSH"; -;;; Creates a tuple of length one with given argument as element. -forall X -> [X] single(X x) asm "SINGLE"; +/// Creates a tuple of length one with given argument as element. +forall X -> [X] single(X x) pure asm "SINGLE"; -;;; Unpacks a tuple of length one -forall X -> X unsingle([X] t) asm "UNSINGLE"; +/// Unpacks a tuple of length one +forall X -> X unsingle([X] t) pure asm "UNSINGLE"; -;;; Creates a tuple of length two with given arguments as elements. -forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; +/// Creates a tuple of length two with given arguments as elements. +forall X, Y -> [X, Y] pair(X x, Y y) pure asm "PAIR"; -;;; Unpacks a tuple of length two -forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; +/// Unpacks a tuple of length two +forall X, Y -> (X, Y) unpair([X, Y] t) pure asm "UNPAIR"; -;;; Creates a tuple of length three with given arguments as elements. -forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; +/// Creates a tuple of length three with given arguments as elements. +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) pure asm "TRIPLE"; -;;; Unpacks a tuple of length three -forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; +/// Unpacks a tuple of length three +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) pure asm "UNTRIPLE"; -;;; Creates a tuple of length four with given arguments as elements. -forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; +/// Creates a tuple of length four with given arguments as elements. +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) pure asm "4 TUPLE"; -;;; Unpacks a tuple of length four -forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; +/// Unpacks a tuple of length four +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) pure asm "4 UNTUPLE"; -;;; Returns the first element of a tuple (with unknown element types). -forall X -> X first(tuple t) asm "FIRST"; +/// Returns the first element of a tuple (with unknown element types). +forall X -> X first(tuple t) pure asm "FIRST"; -;;; Returns the second element of a tuple (with unknown element types). -forall X -> X second(tuple t) asm "SECOND"; +/// Returns the second element of a tuple (with unknown element types). +forall X -> X second(tuple t) pure asm "SECOND"; -;;; Returns the third element of a tuple (with unknown element types). -forall X -> X third(tuple t) asm "THIRD"; +/// Returns the third element of a tuple (with unknown element types). +forall X -> X third(tuple t) pure asm "THIRD"; -;;; Returns the fourth element of a tuple (with unknown element types). -forall X -> X fourth(tuple t) asm "3 INDEX"; +/// Returns the fourth element of a tuple (with unknown element types). +forall X -> X fourth(tuple t) pure asm "3 INDEX"; -;;; Returns the first element of a pair tuple. -forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; +/// Returns the [`index`]-th element of tuple [`t`]. +forall X -> X at(tuple t, int index) pure builtin; -;;; Returns the second element of a pair tuple. -forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; +/// Returns the first element of a pair tuple. +forall X, Y -> X pair_first([X, Y] p) pure asm "FIRST"; -;;; Returns the first element of a triple tuple. -forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; +/// Returns the second element of a pair tuple. +forall X, Y -> Y pair_second([X, Y] p) pure asm "SECOND"; -;;; Returns the second element of a triple tuple. -forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; +/// Returns the first element of a triple tuple. +forall X, Y, Z -> X triple_first([X, Y, Z] p) pure asm "FIRST"; -;;; Returns the third element of a triple tuple. -forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; +/// Returns the second element of a triple tuple. +forall X, Y, Z -> Y triple_second([X, Y, Z] p) pure asm "SECOND"; + +/// Returns the third element of a triple tuple. +forall X, Y, Z -> Z triple_third([X, Y, Z] p) pure asm "THIRD"; -;;; Push null element (casted to given type) -;;; By the TVM type `Null` Tolk represents absence of a value of some atomic type. -;;; So `null` can actually have any atomic type. -forall X -> X null() asm "PUSHNULL"; +/// Push null element (casted to given type) +/// By the TVM type `Null` Tolk represents absence of a value of some atomic type. +/// So `null` can actually have any atomic type. +forall X -> X null() pure asm "PUSHNULL"; -;;; Moves a variable [x] to the top of the stack -forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; +/// Checks whether the argument is null. +forall X -> int null?(X x) pure builtin; + +/// Moves a variable [x] to the top of the stack. +forall X -> X touch(X x) pure builtin; + +/// Moves a variable [x] to the top of the stack. +forall X -> (X, ()) ~touch(X x) pure builtin; + +/// Mark a variable as used, such that the code which produced it won't be deleted even if it is not impure. +forall X -> (X, ()) ~impure_touch(X x) asm "NOP"; -;;; Returns the current Unix time as an Integer -int now() asm "NOW"; +/// Returns the current Unix time as an Integer +int now() pure asm "NOW"; -;;; Returns the internal address of the current smart contract as a Slice with a `MsgAddressInt`. -;;; If necessary, it can be parsed further using primitives such as [parse_std_addr]. -slice my_address() asm "MYADDR"; +/// Returns the internal address of the current smart contract as a Slice with a `MsgAddressInt`. +/// If necessary, it can be parsed further using primitives such as [parse_std_addr]. +slice my_address() pure asm "MYADDR"; -;;; Returns the balance of the smart contract as a tuple consisting of an int -;;; (balance in nanotoncoins) and a `cell` -;;; (a dictionary with 32-bit keys representing the balance of "extra currencies") -;;; at the start of Computation Phase. -;;; Note that RAW primitives such as [send_raw_message] do not update this field. -[int, cell] get_balance() asm "BALANCE"; +/// Returns the balance of the smart contract as a tuple consisting of an int +/// (balance in nanotoncoins) and a `cell` +/// (a dictionary with 32-bit keys representing the balance of "extra currencies") +/// at the start of Computation Phase. +/// Note that RAW primitives such as [send_raw_message] do not update this field. +[int, cell] get_balance() pure asm "BALANCE"; -;;; Returns the logical time of the current transaction. -int cur_lt() asm "LTIME"; +/// Returns the logical time of the current transaction. +int cur_lt() pure asm "LTIME"; -;;; Returns the starting logical time of the current block. -int block_lt() asm "BLOCKLT"; +/// Returns the starting logical time of the current block. +int block_lt() pure asm "BLOCKLT"; -;;; Computes the representation hash of a `cell` [c] and returns it as a 256-bit unsigned integer `x`. -;;; Useful for signing and checking signatures of arbitrary entities represented by a tree of cells. -int cell_hash(cell c) asm "HASHCU"; +/// Computes the representation hash of a `cell` [c] and returns it as a 256-bit unsigned integer `x`. +/// Useful for signing and checking signatures of arbitrary entities represented by a tree of cells. +int cell_hash(cell c) pure asm "HASHCU"; -;;; Computes the hash of a `slice s` and returns it as a 256-bit unsigned integer `x`. -;;; The result is the same as if an ordinary cell containing only data and references from `s` had been created -;;; and its hash computed by [cell_hash]. -int slice_hash(slice s) asm "HASHSU"; +/// Computes the hash of a `slice s` and returns it as a 256-bit unsigned integer `x`. +/// The result is the same as if an ordinary cell containing only data and references from `s` had been created +/// and its hash computed by [cell_hash]. +int slice_hash(slice s) pure asm "HASHSU"; -;;; Computes sha256 of the data bits of `slice` [s]. If the bit length of `s` is not divisible by eight, -;;; throws a cell underflow exception. The hash value is returned as a 256-bit unsigned integer `x`. -int string_hash(slice s) asm "SHA256U"; +/// Computes sha256 of the data bits of `slice` [s]. If the bit length of `s` is not divisible by eight, +/// throws a cell underflow exception. The hash value is returned as a 256-bit unsigned integer `x`. +int string_hash(slice s) pure asm "SHA256U"; -{- +/*** # Signature checks --} +*/ -;;; Checks the Ed25519-`signature` of a `hash` (a 256-bit unsigned integer, usually computed as the hash of some data) -;;; using [public_key] (also represented by a 256-bit unsigned integer). -;;; The signature must contain at least 512 data bits; only the first 512 bits are used. -;;; The result is `−1` if the signature is valid, `0` otherwise. -;;; Note that `CHKSIGNU` creates a 256-bit slice with the hash and calls `CHKSIGNS`. -;;; That is, if [hash] is computed as the hash of some data, these data are hashed twice, -;;; the second hashing occurring inside `CHKSIGNS`. -int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; +/// Checks the Ed25519-`signature` of a `hash` (a 256-bit unsigned integer, usually computed as the hash of some data) +/// using [public_key] (also represented by a 256-bit unsigned integer). +/// The signature must contain at least 512 data bits; only the first 512 bits are used. +/// The result is `−1` if the signature is valid, `0` otherwise. +/// Note that `CHKSIGNU` creates a 256-bit slice with the hash and calls `CHKSIGNS`. +/// That is, if [hash] is computed as the hash of some data, these data are hashed twice, +/// the second hashing occurring inside `CHKSIGNS`. +int check_signature(int hash, slice signature, int public_key) pure asm "CHKSIGNU"; -;;; Checks whether [signature] is a valid Ed25519-signature of the data portion of `slice data` using `public_key`, -;;; similarly to [check_signature]. -;;; If the bit length of [data] is not divisible by eight, throws a cell underflow exception. -;;; The verification of Ed25519 signatures is the standard one, -;;; with sha256 used to reduce [data] to the 256-bit number that is actually signed. -int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; +/// Checks whether [signature] is a valid Ed25519-signature of the data portion of `slice data` using `public_key`, +/// similarly to [check_signature]. +/// If the bit length of [data] is not divisible by eight, throws a cell underflow exception. +/// The verification of Ed25519 signatures is the standard one, +/// with sha256 used to reduce [data] to the 256-bit number that is actually signed. +int check_data_signature(slice data, slice signature, int public_key) pure asm "CHKSIGNS"; -{--- +/*** # Computation of boc size The primitives below may be useful for computing storage fees of user-provided data. --} +*/ -;;; Returns `(x, y, z, -1)` or `(null, null, null, 0)`. -;;; Recursively computes the count of distinct cells `x`, data bits `y`, and cell references `z` -;;; in the DAG rooted at `cell` [c], effectively returning the total storage used by this DAG taking into account -;;; the identification of equal cells. -;;; The values of `x`, `y`, and `z` are computed by a depth-first traversal of this DAG, -;;; with a hash table of visited cell hashes used to prevent visits of already-visited cells. -;;; The total count of visited cells `x` cannot exceed non-negative [max_cells]; -;;; otherwise the computation is aborted before visiting the `(max_cells + 1)`-st cell and -;;; a zero flag is returned to indicate failure. If [c] is `null`, returns `x = y = z = 0`. -(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; +/// A non-quiet version of [compute_data_size?] that throws a cell overflow exception (`8`) on failure. +(int, int, int) compute_data_size(cell c, int max_cells) asm "CDATASIZE"; -;;; Similar to [compute_data_size?], but accepting a `slice` [s] instead of a `cell`. -;;; The returned value of `x` does not take into account the cell that contains the `slice` [s] itself; -;;; however, the data bits and the cell references of [s] are accounted for in `y` and `z`. -(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; +/// A non-quiet version of [slice_compute_data_size?] that throws a cell overflow exception (`8`) on failure. +(int, int, int) slice_compute_data_size(slice s, int max_cells) asm "SDATASIZE"; -;;; A non-quiet version of [compute_data_size?] that throws a cell overflow exception (`8`) on failure. -(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +/// Returns `(x, y, z, -1)` or `(null, null, null, 0)`. +/// Recursively computes the count of distinct cells `x`, data bits `y`, and cell references `z` +/// in the DAG rooted at `cell` [c], effectively returning the total storage used by this DAG taking into account +/// the identification of equal cells. +/// The values of `x`, `y`, and `z` are computed by a depth-first traversal of this DAG, +/// with a hash table of visited cell hashes used to prevent visits of already-visited cells. +/// The total count of visited cells `x` cannot exceed non-negative [max_cells]; +/// otherwise the computation is aborted before visiting the `(max_cells + 1)`-st cell and +/// a zero flag is returned to indicate failure. If [c] is `null`, returns `x = y = z = 0`. +(int, int, int, int) compute_data_size?(cell c, int max_cells) pure asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; -;;; A non-quiet version of [slice_compute_data_size?] that throws a cell overflow exception (8) on failure. -(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; +/// Similar to [compute_data_size?], but accepting a `slice` [s] instead of a `cell`. +/// The returned value of `x` does not take into account the cell that contains the `slice` [s] itself; +/// however, the data bits and the cell references of [s] are accounted for in `y` and `z`. +(int, int, int, int) slice_compute_data_size?(slice s, int max_cells) pure asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; -;;; Throws an exception with exit_code excno if cond is not 0 (commented since implemented in compilator) -;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; +/// Throws exception [`excno`] with parameter zero. +/// In other words, it transfers control to the continuation in `c2`, +/// pushing `0` and [`excno`] into it's stack, and discarding the old stack altogether. +() throw(int excno) builtin; -{-- +/// Throws exception [`excno`] with parameter zero only if [`cond`] != `0`. +() throw_if(int excno, int cond) builtin; + +/// Throws exception [`excno`] with parameter zero only if [`cond`] == `0`. +() throw_unless(int excno, int cond) builtin; + +/// Throws exception [`excno`] with parameter [`x`], +/// by copying [`x`] and [`excno`] into the stack of `c2` and transferring control to `c2`. +forall X -> () throw_arg(X x, int excno) builtin; + +/// Throws exception [`excno`] with parameter [`x`] only if [`cond`] != `0`. +forall X -> () throw_arg_if(X x, int excno, int cond) builtin; + +/// Throws exception [`excno`] with parameter [`x`] only if [`cond`] == `0`. +forall X -> () throw_arg_unless(X x, int excno, int cond) builtin; + +/*** # Debug primitives Only works for local TVM execution with debug level verbosity --} -;;; Dumps the stack (at most the top 255 values) and shows the total stack depth. -() dump_stack() impure asm "DUMPSTK"; +*/ -{- +/// Dump a variable [x] to the debug log. +forall X -> (X, ()) ~dump(X x) builtin; + +/// Dump a string [x] to the debug log. +forall X -> (X, ()) ~strdump(X x) builtin; + +/// Dumps the stack (at most the top 255 values) and shows the total stack depth. +() dump_stack() asm "DUMPSTK"; + +/*** # Persistent storage save and load --} +*/ -;;; Returns the persistent contract storage cell. It can be parsed or modified with slice and builder primitives later. -cell get_data() asm "c4 PUSH"; +/// Returns the persistent contract storage cell. It can be parsed or modified with slice and builder primitives later. +cell get_data() pure asm "c4 PUSH"; -;;; Sets `cell` [c] as persistent contract data. You can update persistent contract storage with this primitive. -() set_data(cell c) impure asm "c4 POP"; +/// Sets `cell` [c] as persistent contract data. You can update persistent contract storage with this primitive. +() set_data(cell c) asm "c4 POP"; -{- +/*** # Continuation primitives --} -;;; Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls. -;;; The primitive returns the current value of `c3`. -cont get_c3() impure asm "c3 PUSH"; +*/ +/// Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls. +/// The primitive returns the current value of `c3`. +cont get_c3() pure asm "c3 PUSH"; -;;; Updates the current value of `c3`. Usually, it is used for updating smart contract code in run-time. -;;; Note that after execution of this primitive the current code -;;; (and the stack of recursive function calls) won't change, -;;; but any other function call will use a function from the new code. -() set_c3(cont c) impure asm "c3 POP"; +/// Updates the current value of `c3`. Usually, it is used for updating smart contract code in run-time. +/// Note that after execution of this primitive the current code +/// (and the stack of recursive function calls) won't change, +/// but any other function call will use a function from the new code. +() set_c3(cont c) asm "c3 POP"; -;;; Transforms a `slice` [s] into a simple ordinary continuation `c`, with `c.code = s` and an empty stack and savelist. -cont bless(slice s) impure asm "BLESS"; +/// Transforms a `slice` [s] into a simple ordinary continuation `c`, with `c.code = s` and an empty stack and savelist. +cont bless(slice s) pure asm "BLESS"; -{--- +/*** # Gas related primitives --} +*/ -;;; Sets current gas limit `gl` to its maximal allowed value `gm`, and resets the gas credit `gc` to zero, -;;; decreasing the value of `gr` by `gc` in the process. -;;; In other words, the current smart contract agrees to buy some gas to finish the current transaction. -;;; This action is required to process external messages, which bring no value (hence no gas) with themselves. -;;; -;;; For more details check [accept_message effects](https://ton.org/docs/#/smart-contracts/accept). -() accept_message() impure asm "ACCEPT"; +/// Sets current gas limit `gl` to its maximal allowed value `gm`, and resets the gas credit `gc` to zero, +/// decreasing the value of `gr` by `gc` in the process. +/// In other words, the current smart contract agrees to buy some gas to finish the current transaction. +/// This action is required to process external messages, which bring no value (hence no gas) with themselves. +/// +/// For more details check [accept_message effects](https://ton.org/docs/#/smart-contracts/accept). +() accept_message() asm "ACCEPT"; -;;; Sets current gas limit `gl` to the minimum of limit and `gm`, and resets the gas credit `gc` to zero. -;;; If the gas consumed so far (including the present instruction) exceeds the resulting value of `gl`, -;;; an (unhandled) out of gas exception is thrown before setting new gas limits. -;;; Notice that [set_gas_limit] with an argument `limit ≥ 2^63 − 1` is equivalent to [accept_message]. -() set_gas_limit(int limit) impure asm "SETGASLIMIT"; +/// Sets current gas limit `gl` to the minimum of limit and `gm`, and resets the gas credit `gc` to zero. +/// If the gas consumed so far (including the present instruction) exceeds the resulting value of `gl`, +/// an (unhandled) out of gas exception is thrown before setting new gas limits. +/// Notice that [set_gas_limit] with an argument `limit ≥ 2^63 − 1` is equivalent to [accept_message]. +() set_gas_limit(int limit) asm "SETGASLIMIT"; -;;; Commits the current state of registers `c4` (“persistent data”) and `c5` (“actions”) -;;; so that the current execution is considered “successful” with the saved values even if an exception -;;; in Computation Phase is thrown later. -() commit() impure asm "COMMIT"; +/// Commits the current state of registers `c4` (“persistent data”) and `c5` (“actions”) +/// so that the current execution is considered “successful” with the saved values even if an exception +/// in Computation Phase is thrown later. +() commit() asm "COMMIT"; -;;; Not implemented -;;() buy_gas(int gram) impure asm "BUYGAS"; +/// Not implemented +//() buy_gas(int gram) asm "BUYGAS"; -;;; Computes the amount of gas that can be bought for `amount` nanoTONs, -;;; and sets `gl` accordingly in the same way as [set_gas_limit]. -() buy_gas(int amount) impure asm "BUYGAS"; +/// Computes the amount of gas that can be bought for `amount` nanoTONs, +/// and sets `gl` accordingly in the same way as [set_gas_limit]. +() buy_gas(int amount) asm "BUYGAS"; -;;; Computes the minimum of two integers [x] and [y]. -int min(int x, int y) asm "MIN"; +/// Computes the minimum of two integers [x] and [y]. +int min(int x, int y) pure asm "MIN"; -;;; Computes the maximum of two integers [x] and [y]. -int max(int x, int y) asm "MAX"; +/// Computes the maximum of two integers [x] and [y]. +int max(int x, int y) pure asm "MAX"; -;;; Sorts two integers. -(int, int) minmax(int x, int y) asm "MINMAX"; +/// Sorts two integers. +(int, int) minmax(int x, int y) pure asm "MINMAX"; -;;; Computes the absolute value of an integer [x]. -int abs(int x) asm "ABS"; +/// Computes the absolute value of an integer [x]. +int abs(int x) pure asm "ABS"; -{- +/// Computes the quotient and remainder of [x] / [y]. Example: divmod(112,3) = (37,1) +(int, int) divmod(int x, int y) pure builtin; + +/// Computes the remainder and quotient of [x] / [y]. Example: moddiv(112,3) = (1,37) +(int, int) moddiv(int x, int y) pure builtin; + +/// Computes multiple-then-divide: floor([x] * [y] / [z]). +/// The intermediate result is stored in a 513-bit integer to prevent precision loss. +int muldiv(int x, int y, int z) pure builtin; + +/// Similar to `muldiv`, but rounds the result: round([x] * [y] / [z]). +int muldivr(int x, int y, int z) pure builtin; + +/// Similar to `muldiv`, but ceils the result: ceil([x] * [y] / [z]). +int muldivc(int x, int y, int z) pure builtin; + +/// Computes the quotient and remainder of ([x] * [y] / [z]). Example: muldivmod(112,3,10) = (33,6) +(int, int) muldivmod(int x, int y, int z) pure builtin; + +/*** # Slice primitives It is said that a primitive _loads_ some data, @@ -287,131 +343,129 @@ int abs(int x) asm "ABS"; (it can be used as non-modifying method). Unless otherwise stated, loading and preloading primitives read the data from a prefix of the slice. --} +*/ -;;; Converts a `cell` [c] into a `slice`. Notice that [c] must be either an ordinary cell, -;;; or an exotic cell (see [TVM.pdf](https://ton-blockchain.github.io/docs/tvm.pdf), 3.1.2) -;;; which is automatically loaded to yield an ordinary cell `c'`, converted into a `slice` afterwards. -slice begin_parse(cell c) asm "CTOS"; +/// Converts a `cell` [c] into a `slice`. Notice that [c] must be either an ordinary cell, +/// or an exotic cell (see [TVM.pdf](https://ton-blockchain.github.io/docs/tvm.pdf), 3.1.2) +/// which is automatically loaded to yield an ordinary cell `c'`, converted into a `slice` afterwards. +slice begin_parse(cell c) pure asm "CTOS"; -;;; Checks if [s] is empty. If not, throws an exception. -() end_parse(slice s) impure asm "ENDS"; +/// Checks if [s] is empty. If not, throws an exception. +() end_parse(slice s) asm "ENDS"; -;;; Loads the first reference from the slice. -(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +/// Loads the first reference from the slice. +(slice, cell) load_ref(slice s) pure asm( -> 1 0) "LDREF"; -;;; Preloads the first reference from the slice. -cell preload_ref(slice s) asm "PLDREF"; +/// Preloads the first reference from the slice. +cell preload_ref(slice s) pure asm "PLDREF"; - {- Functions below are commented because are implemented on compilator level for optimisation -} +/// Loads a signed [len]-bit integer from a slice [s]. +(slice, int) load_int(slice s, int len) pure builtin; -;;; Loads a signed [len]-bit integer from a slice [s]. -;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; +/// Loads an unsigned [len]-bit integer from a slice [s]. +(slice, int) load_uint(slice s, int len) pure builtin; -;;; Loads an unsigned [len]-bit integer from a slice [s]. -;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; +/// Preloads a signed [len]-bit integer from a slice [s]. +int preload_int(slice s, int len) pure builtin; -;;; Preloads a signed [len]-bit integer from a slice [s]. -;; int preload_int(slice s, int len) asm "PLDIX"; +/// Preloads an unsigned [len]-bit integer from a slice [s]. +int preload_uint(slice s, int len) pure builtin; -;;; Preloads an unsigned [len]-bit integer from a slice [s]. -;; int preload_uint(slice s, int len) asm "PLDUX"; +/// Loads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate slice `s''`. +(slice, slice) load_bits(slice s, int len) pure builtin; -;;; Loads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. -;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; +/// Preloads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate slice `s''`. +slice preload_bits(slice s, int len) pure builtin; -;;; Preloads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. -;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; +/// Loads serialized amount of TonCoins (any unsigned integer up to `2^120 - 1`). +(slice, int) load_grams(slice s) pure asm( -> 1 0) "LDGRAMS"; +(slice, int) load_coins(slice s) pure asm( -> 1 0) "LDGRAMS"; -;;; Loads serialized amount of TonCoins (any unsigned integer up to `2^120 - 1`). -(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; -(slice, int) load_coins(slice s) asm( -> 1 0) "LDGRAMS"; +/// Returns all but the first `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice skip_bits(slice s, int len) pure asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) pure asm "SDSKIPFIRST"; -;;; Returns all but the first `0 ≤ len ≤ 1023` bits of `slice` [s]. -slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; -(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; +/// Returns the first `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice first_bits(slice s, int len) pure asm "SDCUTFIRST"; -;;; Returns the first `0 ≤ len ≤ 1023` bits of `slice` [s]. -slice first_bits(slice s, int len) asm "SDCUTFIRST"; +/// Returns all but the last `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice skip_last_bits(slice s, int len) pure asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) pure asm "SDSKIPLAST"; -;;; Returns all but the last `0 ≤ len ≤ 1023` bits of `slice` [s]. -slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; -(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +/// Returns the last `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice slice_last(slice s, int len) pure asm "SDCUTLAST"; -;;; Returns the last `0 ≤ len ≤ 1023` bits of `slice` [s]. -slice slice_last(slice s, int len) asm "SDCUTLAST"; +/// Loads a dictionary `D` (HashMapE) from `slice` [s]. +/// (returns `null` if `nothing` constructor is used). +(slice, cell) load_dict(slice s) pure asm( -> 1 0) "LDDICT"; -;;; Loads a dictionary `D` (HashMapE) from `slice` [s]. -;;; (returns `null` if `nothing` constructor is used). -(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +/// Preloads a dictionary `D` from `slice` [s]. +cell preload_dict(slice s) pure asm "PLDDICT"; -;;; Preloads a dictionary `D` from `slice` [s]. -cell preload_dict(slice s) asm "PLDDICT"; +/// Loads a dictionary as [load_dict], but returns only the remainder of the slice. +slice skip_dict(slice s) pure asm "SKIPDICT"; -;;; Loads a dictionary as [load_dict], but returns only the remainder of the slice. -slice skip_dict(slice s) asm "SKIPDICT"; +/// Loads (Maybe ^Cell) from `slice` [s]. +/// In other words loads 1 bit and if it is true +/// loads first ref and return it with slice remainder +/// otherwise returns `null` and slice remainder +(slice, cell) load_maybe_ref(slice s) pure asm( -> 1 0) "LDOPTREF"; -;;; Loads (Maybe ^Cell) from `slice` [s]. -;;; In other words loads 1 bit and if it is true -;;; loads first ref and return it with slice remainder -;;; otherwise returns `null` and slice remainder -(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; - -;;; Preloads (Maybe ^Cell) from `slice` [s]. -cell preload_maybe_ref(slice s) asm "PLDOPTREF"; +/// Preloads (Maybe ^Cell) from `slice` [s]. +cell preload_maybe_ref(slice s) pure asm "PLDOPTREF"; -;;; Returns the depth of `cell` [c]. -;;; If [c] has no references, then return `0`; -;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [c]. -;;; If [c] is a `null` instead of a cell, returns zero. -int cell_depth(cell c) asm "CDEPTH"; +/// Returns the depth of `cell` [c]. +/// If [c] has no references, then return `0`; +/// otherwise the returned value is one plus the maximum of depths of cells referred to from [c]. +/// If [c] is a `null` instead of a cell, returns zero. +int cell_depth(cell c) pure asm "CDEPTH"; -{- +/*** # Slice size primitives --} +*/ -;;; Returns the number of references in `slice` [s]. -int slice_refs(slice s) asm "SREFS"; +/// Returns the number of references in `slice` [s]. +int slice_refs(slice s) pure asm "SREFS"; -;;; Returns the number of data bits in `slice` [s]. -int slice_bits(slice s) asm "SBITS"; +/// Returns the number of data bits in `slice` [s]. +int slice_bits(slice s) pure asm "SBITS"; -;;; Returns both the number of data bits and the number of references in `slice` [s]. -(int, int) slice_bits_refs(slice s) asm "SBITREFS"; +/// Returns both the number of data bits and the number of references in `slice` [s]. +(int, int) slice_bits_refs(slice s) pure asm "SBITREFS"; -;;; Checks whether a `slice` [s] is empty (i.e., contains no bits of data and no cell references). -int slice_empty?(slice s) asm "SEMPTY"; +/// Checks whether a `slice` [s] is empty (i.e., contains no bits of data and no cell references). +int slice_empty?(slice s) pure asm "SEMPTY"; -;;; Checks whether `slice` [s] has no bits of data. -int slice_data_empty?(slice s) asm "SDEMPTY"; +/// Checks whether `slice` [s] has no bits of data. +int slice_data_empty?(slice s) pure asm "SDEMPTY"; -;;; Checks whether `slice` [s] has no references. -int slice_refs_empty?(slice s) asm "SREMPTY"; +/// Checks whether `slice` [s] has no references. +int slice_refs_empty?(slice s) pure asm "SREMPTY"; -;;; Returns the depth of `slice` [s]. -;;; If [s] has no references, then returns `0`; -;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [s]. -int slice_depth(slice s) asm "SDEPTH"; +/// Returns the depth of `slice` [s]. +/// If [s] has no references, then returns `0`; +/// otherwise the returned value is one plus the maximum of depths of cells referred to from [s]. +int slice_depth(slice s) pure asm "SDEPTH"; -{- +/*** # Builder size primitives --} +*/ -;;; Returns the number of cell references already stored in `builder` [b] -int builder_refs(builder b) asm "BREFS"; +/// Returns the number of cell references already stored in `builder` [b] +int builder_refs(builder b) pure asm "BREFS"; -;;; Returns the number of data bits already stored in `builder` [b]. -int builder_bits(builder b) asm "BBITS"; +/// Returns the number of data bits already stored in `builder` [b]. +int builder_bits(builder b) pure asm "BBITS"; -;;; Returns the depth of `builder` [b]. -;;; If no cell references are stored in [b], then returns 0; -;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [b]. -int builder_depth(builder b) asm "BDEPTH"; +/// Returns the depth of `builder` [b]. +/// If no cell references are stored in [b], then returns 0; +/// otherwise the returned value is one plus the maximum of depths of cells referred to from [b]. +int builder_depth(builder b) pure asm "BDEPTH"; -{- +/*** # Builder primitives It is said that a primitive _stores_ a value `x` into a builder `b` if it returns a modified version of the builder `b'` with the value `x` stored at the end of it. @@ -419,48 +473,47 @@ int builder_depth(builder b) asm "BDEPTH"; All the primitives below first check whether there is enough space in the `builder`, and only then check the range of the value being serialized. --} +*/ -;;; Creates a new empty `builder`. -builder begin_cell() asm "NEWC"; +/// Creates a new empty `builder`. +builder begin_cell() pure asm "NEWC"; -;;; Converts a `builder` into an ordinary `cell`. -cell end_cell(builder b) asm "ENDC"; +/// Converts a `builder` into an ordinary `cell`. +cell end_cell(builder b) pure asm "ENDC"; -;;; Stores a reference to `cell` [c] into `builder` [b]. -builder store_ref(builder b, cell c) asm(c b) "STREF"; +/// Stores a reference to `cell` [c] into `builder` [b]. +builder store_ref(builder b, cell c) pure asm(c b) "STREF"; -;;; Stores an unsigned [len]-bit integer `x` into `b` for `0 ≤ len ≤ 256`. -;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; +/// Stores an unsigned [len]-bit integer `x` into `b` for `0 ≤ len ≤ 256`. +builder store_uint(builder b, int x, int len) pure builtin; -;;; Stores a signed [len]-bit integer `x` into `b` for` 0 ≤ len ≤ 257`. -;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; +/// Stores a signed [len]-bit integer `x` into `b` for `0 ≤ len ≤ 257`. +builder store_int(builder b, int x, int len) pure builtin; + +/// Stores `slice` [s] into `builder` [b]. +builder store_slice(builder b, slice s) pure asm "STSLICER"; + +/// Stores (serializes) an integer [x] in the range `0..2^120 − 1` into `builder` [b]. +/// The serialization of [x] consists of a 4-bit unsigned big-endian integer `l`, +/// which is the smallest integer `l ≥ 0`, such that `x < 2^8l`, +/// followed by an `8l`-bit unsigned big-endian representation of [x]. +/// If [x] does not belong to the supported range, a range check exception is thrown. +/// +/// Store amounts of TonCoins to the builder as VarUInteger 16 +builder store_grams(builder b, int x) pure asm "STGRAMS"; +builder store_coins(builder b, int x) pure asm "STGRAMS"; + +/// Stores dictionary `D` represented by `cell` [c] or `null` into `builder` [b]. +/// In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise. +builder store_dict(builder b, cell c) pure asm(c b) "STDICT"; + +/// Stores (Maybe ^Cell) to builder: +/// if cell is null store 1 zero bit +/// otherwise store 1 true bit and ref to cell +builder store_maybe_ref(builder b, cell c) pure asm(c b) "STOPTREF"; -;;; Stores `slice` [s] into `builder` [b] -builder store_slice(builder b, slice s) asm "STSLICER"; - -;;; Stores (serializes) an integer [x] in the range `0..2^120 − 1` into `builder` [b]. -;;; The serialization of [x] consists of a 4-bit unsigned big-endian integer `l`, -;;; which is the smallest integer `l ≥ 0`, such that `x < 2^8l`, -;;; followed by an `8l`-bit unsigned big-endian representation of [x]. -;;; If [x] does not belong to the supported range, a range check exception is thrown. -;;; -;;; Store amounts of TonCoins to the builder as VarUInteger 16 -builder store_grams(builder b, int x) asm "STGRAMS"; -builder store_coins(builder b, int x) asm "STGRAMS"; - -;;; Stores dictionary `D` represented by `cell` [c] or `null` into `builder` [b]. -;;; In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise. -builder store_dict(builder b, cell c) asm(c b) "STDICT"; - -;;; Stores (Maybe ^Cell) to builder: -;;; if cell is null store 1 zero bit -;;; otherwise store 1 true bit and ref to cell -builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; - - -{- +/*** # Address manipulation primitives The address manipulation primitives listed below serialize and deserialize values according to the following TL-B scheme: ```TL-B @@ -495,144 +548,144 @@ builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; Next, integer `x` is the `workchain_id`, and slice `s` contains the address. - `addr_var` is represented by `t = (3, u, x, s)`, where `u`, `x`, and `s` have the same meaning as for `addr_std`. --} +*/ -;;; Loads from slice [s] the only prefix that is a valid `MsgAddress`, -;;; and returns both this prefix `s'` and the remainder `s''` of [s] as slices. -(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +/// Loads from slice [s] the only prefix that is a valid `MsgAddress`, +/// and returns both this prefix `s'` and the remainder `s''` of [s] as slices. +(slice, slice) load_msg_addr(slice s) pure asm( -> 1 0) "LDMSGADDR"; -;;; Decomposes slice [s] containing a valid `MsgAddress` into a `tuple t` with separate fields of this `MsgAddress`. -;;; If [s] is not a valid `MsgAddress`, a cell deserialization exception is thrown. -tuple parse_addr(slice s) asm "PARSEMSGADDR"; +/// Decomposes slice [s] containing a valid `MsgAddress` into a `tuple t` with separate fields of this `MsgAddress`. +/// If [s] is not a valid `MsgAddress`, a cell deserialization exception is thrown. +tuple parse_addr(slice s) pure asm "PARSEMSGADDR"; -;;; Parses slice [s] containing a valid `MsgAddressInt` (usually a `msg_addr_std`), -;;; applies rewriting from the anycast (if present) to the same-length prefix of the address, -;;; and returns both the workchain and the 256-bit address as integers. -;;; If the address is not 256-bit, or if [s] is not a valid serialization of `MsgAddressInt`, -;;; throws a cell deserialization exception. -(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; +/// Parses slice [s] containing a valid `MsgAddressInt` (usually a `msg_addr_std`), +/// applies rewriting from the anycast (if present) to the same-length prefix of the address, +/// and returns both the workchain and the 256-bit address as integers. +/// If the address is not 256-bit, or if [s] is not a valid serialization of `MsgAddressInt`, +/// throws a cell deserialization exception. +(int, int) parse_std_addr(slice s) pure asm "REWRITESTDADDR"; -;;; A variant of [parse_std_addr] that returns the (rewritten) address as a slice [s], -;;; even if it is not exactly 256 bit long (represented by a `msg_addr_var`). -(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; +/// A variant of [parse_std_addr] that returns the (rewritten) address as a slice [s], +/// even if it is not exactly 256 bit long (represented by a `msg_addr_var`). +(int, slice) parse_var_addr(slice s) pure asm "REWRITEVARADDR"; -{- +/*** # Dictionary primitives --} +*/ -;;; Sets the value associated with [key_len]-bit key signed index in dictionary [dict] to [value] (cell), -;;; and returns the resulting dictionary. -cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; -(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +/// Sets the value associated with [key_len]-bit key signed index in dictionary [dict] to [value] (cell), +/// and returns the resulting dictionary. +cell idict_set_ref(cell dict, int key_len, int index, cell value) pure asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) pure asm(value index dict key_len) "DICTISETREF"; -;;; Sets the value associated with [key_len]-bit key unsigned index in dictionary [dict] to [value] (cell), -;;; and returns the resulting dictionary. -cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; -(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +/// Sets the value associated with [key_len]-bit key unsigned index in dictionary [dict] to [value] (cell), +/// and returns the resulting dictionary. +cell udict_set_ref(cell dict, int key_len, int index, cell value) pure asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) pure asm(value index dict key_len) "DICTUSETREF"; -cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; -(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT"; -(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT"; -(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; -(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; -(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; -(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; -(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; -(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; -(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; -(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; -(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; -(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; -cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; -(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; -cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; -(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; -cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; -(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; -(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; -(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; -(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; -(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; -cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; -(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; -cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; -(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; -cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; -(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; -(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; -(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; -(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; -(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; -(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; -(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; -(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; -(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; -(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; -(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; -(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; -(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; -(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; -(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; -(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; -(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; -(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; -(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; -(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; -(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; -(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; -(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; -(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; -(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; -(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; -(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; -(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; -(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; -(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; -(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; -(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; -(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; +cell idict_get_ref(cell dict, int key_len, int index) pure asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) pure asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) pure asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) pure asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) pure asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) pure asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) pure asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) pure asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) pure asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) pure asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) pure asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) pure asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) pure asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) pure asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) pure asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) pure asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) pure asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) pure asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) pure asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) pure asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) pure asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) pure asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) pure asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) pure asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) pure asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) pure asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) pure asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) pure asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) pure asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) pure asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) pure asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) pure asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) pure asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) pure asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) pure asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) pure asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) pure asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) pure asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) pure asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) pure asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) pure asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) pure asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) pure asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) pure asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) pure asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) pure asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) pure asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) pure asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) pure asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) pure asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; -;;; Creates an empty dictionary, which is actually a null value. Equivalent to PUSHNULL -cell new_dict() asm "NEWDICT"; -;;; Checks whether a dictionary is empty. Equivalent to cell_null?. -int dict_empty?(cell c) asm "DICTEMPTY"; +/// Creates an empty dictionary, which is actually a null value. Equivalent to PUSHNULL +cell new_dict() pure asm "NEWDICT"; +/// Checks whether a dictionary is empty. Equivalent to cell_null?. +int dict_empty?(cell c) pure asm "DICTEMPTY"; -{- Prefix dictionary primitives -} -(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; -(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; -(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; +/* Prefix dictionary primitives */ +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) pure asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) pure asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) pure asm(key dict key_len) "PFXDICTDEL"; -;;; Returns the value of the global configuration parameter with integer index `i` as a `cell` or `null` value. -cell config_param(int x) asm "CONFIGOPTPARAM"; -;;; Checks whether c is a null. Note, that Tolk also has polymorphic null? built-in. -int cell_null?(cell c) asm "ISNULL"; +/// Returns the value of the global configuration parameter with integer index `i` as a `cell` or `null` value. +cell config_param(int x) pure asm "CONFIGOPTPARAM"; +/// Checks whether c is a null. Note, that Tolk also has polymorphic null? built-in. +int cell_null?(cell c) pure asm "ISNULL"; -;;; Creates an output action which would reserve exactly amount nanotoncoins (if mode = 0), at most amount nanotoncoins (if mode = 2), or all but amount nanotoncoins (if mode = 1 or mode = 3), from the remaining balance of the account. It is roughly equivalent to creating an outbound message carrying amount nanotoncoins (or b − amount nanotoncoins, where b is the remaining balance) to oneself, so that the subsequent output actions would not be able to spend more money than the remainder. Bit +2 in mode means that the external action does not fail if the specified amount cannot be reserved; instead, all remaining balance is reserved. Bit +8 in mode means `amount <- -amount` before performing any further actions. Bit +4 in mode means that amount is increased by the original balance of the current account (before the compute phase), including all extra currencies, before performing any other checks and actions. Currently, amount must be a non-negative integer, and mode must be in the range 0..15. -() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; -;;; Similar to raw_reserve, but also accepts a dictionary extra_amount (represented by a cell or null) with extra currencies. In this way currencies other than TonCoin can be reserved. -() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; -;;; Sends a raw message contained in msg, which should contain a correctly serialized object Message X, with the only exception that the source address is allowed to have dummy value addr_none (to be automatically replaced with the current smart contract address), and ihr_fee, fwd_fee, created_lt and created_at fields can have arbitrary values (to be rewritten with correct values during the action phase of the current transaction). Integer parameter mode contains the flags. Currently mode = 0 is used for ordinary messages; mode = 128 is used for messages that are to carry all the remaining balance of the current smart contract (instead of the value originally indicated in the message); mode = 64 is used for messages that carry all the remaining value of the inbound message in addition to the value initially indicated in the new message (if bit 0 is not set, the gas fees are deducted from this amount); mode' = mode + 1 means that the sender wants to pay transfer fees separately; mode' = mode + 2 means that any errors arising while processing this message during the action phase should be ignored. Finally, mode' = mode + 32 means that the current account must be destroyed if its resulting balance is zero. This flag is usually employed together with +128. -() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; -;;; Creates an output action that would change this smart contract code to that given by cell new_code. Notice that this change will take effect only after the successful termination of the current run of the smart contract -() set_code(cell new_code) impure asm "SETCODE"; +/// Creates an output action which would reserve exactly amount nanotoncoins (if mode = 0), at most amount nanotoncoins (if mode = 2), or all but amount nanotoncoins (if mode = 1 or mode = 3), from the remaining balance of the account. It is roughly equivalent to creating an outbound message carrying amount nanotoncoins (or b − amount nanotoncoins, where b is the remaining balance) to oneself, so that the subsequent output actions would not be able to spend more money than the remainder. Bit +2 in mode means that the external action does not fail if the specified amount cannot be reserved; instead, all remaining balance is reserved. Bit +8 in mode means `amount <- -amount` before performing any further actions. Bit +4 in mode means that amount is increased by the original balance of the current account (before the compute phase), including all extra currencies, before performing any other checks and actions. Currently, amount must be a non-negative integer, and mode must be in the range 0..15. +() raw_reserve(int amount, int mode) asm "RAWRESERVE"; +/// Similar to raw_reserve, but also accepts a dictionary extra_amount (represented by a cell or null) with extra currencies. In this way currencies other than TonCoin can be reserved. +() raw_reserve_extra(int amount, cell extra_amount, int mode) asm "RAWRESERVEX"; +/// Sends a raw message contained in msg, which should contain a correctly serialized object Message X, with the only exception that the source address is allowed to have dummy value addr_none (to be automatically replaced with the current smart contract address), and ihr_fee, fwd_fee, created_lt and created_at fields can have arbitrary values (to be rewritten with correct values during the action phase of the current transaction). Integer parameter mode contains the flags. Currently mode = 0 is used for ordinary messages; mode = 128 is used for messages that are to carry all the remaining balance of the current smart contract (instead of the value originally indicated in the message); mode = 64 is used for messages that carry all the remaining value of the inbound message in addition to the value initially indicated in the new message (if bit 0 is not set, the gas fees are deducted from this amount); mode' = mode + 1 means that the sender wants to pay transfer fees separately; mode' = mode + 2 means that any errors arising while processing this message during the action phase should be ignored. Finally, mode' = mode + 32 means that the current account must be destroyed if its resulting balance is zero. This flag is usually employed together with +128. +() send_raw_message(cell msg, int mode) asm "SENDRAWMSG"; +/// Creates an output action that would change this smart contract code to that given by cell new_code. Notice that this change will take effect only after the successful termination of the current run of the smart contract +() set_code(cell new_code) asm "SETCODE"; -;;; Generates a new pseudo-random unsigned 256-bit integer x. The algorithm is as follows: if r is the old value of the random seed, considered as a 32-byte array (by constructing the big-endian representation of an unsigned 256-bit integer), then its sha512(r) is computed; the first 32 bytes of this hash are stored as the new value r' of the random seed, and the remaining 32 bytes are returned as the next random value x. -int random() impure asm "RANDU256"; -;;; Generates a new pseudo-random integer z in the range 0..range−1 (or range..−1, if range < 0). More precisely, an unsigned random value x is generated as in random; then z := x * range / 2^256 is computed. -int rand(int range) impure asm "RAND"; -;;; Returns the current random seed as an unsigned 256-bit Integer. -int get_seed() impure asm "RANDSEED"; -;;; Sets the random seed to unsigned 256-bit seed. -() set_seed(int) impure asm "SETRAND"; -;;; Mixes unsigned 256-bit integer x into the random seed r by setting the random seed to sha256 of the concatenation of two 32-byte strings: the first with the big-endian representation of the old seed r, and the second with the big-endian representation of x. -() randomize(int x) impure asm "ADDRAND"; -;;; Equivalent to randomize(cur_lt());. -() randomize_lt() impure asm "LTIME" "ADDRAND"; +/// Generates a new pseudo-random unsigned 256-bit integer x. The algorithm is as follows: if r is the old value of the random seed, considered as a 32-byte array (by constructing the big-endian representation of an unsigned 256-bit integer), then its sha512(r) is computed; the first 32 bytes of this hash are stored as the new value r' of the random seed, and the remaining 32 bytes are returned as the next random value x. +int random() asm "RANDU256"; +/// Generates a new pseudo-random integer z in the range 0..range−1 (or range..−1, if range < 0). More precisely, an unsigned random value x is generated as in random; then z := x * range / 2^256 is computed. +int rand(int range) asm "RAND"; +/// Returns the current random seed as an unsigned 256-bit Integer. +int get_seed() pure asm "RANDSEED"; +/// Sets the random seed to unsigned 256-bit seed. +() set_seed(int) asm "SETRAND"; +/// Mixes unsigned 256-bit integer x into the random seed r by setting the random seed to sha256 of the concatenation of two 32-byte strings: the first with the big-endian representation of the old seed r, and the second with the big-endian representation of x. +() randomize(int x) asm "ADDRAND"; +/// Equivalent to randomize(cur_lt());. +() randomize_lt() asm "LTIME" "ADDRAND"; -;;; Checks whether the data parts of two slices coinside -int equal_slice_bits (slice a, slice b) asm "SDEQ"; +/// Checks whether the data parts of two slices coinside +int equal_slice_bits (slice a, slice b) pure asm "SDEQ"; -;;; Concatenates two builders -builder store_builder(builder to, builder from) asm "STBR"; +/// Concatenates two builders +builder store_builder(builder to, builder from) pure asm "STBR"; diff --git a/lite-client/lite-client.cpp b/lite-client/lite-client.cpp index 1a4201a7..77c9a8c8 100644 --- a/lite-client/lite-client.cpp +++ b/lite-client/lite-client.cpp @@ -926,7 +926,7 @@ bool TestNode::show_help(std::string command) { "saveaccount[code|data] []\tSaves into specified file the most recent state " "(StateInit) or just the code or data of specified account; is in " "[:] format\n" - "runmethod[full] [] ...\tRuns GET method of account " + "runmethod[full] [] ...\tRuns GET method of account " " " "with specified parameters\n" "dnsresolve [] []\tResolves a domain starting from root dns smart contract\n" diff --git a/tolk/CMakeLists.txt b/tolk/CMakeLists.txt index 54aaf8d2..8c890859 100644 --- a/tolk/CMakeLists.txt +++ b/tolk/CMakeLists.txt @@ -24,6 +24,10 @@ target_link_libraries(tolk PUBLIC git ton_crypto) # todo replace with ton_crypt if (WINGETOPT_FOUND) target_link_libraries_system(tolk wingetopt) endif () +if (${TOLK_DEBUG}) # -DTOLK_DEBUG=1 in CMake options => #define TOLK_DEBUG (for development purposes) + message(STATUS "TOLK_DEBUG is ON") + target_compile_definitions(tolk PRIVATE TOLK_DEBUG=1) +endif() if (USE_EMSCRIPTEN) add_executable(tolkfiftlib tolk-wasm.cpp ${TOLK_SOURCE}) diff --git a/tolk/abscode.cpp b/tolk/abscode.cpp index 7dd64bd0..5833c004 100644 --- a/tolk/abscode.cpp +++ b/tolk/abscode.cpp @@ -221,15 +221,6 @@ void VarDescrList::show(std::ostream& os) const { os << " ]\n"; } -void Op::flags_set_clear(int set, int clear) { - flags = (flags | set) & ~clear; - for (auto& op : block0) { - op.flags_set_clear(set, clear); - } - for (auto& op : block1) { - op.flags_set_clear(set, clear); - } -} void Op::split_vars(const std::vector& vars) { split_var_list(left, vars); split_var_list(right, vars); @@ -294,7 +285,7 @@ void Op::show(std::ostream& os, const std::vector& vars, std::string pfx if (noreturn()) { dis += " "; } - if (!is_pure()) { + if (impure()) { dis += " "; } switch (cl) { @@ -467,12 +458,6 @@ void Op::show_block(std::ostream& os, const Op* block, const std::vector os << pfx << "}"; } -void CodeBlob::flags_set_clear(int set, int clear) { - for (auto& op : ops) { - op.flags_set_clear(set, clear); - } -} - std::ostream& operator<<(std::ostream& os, const CodeBlob& code) { code.print(os); return os; diff --git a/tolk/analyzer.cpp b/tolk/analyzer.cpp index ea41a103..ab55a2b6 100644 --- a/tolk/analyzer.cpp +++ b/tolk/analyzer.cpp @@ -360,10 +360,10 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) { case _Tuple: case _UnTuple: { // left = EXEC right; - if (!next_var_info.count_used(left) && is_pure()) { + if (!next_var_info.count_used(left) && !impure()) { // all variables in `left` are not needed if (edit) { - disable(); + set_disabled(); } return std_compute_used_vars(true); } @@ -372,7 +372,7 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) { case _SetGlob: { // GLOB = right if (right.empty() && edit) { - disable(); + set_disabled(); } return std_compute_used_vars(right.empty()); } @@ -399,7 +399,7 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) { } if (!cnt && edit) { // all variables in `left` are not needed - disable(); + set_disabled(); } return set_var_info(std::move(new_var_info)); } @@ -860,15 +860,45 @@ VarDescrList Op::fwd_analyze(VarDescrList values) { } } -bool Op::set_noreturn(bool nr) { - if (nr) { +void Op::set_disabled(bool flag) { + if (flag) { + flags |= _Disabled; + } else { + flags &= ~_Disabled; + } +} + + +bool Op::set_noreturn(bool flag) { + if (flag) { flags |= _NoReturn; } else { flags &= ~_NoReturn; } - return nr; + return flag; } +void Op::set_impure(const CodeBlob &code) { + // todo calling this function with `code` is a bad design (flags are assigned after Op is constructed) + // later it's better to check this somewhere in code.emplace_back() + if (code.flags & CodeBlob::_ForbidImpure) { + throw ParseError(where, "An impure operation in a pure function"); + } + flags |= _Impure; +} + +void Op::set_impure(const CodeBlob &code, bool flag) { + if (flag) { + if (code.flags & CodeBlob::_ForbidImpure) { + throw ParseError(where, "An impure operation in a pure function"); + } + flags |= _Impure; + } else { + flags &= ~_Impure; + } +} + + bool Op::mark_noreturn() { switch (cl) { case _Nop: @@ -888,13 +918,14 @@ bool Op::mark_noreturn() { case _Call: return set_noreturn(next->mark_noreturn()); case _Return: - return set_noreturn(true); + return set_noreturn(); case _If: case _TryCatch: + // note, that & | (not && ||) here and below is mandatory to invoke both left and right calls return set_noreturn((static_cast(block0->mark_noreturn()) & static_cast(block1 && block1->mark_noreturn())) | static_cast(next->mark_noreturn())); case _Again: block0->mark_noreturn(); - return set_noreturn(true); + return set_noreturn(); case _Until: return set_noreturn(static_cast(block0->mark_noreturn()) | static_cast(next->mark_noreturn())); case _While: diff --git a/tolk/asmops.cpp b/tolk/asmops.cpp index cbe268f2..8db75091 100644 --- a/tolk/asmops.cpp +++ b/tolk/asmops.cpp @@ -317,6 +317,9 @@ void AsmOpList::show_var_ext(std::ostream& os, std::pair os << '_' << i; } else { var_names_->at(i).show(os, 2); + // if (!var_names_->at(i).v_type->is_int()) { + // os << '<'; var_names_->at(i).v_type->print(os); os << '>'; + // } } if ((unsigned)j < constants_.size() && constants_[j].not_null()) { os << '=' << constants_[j]; diff --git a/tolk/builtins.cpp b/tolk/builtins.cpp index 16ebd259..6589b9fc 100644 --- a/tolk/builtins.cpp +++ b/tolk/builtins.cpp @@ -26,10 +26,10 @@ using namespace std::literals::string_literals; */ int glob_func_cnt, undef_func_cnt, glob_var_cnt, const_cnt; -std::vector glob_func, glob_vars; +std::vector glob_func, glob_vars, glob_get_methods; std::set prohibited_var_names; -SymDef* predefine_builtin_func(std::string name, TypeExpr* func_type) { +SymDef* define_builtin_func_impl(const std::string& name, SymValAsmFunc* func_val) { if (name.back() == '_') { prohibited_var_names.insert(name); } @@ -42,30 +42,40 @@ SymDef* predefine_builtin_func(std::string name, TypeExpr* func_type) { std::cerr << "fatal: global function `" << name << "` already defined" << std::endl; std::exit(1); } + func_val->flags |= SymValFunc::flagBuiltinFunction; + def->value = func_val; +#ifdef TOLK_DEBUG + dynamic_cast(def->value)->name = name; +#endif return def; } -template -SymDef* define_builtin_func(std::string name, TypeExpr* func_type, const T& func, bool impure = false) { - SymDef* def = predefine_builtin_func(name, func_type); - def->value = new SymValAsmFunc{func_type, func, impure}; - return def; +SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const simple_compile_func_t& func, bool impure = false) { + return define_builtin_func_impl(name, new SymValAsmFunc{func_type, func, !impure}); } -template -SymDef* define_builtin_func(std::string name, TypeExpr* func_type, const T& func, std::initializer_list arg_order, +SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const compile_func_t& func, bool impure = false) { + return define_builtin_func_impl(name, new SymValAsmFunc{func_type, func, !impure}); +} + +SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const AsmOp& macro, bool impure = false) { + return define_builtin_func_impl(name, new SymValAsmFunc{func_type, make_simple_compile(macro), !impure}); +} + +SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const simple_compile_func_t& func, std::initializer_list arg_order, std::initializer_list ret_order = {}, bool impure = false) { - SymDef* def = predefine_builtin_func(name, func_type); - def->value = new SymValAsmFunc{func_type, func, arg_order, ret_order, impure}; - return def; + return define_builtin_func_impl(name, new SymValAsmFunc{func_type, func, arg_order, ret_order, !impure}); } -SymDef* define_builtin_func(std::string name, TypeExpr* func_type, const AsmOp& macro, +SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const compile_func_t& func, std::initializer_list arg_order, + std::initializer_list ret_order = {}, bool impure = false) { + return define_builtin_func_impl(name, new SymValAsmFunc{func_type, func, arg_order, ret_order, !impure}); +} + +SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const AsmOp& macro, std::initializer_list arg_order, std::initializer_list ret_order = {}, bool impure = false) { - SymDef* def = predefine_builtin_func(name, func_type); - def->value = new SymValAsmFunc{func_type, make_simple_compile(macro), arg_order, ret_order, impure}; - return def; + return define_builtin_func_impl(name, new SymValAsmFunc{func_type, make_simple_compile(macro), arg_order, ret_order, !impure}); } SymDef* force_autoapply(SymDef* def) { @@ -262,7 +272,7 @@ int emulate_lshift(int a, int b) { } int t = ((b & VarDescr::_NonZero) ? VarDescr::_Even : 0); t |= b & VarDescr::_Finite; - return emulate_mul(a, VarDescr::_Int | VarDescr::_Pos | VarDescr::_NonZero | VarDescr::_Even | t); + return emulate_mul(a, VarDescr::_Int | VarDescr::_Pos | VarDescr::_NonZero | t); } int emulate_div(int a, int b) { @@ -308,7 +318,7 @@ int emulate_rshift(int a, int b) { } int t = ((b & VarDescr::_NonZero) ? VarDescr::_Even : 0); t |= b & VarDescr::_Finite; - return emulate_div(a, VarDescr::_Int | VarDescr::_Pos | VarDescr::_NonZero | VarDescr::_Even | t); + return emulate_div(a, VarDescr::_Int | VarDescr::_Pos | VarDescr::_NonZero | t); } int emulate_mod(int a, int b, int round_mode = -1) { @@ -1128,9 +1138,9 @@ void define_builtins() { auto Int3 = TypeExpr::new_tensor({Int, Int, Int}); auto TupleInt = TypeExpr::new_tensor({Tuple, Int}); auto SliceInt = TypeExpr::new_tensor({Slice, Int}); - auto X = TypeExpr::new_var(); - auto Y = TypeExpr::new_var(); - auto Z = TypeExpr::new_var(); + auto X = TypeExpr::new_var(0); + auto Y = TypeExpr::new_var(1); + auto Z = TypeExpr::new_var(2); auto XY = TypeExpr::new_tensor({X, Y}); auto arith_bin_op = TypeExpr::new_map(Int2, Int); auto arith_un_op = TypeExpr::new_map(Int, Int); diff --git a/tolk/codegen.cpp b/tolk/codegen.cpp index 504d0b21..64d8fdf0 100644 --- a/tolk/codegen.cpp +++ b/tolk/codegen.cpp @@ -437,6 +437,7 @@ bool Op::generate_code_step(Stack& stack) { if (disabled()) { return true; } + // fun_ref can be nullptr for Op::_CallInd (invoke a variable, not a function) SymValFunc* func = (fun_ref ? dynamic_cast(fun_ref->value) : nullptr); auto arg_order = (func ? func->get_arg_order() : nullptr); auto ret_order = (func ? func->get_ret_order() : nullptr); @@ -486,27 +487,24 @@ bool Op::generate_code_step(Stack& stack) { }; if (cl == _CallInd) { exec_callxargs((int)right.size() - 1, (int)left.size()); + } else if (auto asm_fv = dynamic_cast(fun_ref->value)) { + std::vector res; + res.reserve(left.size()); + for (var_idx_t i : left) { + res.emplace_back(i); + } + asm_fv->compile(stack.o, res, args, where); // compile res := f (args) } else { - auto func = dynamic_cast(fun_ref->value); - if (func) { - std::vector res; - res.reserve(left.size()); - for (var_idx_t i : left) { - res.emplace_back(i); - } - func->compile(stack.o, res, args, where); // compile res := f (args) + auto fv = dynamic_cast(fun_ref->value); + // todo can be fv == nullptr? + std::string name = symbols.get_name(fun_ref->sym_idx); + if (fv && (fv->is_inline() || fv->is_inline_ref())) { + stack.o << AsmOp::Custom(name + " INLINECALLDICT", (int)right.size(), (int)left.size()); + } else if (fv && fv->code && fv->code->require_callxargs) { + stack.o << AsmOp::Custom(name + (" PREPAREDICT"), 0, 2); + exec_callxargs((int)right.size() + 1, (int)left.size()); } else { - auto fv = dynamic_cast(fun_ref->value); - std::string name = symbols.get_name(fun_ref->sym_idx); - bool is_inline = (fv && (fv->flags & 3)); - if (is_inline) { - stack.o << AsmOp::Custom(name + " INLINECALLDICT", (int)right.size(), (int)left.size()); - } else if (fv && fv->code && fv->code->require_callxargs) { - stack.o << AsmOp::Custom(name + (" PREPAREDICT"), 0, 2); - exec_callxargs((int)right.size() + 1, (int)left.size()); - } else { - stack.o << AsmOp::Custom(name + " CALLDICT", (int)right.size(), (int)left.size()); - } + stack.o << AsmOp::Custom(name + " CALLDICT", (int)right.size(), (int)left.size()); } } stack.s.resize(k); diff --git a/tolk/gen-abscode.cpp b/tolk/gen-abscode.cpp index bfce6f0c..a537d99c 100644 --- a/tolk/gen-abscode.cpp +++ b/tolk/gen-abscode.cpp @@ -35,7 +35,7 @@ Expr* Expr::copy() const { return res; } -Expr::Expr(int c, sym_idx_t name_idx, std::initializer_list _arglist) : cls(c), args(std::move(_arglist)) { +Expr::Expr(ExprCls c, sym_idx_t name_idx, std::initializer_list _arglist) : cls(c), args(std::move(_arglist)) { sym = lookup_symbol(name_idx); if (!sym) { } @@ -227,11 +227,11 @@ var_idx_t Expr::new_tmp(CodeBlob& code) const { void add_set_globs(CodeBlob& code, std::vector>& globs, const SrcLocation& here) { for (const auto& p : globs) { auto& op = code.emplace_back(here, Op::_SetGlob, std::vector{}, std::vector{ p.second }, p.first); - op.flags |= Op::_Impure; + op.set_impure(code); } } -std::vector Expr::pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rhs, const SrcLocation& here) { +std::vector pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rhs, const SrcLocation& here) { while (lhs->is_type_apply()) { lhs = lhs->args.at(0); } @@ -247,7 +247,7 @@ std::vector Expr::pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rh auto unpacked_type = rhs->e_type->args.at(0); std::vector tmp{code.create_tmp_var(unpacked_type, &rhs->here)}; code.emplace_back(lhs->here, Op::_UnTuple, tmp, std::move(right)); - auto tvar = new Expr{_Var}; + auto tvar = new Expr{Expr::_Var}; tvar->set_val(tmp[0]); tvar->set_location(rhs->here); tvar->e_type = unpacked_type; @@ -265,43 +265,35 @@ std::vector Expr::pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rh return right; } -std::vector pre_compile_tensor(const std::vector args, CodeBlob &code, - std::vector> *lval_globs, - std::vector arg_order) { - if (arg_order.empty()) { - arg_order.resize(args.size()); - std::iota(arg_order.begin(), arg_order.end(), 0); +std::vector pre_compile_tensor(const std::vector& args, CodeBlob &code, + std::vector> *lval_globs) { + const size_t n = args.size(); + if (n == 0) { // just `()` + return {}; } - tolk_assert(args.size() == arg_order.size()); - std::vector> res_lists(args.size()); + if (n == 1) { // just `(x)`: even if x is modified (e.g. `f(x=x+2)`), there are no next arguments + return args[0]->pre_compile(code, lval_globs); + } + std::vector> res_lists(n); struct ModifiedVar { size_t i, j; - Op* op; + std::unique_ptr* cur_ops; // `LET tmp = v_ij` will be inserted before this }; - auto modified_vars = std::make_shared>(); - for (size_t i : arg_order) { + std::vector modified_vars; + for (size_t i = 0; i < n; ++i) { res_lists[i] = args[i]->pre_compile(code, lval_globs); for (size_t j = 0; j < res_lists[i].size(); ++j) { TmpVar& var = code.vars.at(res_lists[i][j]); - if (code.flags & CodeBlob::_AllowPostModification) { - if (!lval_globs && (var.cls & TmpVar::_Named)) { - Op *op = &code.emplace_back(nullptr, Op::_Let, std::vector(), std::vector()); - op->flags |= Op::_Disabled; - var.on_modification.push_back([modified_vars, i, j, op, done = false](const SrcLocation &here) mutable { - if (!done) { - done = true; - modified_vars->push_back({i, j, op}); - } - }); - } else { - var.on_modification.push_back([](const SrcLocation &) { - }); - } + if (!lval_globs && (var.cls & TmpVar::_Named)) { + var.on_modification.push_back([&modified_vars, i, j, cur_ops = code.cur_ops, done = false](const SrcLocation &here) mutable { + if (!done) { + done = true; + modified_vars.push_back({i, j, cur_ops}); + } + }); } else { - var.on_modification.push_back([name = var.to_string()](const SrcLocation &here) { - throw ParseError{here, PSTRING() << "Modifying local variable " << name - << " after using it in the same expression"}; + var.on_modification.push_back([](const SrcLocation &) { }); } } @@ -312,13 +304,16 @@ std::vector pre_compile_tensor(const std::vector args, CodeBl code.vars.at(v).on_modification.pop_back(); } } - for (const ModifiedVar &m : *modified_vars) { - var_idx_t& v = res_lists[m.i][m.j]; - var_idx_t v2 = code.create_tmp_var(code.vars[v].v_type, code.vars[v].where.get()); - m.op->left = {v2}; - m.op->right = {v}; - m.op->flags &= ~Op::_Disabled; - v = v2; + for (size_t idx = modified_vars.size(); idx--; ) { + const ModifiedVar &m = modified_vars[idx]; + var_idx_t orig_v = res_lists[m.i][m.j]; + var_idx_t tmp_v = code.create_tmp_var(code.vars[orig_v].v_type, code.vars[orig_v].where.get()); + std::unique_ptr op = std::make_unique(*code.vars[orig_v].where, Op::_Let); + op->left = {tmp_v}; + op->right = {orig_v}; + op->next = std::move((*m.cur_ops)); + *m.cur_ops = std::move(op); + res_lists[m.i][m.j] = tmp_v; } std::vector res; for (const auto& list : res_lists) { @@ -334,22 +329,33 @@ std::vector Expr::pre_compile(CodeBlob& code, std::vector(sym->value); std::vector res; - if (func && func->arg_order.size() == args.size() && !(code.flags & CodeBlob::_ComputeAsmLtr)) { - //std::cerr << "!!! reordering " << args.size() << " arguments of " << sym->name() << std::endl; - res = pre_compile_tensor(args, code, lval_globs, func->arg_order); + SymDef* applied_sym = sym; + auto func = dynamic_cast(applied_sym->value); + // replace `beginCell()` with `begin_cell()` + if (func && func->is_just_wrapper_for_another_f()) { + // body is { Op::_Import; Op::_Call; Op::_Return; } + const std::unique_ptr& op_call = dynamic_cast(func)->code->ops->next; + applied_sym = op_call->fun_ref; + // a function may call anotherF with shuffled arguments: f(x,y) { return anotherF(y,x) } + // then op_call looks like (_1,_0), so use op_call->right for correct positions in Op::_Call below + // it's correct, since every argument has width 1 + std::vector res_inner = pre_compile_tensor(args, code, lval_globs); + res.reserve(res_inner.size()); + for (var_idx_t right_idx : op_call->right) { + res.emplace_back(res_inner[right_idx]); + } } else { - res = pre_compile_tensor(args, code, lval_globs, {}); + res = pre_compile_tensor(args, code, lval_globs); } auto rvect = new_tmp_vect(code); - auto& op = code.emplace_back(here, Op::_Call, rvect, std::move(res), sym); + auto& op = code.emplace_back(here, Op::_Call, rvect, res, applied_sym); if (flags & _IsImpure) { - op.flags |= Op::_Impure; + op.set_impure(code); } return rvect; } @@ -362,12 +368,12 @@ std::vector Expr::pre_compile(CodeBlob& code, std::vectorcls == _Glob) { + if (args[0]->cls == _GlobFunc) { auto res = args[1]->pre_compile(code); auto rvect = new_tmp_vect(code); auto& op = code.emplace_back(here, Op::_Call, rvect, std::move(res), args[0]->sym); if (args[0]->flags & _IsImpure) { - op.flags |= Op::_Impure; + op.set_impure(code); } return rvect; } else { @@ -386,8 +392,14 @@ std::vector Expr::pre_compile(CodeBlob& code, std::vector(sym->value)) { + fun_ref->flags |= SymValFunc::flagUsedAsNonCall; + if (!fun_ref->arg_order.empty() || !fun_ref->ret_order.empty()) { + throw ParseError(here, "Saving " + sym->name() + " into a variable will most likely lead to invalid usage, since it changes the order of variables on the stack"); + } + } auto rvect = new_tmp_vect(code); if (lval_globs) { lval_globs->push_back({ sym, rvect[0] }); diff --git a/tolk/keywords.cpp b/tolk/keywords.cpp index db193deb..50d55c41 100644 --- a/tolk/keywords.cpp +++ b/tolk/keywords.cpp @@ -109,10 +109,13 @@ void define_keywords() { .add_keyword("global", Keyword::_Global) .add_keyword("asm", Keyword::_Asm) .add_keyword("impure", Keyword::_Impure) + .add_keyword("pure", Keyword::_Pure) .add_keyword("inline", Keyword::_Inline) .add_keyword("inline_ref", Keyword::_InlineRef) + .add_keyword("builtin", Keyword::_Builtin) .add_keyword("auto_apply", Keyword::_AutoApply) .add_keyword("method_id", Keyword::_MethodId) + .add_keyword("get", Keyword::_Get) .add_keyword("operator", Keyword::_Operator) .add_keyword("infix", Keyword::_Infix) .add_keyword("infixl", Keyword::_Infixl) diff --git a/tolk/lexer.cpp b/tolk/lexer.cpp index f0838f5a..e54c70e4 100644 --- a/tolk/lexer.cpp +++ b/tolk/lexer.cpp @@ -122,8 +122,7 @@ int Lexem::set(std::string _str, const SrcLocation& _loc, int _tp, int _val) { return classify(); } -Lexer::Lexer(SourceReader& _src, bool init, std::string active_chars, std::string eol_cmts, std::string open_cmts, - std::string close_cmts, std::string quote_chars, std::string multiline_quote) +Lexer::Lexer(SourceReader& _src, std::string active_chars, std::string quote_chars, std::string multiline_quote) : src(_src), eof(false), lexem("", src.here(), Lexem::Undefined), peek_lexem("", {}, Lexem::Undefined), multiline_quote(std::move(multiline_quote)) { std::memset(char_class, 0, sizeof(char_class)); @@ -137,17 +136,27 @@ Lexer::Lexer(SourceReader& _src, bool init, std::string active_chars, std::strin char_class[(unsigned)c] |= activity; } } - set_spec(eol_cmt, eol_cmts); - set_spec(cmt_op, open_cmts); - set_spec(cmt_cl, close_cmts); for (int c : quote_chars) { if (c > ' ' && c <= 0x7f) { char_class[(unsigned)c] |= cc::quote_char; } } - if (init) { - next(); - } +} + +void Lexer::set_comment_tokens(const std::string &eol_cmts, const std::string &open_cmts, const std::string &close_cmts) { + set_spec(eol_cmt, eol_cmts); + set_spec(cmt_op, open_cmts); + set_spec(cmt_cl, close_cmts); +} + +void Lexer::set_comment2_tokens(const std::string &eol_cmts2, const std::string &open_cmts2, const std::string &close_cmts2) { + set_spec(eol_cmt2, eol_cmts2); + set_spec(cmt_op2, open_cmts2); + set_spec(cmt_cl2, close_cmts2); +} + +void Lexer::start_parsing() { + next(); } void Lexer::set_spec(std::array& arr, std::string setup) { @@ -202,31 +211,41 @@ const Lexem& Lexer::next() { return lexem.clear(src.here(), Lexem::Eof); } long long comm = 1; + // the code below is very complicated, because it tried to support one-symbol start/end and nesting + // in Tolk, we decided to stop supporting nesting (it was never used in practice and almost impossible for js highlighters) + // later on I'll simplify this code (more precisely, rewrite lexer from scratch) while (!src.seek_eof()) { int cc = src.cur_char(), nc = src.next_char(); - if (cc == eol_cmt[0] || (cc == eol_cmt[1] && nc == eol_cmt[2])) { - src.load_line(); - } else if (cc == cmt_op[1] && nc == cmt_op[2]) { + // note, that in practice, [0]-th element is -256, condition for [0]-th is always false + // todo rewrite this all in the future + if (cc == eol_cmt[0] || (cc == eol_cmt[1] && nc == eol_cmt[2]) || cc == eol_cmt2[0] || (cc == eol_cmt2[1] && nc == eol_cmt2[2])) { + if (comm == 1) { // just "//" — skip a whole line + src.load_line(); + } else { // if "//" is nested into "/*", continue reading, since "*/" may be met + src.advance(1); + } + } else if (cc == cmt_op[1] && nc == cmt_op[2] || cc == cmt_op2[1] && nc == cmt_op2[2]) { src.advance(2); comm = comm * 2 + 1; - } else if (cc == cmt_op[0]) { + } else if (cc == cmt_op[0] || cc == cmt_op2[0]) { // always false src.advance(1); comm *= 2; } else if (comm == 1) { - break; - } else if (cc == cmt_cl[1] && nc == cmt_cl[2]) { - if (!(comm & 1)) { + break; // means that we are not inside a comment + } else if (cc == cmt_cl[1] && nc == cmt_cl[2] || cc == cmt_cl2[1] && nc == cmt_cl2[2]) { + if (!(comm & 1)) { // always false src.error(std::string{"a `"} + (char)cmt_op[0] + "` comment closed by `" + (char)cmt_cl[1] + (char)cmt_cl[2] + "`"); } - comm >>= 1; + // note that {- may be closed with */, but assume it's ok (we'll get rid of {- in the future) + comm = 1; src.advance(2); - } else if (cc == cmt_cl[0]) { + } else if (cc == cmt_cl[0] || cc == cmt_cl2[0]) { // always false if (!(comm & 1)) { src.error(std::string{"a `"} + (char)cmt_op[1] + (char)cmt_op[2] + "` comment closed by `" + (char)cmt_cl[0] + "`"); } - comm >>= 1; + comm = 1; src.advance(1); } else { src.advance(1); @@ -238,11 +257,7 @@ const Lexem& Lexer::next() { if (src.seek_eof()) { eof = true; if (comm > 1) { - if (comm & 1) { - src.error(std::string{"`"} + (char)cmt_op[1] + (char)cmt_op[2] + "` comment extends past end of file"); - } else { - src.error(std::string{"`"} + (char)cmt_op[0] + "` comment extends past end of file"); - } + src.error("comment extends past end of file"); } return lexem.clear(src.here(), Lexem::Eof); } diff --git a/tolk/lexer.h b/tolk/lexer.h index 79d86906..816f7a82 100644 --- a/tolk/lexer.h +++ b/tolk/lexer.h @@ -66,7 +66,8 @@ class Lexer { bool eof; Lexem lexem, peek_lexem; unsigned char char_class[128]; - std::array eol_cmt, cmt_op, cmt_cl; + std::array eol_cmt, cmt_op, cmt_cl; // for ;; {- -} + std::array eol_cmt2, cmt_op2, cmt_cl2; // for // /* */ std::string multiline_quote; enum cc { left_active = 2, right_active = 1, active = 3, allow_repeat = 4, quote_char = 8 }; @@ -74,9 +75,13 @@ class Lexer { bool eof_found() const { return eof; } - Lexer(SourceReader& _src, bool init = false, std::string active_chars = ";,() ~.", std::string eol_cmts = ";;", - std::string open_cmts = "{-", std::string close_cmts = "-}", std::string quote_chars = "\"", - std::string multiline_quote = "\"\"\""); + explicit Lexer(SourceReader& _src, std::string active_chars = ";,() ~.", + std::string quote_chars = "\"", std::string multiline_quote = "\"\"\""); + + void set_comment_tokens(const std::string &eol_cmts, const std::string &open_cmts, const std::string &close_cmts); + void set_comment2_tokens(const std::string &eol_cmts2, const std::string &open_cmts2, const std::string &close_cmts2); + void start_parsing(); + const Lexem& next(); const Lexem& cur() const { return lexem; diff --git a/tolk/parse-tolk.cpp b/tolk/parse-tolk.cpp index 7fffb15a..3cff0bb5 100644 --- a/tolk/parse-tolk.cpp +++ b/tolk/parse-tolk.cpp @@ -48,6 +48,109 @@ inline bool is_special_ident(sym_idx_t idx) { return symbols.get_subclass(idx) != IdSc::undef; } +// given Expr::_Apply (a function call / a variable call), determine whether it's <, or >, or similar +// (an expression `1 < 2` is expressed as `_<_(1,2)`, see builtins.cpp) +static bool is_comparison_binary_op(const Expr* e_apply) { + const std::string& name = e_apply->sym->name(); + const size_t len = name.size(); + if (len < 3 || len > 5 || name[0] != '_' || name[len-1] != '_') { + return false; // not "_<_" and similar + } + + char c1 = name[1]; + char c2 = name[2]; + // < > <= != == >= <=> + return (len == 3 && (c1 == '<' || c1 == '>')) || + (len == 4 && (c1 == '<' || c1 == '>' || c1 == '!' || c1 == '=') && c2 == '=') || + (len == 5 && (c1 == '<' && c2 == '=' && name[3] == '>')); +} + +// same as above, but to detect bitwise operators: & | ^ +// (in Tolk, they are used as logical ones due to absence of a boolean type and && || operators) +static bool is_bitwise_binary_op(const Expr* e_apply) { + const std::string& name = e_apply->sym->name(); + const size_t len = name.size(); + if (len != 3 || name[0] != '_' || name[len-1] != '_') { + return false; + } + + char c1 = name[1]; + return c1 == '&' || c1 == '|' || c1 == '^'; +} + +// same as above, but to detect addition/subtraction +static bool is_add_or_sub_binary_op(const Expr* e_apply) { + const std::string& name = e_apply->sym->name(); + const size_t len = name.size(); + if (len != 3 || name[0] != '_' || name[len-1] != '_') { + return false; + } + + char c1 = name[1]; + return c1 == '+' || c1 == '-'; +} + +static inline std::string get_builtin_operator_name(sym_idx_t sym_builtin) { + std::string underscored = symbols.get_name(sym_builtin); + return underscored.substr(1, underscored.size() - 2); +} + +// fire an error for a case "flags & 0xFF != 0" (equivalent to "flags & 1", probably unexpected) +// it would better be a warning, but we decided to make it a strict error +[[gnu::cold]] static void fire_error_lower_precedence(const SrcLocation& loc, sym_idx_t op_lower, sym_idx_t op_higher) { + std::string name_lower = get_builtin_operator_name(op_lower); + std::string name_higher = get_builtin_operator_name(op_higher); + throw ParseError(loc, name_lower + " has lower precedence than " + name_higher + + ", probably this code won't work as you expected. " + "Use parenthesis: either (... " + name_lower + " ...) to evaluate it first, or (... " + name_higher + " ...) to suppress this error."); +} + +// fire an error for a case "arg1 & arg2 | arg3" +[[gnu::cold]] static void fire_error_mix_bitwise_and_or(const SrcLocation& loc, sym_idx_t op1, sym_idx_t op2) { + std::string name1 = get_builtin_operator_name(op1); + std::string name2 = get_builtin_operator_name(op2); + throw ParseError(loc, "mixing " + name1 + " with " + name2 + " without parenthesis" + ", probably this code won't work as you expected. " + "Use parenthesis to emphasize operator precedence."); +} + +// diagnose when bitwise operators are used in a probably wrong way due to tricky precedence +// example: "flags & 0xFF != 0" is equivalent to "flags & 1", most likely it's unexpected +// the only way to suppress this error for the programmer is to use parenthesis +static void diagnose_bitwise_precedence(const SrcLocation& loc, sym_idx_t bitwise_sym, const Expr* lhs, const Expr* rhs) { + // handle "0 != flags & 0xFF" (lhs = "0 != flags") + if (!lhs->is_inside_parenthesis() && + lhs->cls == Expr::_Apply && lhs->e_type->is_int() && // fast false if 100% not + is_comparison_binary_op(lhs)) { + fire_error_lower_precedence(loc, bitwise_sym, lhs->sym->sym_idx); + // there is a tiny bug: "flags & _!=_(0xFF,0)" will also suggest to wrap rhs into parenthesis + } + + // handle "flags & 0xFF != 0" (rhs = "0xFF != 0") + if (!rhs->is_inside_parenthesis() && + rhs->cls == Expr::_Apply && rhs->e_type->is_int() && + is_comparison_binary_op(rhs)) { + fire_error_lower_precedence(loc, bitwise_sym, rhs->sym->sym_idx); + } + + // handle "arg1 & arg2 | arg3" (lhs = "arg1 & arg2") + if (!lhs->is_inside_parenthesis() && + lhs->cls == Expr::_Apply && lhs->e_type->is_int() && + is_bitwise_binary_op(lhs) && + lhs->sym->sym_idx != bitwise_sym) { + fire_error_mix_bitwise_and_or(loc, lhs->sym->sym_idx, bitwise_sym); + } +} + +// diagnose "a << 8 + 1" (equivalent to "a << 9", probably unexpected) +static void diagnose_addition_in_bitshift(const SrcLocation& loc, sym_idx_t bitshift_sym, const Expr* rhs) { + if (!rhs->is_inside_parenthesis() && + rhs->cls == Expr::_Apply && rhs->e_type->is_int() && + is_add_or_sub_binary_op(rhs)) { + fire_error_lower_precedence(loc, bitshift_sym, rhs->sym->sym_idx); + } +} + /* * * PARSE SOURCE @@ -220,6 +323,9 @@ void parse_global_var_decl(Lexer& lex) { } } else { sym_def->value = new SymValGlobVar{glob_var_cnt++, var_type}; +#ifdef TOLK_DEBUG + dynamic_cast(sym_def->value)->name = lex.cur().str; +#endif glob_vars.push_back(sym_def); } lex.next(); @@ -253,15 +359,9 @@ void parse_const_decl(Lexer& lex) { } lex.next(); CodeBlob code; - if (pragma_allow_post_modification.enabled()) { - code.flags |= CodeBlob::_AllowPostModification; - } - if (pragma_compute_asm_ltr.enabled()) { - code.flags |= CodeBlob::_ComputeAsmLtr; - } // Handles processing and resolution of literals and consts auto x = parse_expr(lex, code, false); // also does lex.next() ! - if (x->flags != Expr::_IsRvalue) { + if (!x->is_rvalue()) { lex.cur().error("expression is not strictly Rvalue"); } if ((wanted_type == Expr::_Const) && (x->cls == Expr::_Apply)) @@ -274,7 +374,7 @@ void parse_const_decl(Lexer& lex) { new_value = new SymValConst{const_cnt++, x->intval}; } else if (x->cls == Expr::_SliceConst) { // Slice constant (string) new_value = new SymValConst{const_cnt++, x->strval}; - } else if (x->cls == Expr::_Apply) { + } else if (x->cls == Expr::_Apply) { // even "1 + 2" is Expr::_Apply (it applies `_+_`) code.emplace_back(loc, Op::_Import, std::vector()); auto tmp_vars = x->pre_compile(code); code.emplace_back(loc, Op::_Return, std::move(tmp_vars)); @@ -372,27 +472,22 @@ void parse_global_var_decls(Lexer& lex) { lex.expect(';'); } -SymValCodeFunc* make_new_glob_func(SymDef* func_sym, TypeExpr* func_type, bool impure = false) { - SymValCodeFunc* res = new SymValCodeFunc{glob_func_cnt, func_type, impure}; +SymValCodeFunc* make_new_glob_func(SymDef* func_sym, TypeExpr* func_type, bool marked_as_pure) { + SymValCodeFunc* res = new SymValCodeFunc{glob_func_cnt, func_type, marked_as_pure}; +#ifdef TOLK_DEBUG + res->name = func_sym->name(); +#endif func_sym->value = res; glob_func.push_back(func_sym); glob_func_cnt++; return res; } -bool check_global_func(const Lexem& cur, sym_idx_t func_name = 0) { - if (!func_name) { - func_name = cur.val; - } +bool check_global_func(const Lexem& cur, sym_idx_t func_name) { SymDef* def = lookup_symbol(func_name); if (!def) { - cur.loc.show_error(std::string{"undefined function `"} + symbols.get_name(func_name) + - "`, defining a global function of unknown type"); - def = define_global_symbol(func_name, 0, cur.loc); - tolk_assert(def && "cannot define global function"); - ++undef_func_cnt; - make_new_glob_func(def, TypeExpr::new_func()); // was: ... ::new_func() - return true; + cur.error("undefined symbol `" + symbols.get_name(func_name) + "`"); + return false; } SymVal* val = dynamic_cast(def->value); if (!val) { @@ -407,8 +502,8 @@ bool check_global_func(const Lexem& cur, sym_idx_t func_name = 0) { } Expr* make_func_apply(Expr* fun, Expr* x) { - Expr* res; - if (fun->cls == Expr::_Glob) { + Expr* res{nullptr}; + if (fun->cls == Expr::_GlobFunc) { if (x->cls == Expr::_Tensor) { res = new Expr{Expr::_Apply, fun->sym, x->args}; } else { @@ -445,6 +540,7 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) { } Expr* res = parse_expr(lex, code, nv); if (lex.tp() == ')') { + res->flags |= Expr::_IsInsideParenthesis; lex.expect(clbr); return res; } @@ -571,7 +667,7 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) { if (t == '_') { Expr* res = new Expr{Expr::_Hole, lex.cur().loc}; res->val = -1; - res->flags = (Expr::_IsLvalue | Expr::_IsHole | Expr::_IsNewVar); + res->flags = Expr::_IsLvalue; res->e_type = TypeExpr::new_hole(); lex.next(); return res; @@ -633,15 +729,16 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) { if (nv) { res->val = ~lex.cur().val; res->e_type = TypeExpr::new_hole(); - res->flags = Expr::_IsLvalue | Expr::_IsNewVar; + res->flags = Expr::_IsLvalue; // std::cerr << "defined new variable " << lex.cur().str << " : " << res->e_type << std::endl; } else { if (!sym) { - check_global_func(lex.cur()); + check_global_func(lex.cur(), lex.cur().val); sym = lookup_symbol(lex.cur().val); } res->sym = sym; SymVal* val = nullptr; + bool impure = false; if (sym) { val = dynamic_cast(sym->value); } @@ -649,8 +746,9 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) { lex.cur().error_at("undefined identifier `", "`"); } else if (val->type == SymVal::_Func) { res->e_type = val->get_type(); - res->cls = Expr::_Glob; + res->cls = Expr::_GlobFunc; auto_apply = val->auto_apply; + impure = !dynamic_cast(val)->is_marked_as_pure(); } else if (val->idx < 0) { lex.cur().error_at("accessing variable `", "` being defined"); } else { @@ -659,7 +757,7 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) { // std::cerr << "accessing variable " << lex.cur().str << " : " << res->e_type << std::endl; } // std::cerr << "accessing symbol " << lex.cur().str << " : " << res->e_type << (val->impure ? " (impure)" : " (pure)") << std::endl; - res->flags = Expr::_IsLvalue | Expr::_IsRvalue | (val->impure ? Expr::_IsImpure : 0); + res->flags = Expr::_IsLvalue | Expr::_IsRvalue | (impure ? Expr::_IsImpure : 0); } if (auto_apply) { int impure = res->flags & Expr::_IsImpure; @@ -750,7 +848,7 @@ Expr* parse_expr80(Lexer& lex, CodeBlob& code, bool nv) { res = new Expr{Expr::_Apply, name, {obj, x}}; } res->here = loc; - res->flags = Expr::_IsRvalue | (val->impure ? Expr::_IsImpure : 0); + res->flags = Expr::_IsRvalue | (val->is_marked_as_pure() ? 0 : Expr::_IsImpure); res->deduce_type(lex.cur()); if (modify) { auto tmp = res; @@ -784,11 +882,11 @@ Expr* parse_expr75(Lexer& lex, CodeBlob& code, bool nv) { } } -// parse E { (* | / | % | /% ) E } +// parse E { (* | / | % | /% | ^/ | ~/ | ^% | ~% ) E } Expr* parse_expr30(Lexer& lex, CodeBlob& code, bool nv) { Expr* res = parse_expr75(lex, code, nv); while (lex.tp() == '*' || lex.tp() == '/' || lex.tp() == '%' || lex.tp() == _DivMod || lex.tp() == _DivC || - lex.tp() == _DivR || lex.tp() == _ModC || lex.tp() == _ModR || lex.tp() == '&') { + lex.tp() == _DivR || lex.tp() == _ModC || lex.tp() == _ModR) { res->chk_rvalue(lex.cur()); int t = lex.tp(); sym_idx_t name = symbols.lookup_add(std::string{"_"} + lex.cur().str + "_"); @@ -806,7 +904,7 @@ Expr* parse_expr30(Lexer& lex, CodeBlob& code, bool nv) { return res; } -// parse [-] E { (+ | - | `|` | ^) E } +// parse [-] E { (+ | -) E } Expr* parse_expr20(Lexer& lex, CodeBlob& code, bool nv) { Expr* res; int t = lex.tp(); @@ -825,7 +923,7 @@ Expr* parse_expr20(Lexer& lex, CodeBlob& code, bool nv) { } else { res = parse_expr30(lex, code, nv); } - while (lex.tp() == '-' || lex.tp() == '+' || lex.tp() == '|' || lex.tp() == '^') { + while (lex.tp() == '-' || lex.tp() == '+') { res->chk_rvalue(lex.cur()); t = lex.tp(); sym_idx_t name = symbols.lookup_add(std::string{"_"} + lex.cur().str + "_"); @@ -843,7 +941,7 @@ Expr* parse_expr20(Lexer& lex, CodeBlob& code, bool nv) { return res; } -// parse E { ( << | >> | >>~ | >>^ ) E } +// parse E { ( << | >> | ~>> | ^>> ) E } Expr* parse_expr17(Lexer& lex, CodeBlob& code, bool nv) { Expr* res = parse_expr20(lex, code, nv); while (lex.tp() == _Lshift || lex.tp() == _Rshift || lex.tp() == _RshiftC || lex.tp() == _RshiftR) { @@ -855,6 +953,7 @@ Expr* parse_expr17(Lexer& lex, CodeBlob& code, bool nv) { lex.next(); auto x = parse_expr20(lex, code, false); x->chk_rvalue(lex.cur()); + diagnose_addition_in_bitshift(loc, name, x); res = new Expr{Expr::_Apply, name, {res, x}}; res->here = loc; res->set_val(t); @@ -886,9 +985,33 @@ Expr* parse_expr15(Lexer& lex, CodeBlob& code, bool nv) { return res; } +// parse E { ( & | `|` | ^ ) E } +Expr* parse_expr14(Lexer& lex, CodeBlob& code, bool nv) { + Expr* res = parse_expr15(lex, code, nv); + while (lex.tp() == '&' || lex.tp() == '|' || lex.tp() == '^') { + res->chk_rvalue(lex.cur()); + int t = lex.tp(); + sym_idx_t name = symbols.lookup_add(std::string{"_"} + lex.cur().str + "_"); + check_global_func(lex.cur(), name); + SrcLocation loc{lex.cur().loc}; + lex.next(); + auto x = parse_expr15(lex, code, false); + x->chk_rvalue(lex.cur()); + // diagnose tricky bitwise precedence, like "flags & 0xFF != 0" (& has lower precedence) + diagnose_bitwise_precedence(loc, name, res, x); + + res = new Expr{Expr::_Apply, name, {res, x}}; + res->here = loc; + res->set_val(t); + res->flags = Expr::_IsRvalue; + res->deduce_type(lex.cur()); + } + return res; +} + // parse E [ ? E : E ] Expr* parse_expr13(Lexer& lex, CodeBlob& code, bool nv) { - Expr* res = parse_expr15(lex, code, nv); + Expr* res = parse_expr14(lex, code, nv); if (lex.tp() == '?') { res->chk_rvalue(lex.cur()); SrcLocation loc{lex.cur().loc}; @@ -1207,14 +1330,11 @@ blk_fl::val parse_stmt(Lexer& lex, CodeBlob& code) { } } -CodeBlob* parse_func_body(Lexer& lex, FormalArgList arg_list, TypeExpr* ret_type) { +CodeBlob* parse_func_body(Lexer& lex, FormalArgList arg_list, TypeExpr* ret_type, bool marked_as_pure) { lex.expect('{'); CodeBlob* blob = new CodeBlob{ret_type}; - if (pragma_allow_post_modification.enabled()) { - blob->flags |= CodeBlob::_AllowPostModification; - } - if (pragma_compute_asm_ltr.enabled()) { - blob->flags |= CodeBlob::_ComputeAsmLtr; + if (marked_as_pure) { + blob->flags |= CodeBlob::_ForbidImpure; } blob->import_params(std::move(arg_list)); blk_fl::val res = blk_fl::init; @@ -1235,7 +1355,7 @@ CodeBlob* parse_func_body(Lexer& lex, FormalArgList arg_list, TypeExpr* ret_type } SymValAsmFunc* parse_asm_func_body(Lexer& lex, TypeExpr* func_type, const FormalArgList& arg_list, TypeExpr* ret_type, - bool impure = false) { + bool marked_as_pure) { auto loc = lex.cur().loc; lex.expect(_Asm); int cnt = (int)arg_list.size(); @@ -1339,14 +1459,14 @@ SymValAsmFunc* parse_asm_func_body(Lexer& lex, TypeExpr* func_type, const Formal for (const AsmOp& asm_op : asm_ops) { crc_s += asm_op.op; } - crc_s.push_back(impure); + crc_s.push_back(!marked_as_pure); for (const int& x : arg_order) { crc_s += std::string((const char*) (&x), (const char*) (&x + 1)); } for (const int& x : ret_order) { crc_s += std::string((const char*) (&x), (const char*) (&x + 1)); } - auto res = new SymValAsmFunc{func_type, asm_ops, impure}; + auto res = new SymValAsmFunc{func_type, std::move(asm_ops), marked_as_pure}; res->arg_order = std::move(arg_order); res->ret_order = std::move(ret_order); res->crc = td::crc64(crc_s); @@ -1420,12 +1540,90 @@ TypeExpr* compute_type_closure(TypeExpr* expr, const std::vector& typ return expr; } +// if a function looks like `T f(...args) { return anotherF(...args); }`, +// set a bit to flags +// then, all calls to `f(...)` will be effectively replaced with `anotherF(...)` +void detect_if_function_just_wraps_another(SymValCodeFunc* v_current, const td::RefInt256 &method_id) { + const std::string& function_name = v_current->code->name; + + // in "AST" representation, the first is Op::_Import (input arguments, even if none) + const auto& op_import = v_current->code->ops; + tolk_assert(op_import && op_import->cl == Op::_Import); + + // then Op::_Call (anotherF) + const Op* op_call = op_import->next.get(); + if (!op_call || op_call->cl != Op::_Call) + return; + tolk_assert(op_call->left.size() == 1); + + const auto& op_return = op_call->next; + if (!op_return || op_return->cl != Op::_Return || op_return->left.size() != 1) + return; + + bool indices_expected = static_cast(op_import->left.size()) == op_call->left[0] && op_call->left[0] == op_return->left[0]; + if (!indices_expected) + return; + + const SymDef* f_called = op_call->fun_ref; + const SymValFunc* v_called = dynamic_cast(f_called->value); + if (!v_called) + return; + + // `return` must use all arguments, e.g. `return (_0,_2,_1)`, not `return (_0,_1,_1)` + int args_used_mask = 0; + for (var_idx_t arg_idx : op_call->right) { + args_used_mask |= 1 << arg_idx; + } + if (args_used_mask != (1 << op_call->right.size()) - 1) + return; + + // detect getters (having method_id), they should not be treated as wrappers + // v_current->method_id will be assigned later; todo refactor function parsing completely, it's weird + // moreover, `recv_external()` and others are also exported, but FunC is unaware of method_id + // (it's assigned by Fift later) + // so, for now, just handle "special" function names, the same as in Asm.fif + if (!method_id.is_null()) + return; + if (function_name == "main" || function_name == "recv_internal" || function_name == "recv_external" || + function_name == "run_ticktock" || function_name == "split_prepare" || function_name == "split_install") + return; + + // all types must be strictly defined (on mismatch, a compilation error will be triggered anyway) + if (v_called->sym_type->has_unknown_inside() || v_current->sym_type->has_unknown_inside()) + return; + // avoid situations like `f(int a, (int,int) b)`, inlining will be cumbersome + if (v_current->get_arg_type()->get_width() != static_cast(op_call->right.size())) + return; + // 'return true;' (false, nil) are (surprisingly) also function calls, with auto_apply=true + if (v_called->auto_apply) + return; + // if an original is marked `pure`, and this one doesn't, it's okay; just check for inline_ref storage + if (v_current->is_inline_ref()) + return; + + // ok, f_current is a wrapper + v_current->flags |= SymValFunc::flagWrapsAnotherF; + if (verbosity >= 2) { + std::cerr << function_name << " -> " << f_called->name() << std::endl; + } +} + +static td::RefInt256 calculate_method_id_by_func_name(const std::string &func_name) { + unsigned int crc = td::crc16(func_name); + return td::make_refint((crc & 0xffff) | 0x10000); +} + +// todo rewrite function declaration parsing completely, it's weird void parse_func_def(Lexer& lex) { SrcLocation loc{lex.cur().loc}; open_scope(lex); std::vector type_vars; + bool is_get_method = false; if (lex.tp() == _Forall) { type_vars = parse_type_var_list(lex); + } else if (lex.tp() == _Get) { + is_get_method = true; + lex.next(); } auto ret_type = parse_type(lex); if (lex.tp() != _Ident) { @@ -1434,47 +1632,80 @@ void parse_func_def(Lexer& lex) { Lexem func_name = lex.cur(); lex.next(); FormalArgList arg_list = parse_formal_args(lex); - bool impure = (lex.tp() == _Impure); - if (impure) { + bool marked_as_pure = false; + if (lex.tp() == _Impure) { + static bool warning_shown = false; + if (!warning_shown) { + lex.cur().loc.show_warning("`impure` specifier is deprecated. All functions are impure by default, use `pure` to mark a function as pure"); + warning_shown = true; + } + lex.next(); + } else if (lex.tp() == _Pure) { + marked_as_pure = true; lex.next(); } - int f = 0; - if (lex.tp() == _Inline || lex.tp() == _InlineRef) { - f = (lex.tp() == _Inline) ? 1 : 2; + int flags_inline = 0; + if (lex.tp() == _Inline) { + flags_inline = SymValFunc::flagInline; + lex.next(); + } else if (lex.tp() == _InlineRef) { + flags_inline = SymValFunc::flagInlineRef; lex.next(); } td::RefInt256 method_id; - std::string method_name; if (lex.tp() == _MethodId) { + if (is_get_method) { + lex.cur().error("both `get` and `method_id` are not allowed"); + } lex.next(); - if (lex.tp() == '(') { + if (lex.tp() == '(') { // method_id(N) lex.expect('('); - if (lex.tp() == Lexem::String) { - method_name = lex.cur().str; - } else if (lex.tp() == Lexem::Number) { - method_name = lex.cur().str; - method_id = td::string_to_int256(method_name); - if (method_id.is_null()) { - lex.cur().error_at("invalid integer constant `", "`"); - } - } else { - throw ParseError{lex.cur().loc, "integer or string method identifier expected"}; + method_id = td::string_to_int256(lex.cur().str); + lex.expect(Lexem::Number); + if (method_id.is_null()) { + lex.cur().error_at("invalid integer constant `", "`"); } - lex.next(); lex.expect(')'); } else { - method_name = func_name.str; - } - if (method_id.is_null()) { - unsigned crc = td::crc16(method_name); - method_id = td::make_refint((crc & 0xffff) | 0x10000); + static bool warning_shown = false; + if (!warning_shown) { + lex.cur().loc.show_warning("`method_id` specifier is deprecated, use `get` keyword.\nExample: `get int seqno() { ... }`"); + warning_shown = true; + } + method_id = calculate_method_id_by_func_name(func_name.str); } } - if (lex.tp() != ';' && lex.tp() != '{' && lex.tp() != _Asm) { - lex.expect('{', "function body block expected"); + if (is_get_method) { + tolk_assert(method_id.is_null()); + method_id = calculate_method_id_by_func_name(func_name.str); + for (const SymDef* other : glob_get_methods) { + if (!td::cmp(dynamic_cast(other->value)->method_id, method_id)) { + lex.cur().error(PSTRING() << "GET methods hash collision: `" << other->name() << "` and `" + func_name.str + "` produce the same hash. Consider renaming one of these functions."); + } + } } TypeExpr* func_type = TypeExpr::new_map(extract_total_arg_type(arg_list), ret_type); func_type = compute_type_closure(func_type, type_vars); + if (lex.tp() == _Builtin) { + const SymDef* builtin_func = lookup_symbol(func_name.str); + const SymValFunc* func_val = builtin_func ? dynamic_cast(builtin_func->value) : nullptr; + if (!func_val || !func_val->is_builtin()) { + lex.cur().error("`builtin` used for non-builtin function"); + } +#ifdef TOLK_DEBUG + // in release, we don't need this check, since `builtin` is used only in stdlib.tolk, which is our responsibility + if (!func_val->sym_type->equals_to(func_type) || func_val->is_marked_as_pure() != marked_as_pure) { + lex.cur().error("declaration for `builtin` function doesn't match an actual one"); + } +#endif + lex.next(); + lex.expect(';'); + close_scope(lex); + return; + } + if (lex.tp() != ';' && lex.tp() != '{' && lex.tp() != _Asm) { + lex.expect('{', "function body block"); + } if (verbosity >= 1) { std::cerr << "function " << func_name.str << " : " << func_type << std::endl; } @@ -1495,7 +1726,7 @@ void parse_func_def(Lexer& lex) { } } if (lex.tp() == ';') { - make_new_glob_func(func_sym, func_type, impure); + make_new_glob_func(func_sym, func_type, marked_as_pure); lex.next(); } else if (lex.tp() == '{') { if (dynamic_cast(func_sym_val)) { @@ -1508,19 +1739,26 @@ void parse_func_def(Lexer& lex) { lex.cur().error("function `"s + func_name.str + "` has been already defined in an yet-unknown way"); } } else { - func_sym_code = make_new_glob_func(func_sym, func_type, impure); + func_sym_code = make_new_glob_func(func_sym, func_type, marked_as_pure); } if (func_sym_code->code) { lex.cur().error("redefinition of function `"s + func_name.str + "`"); } - CodeBlob* code = parse_func_body(lex, arg_list, ret_type); + if (marked_as_pure && ret_type->get_width() == 0) { + lex.cur().error("a pure function should return something, otherwise it will be optimized out anyway"); + } + CodeBlob* code = parse_func_body(lex, arg_list, ret_type, marked_as_pure); code->name = func_name.str; code->loc = loc; // code->print(std::cerr); // !!!DEBUG!!! func_sym_code->code = code; + detect_if_function_just_wraps_another(func_sym_code, method_id); } else { Lexem asm_lexem = lex.cur(); - SymValAsmFunc* asm_func = parse_asm_func_body(lex, func_type, arg_list, ret_type, impure); + SymValAsmFunc* asm_func = parse_asm_func_body(lex, func_type, arg_list, ret_type, marked_as_pure); +#ifdef TOLK_DEBUG + asm_func->name = func_name.str; +#endif if (func_sym_val) { if (dynamic_cast(func_sym_val)) { asm_lexem.error("function `"s + func_name.str + "` was already declared as an ordinary function"); @@ -1537,7 +1775,7 @@ void parse_func_def(Lexer& lex) { func_sym->value = asm_func; } if (method_id.not_null()) { - auto val = dynamic_cast(func_sym->value); + auto val = dynamic_cast(func_sym->value); if (!val) { lex.cur().error("cannot set method id for unknown function `"s + func_name.str + "`"); } @@ -1548,17 +1786,25 @@ void parse_func_def(Lexer& lex) { val->method_id->to_dec_string() + " to a different value " + method_id->to_dec_string()); } } - if (f) { - auto val = dynamic_cast(func_sym->value); + if (flags_inline) { + auto val = dynamic_cast(func_sym->value); if (!val) { lex.cur().error("cannot set unknown function `"s + func_name.str + "` as an inline"); } - if (!(val->flags & 3)) { - val->flags = (short)(val->flags | f); - } else if ((val->flags & 3) != f) { + if (!val->is_inline() && !val->is_inline_ref()) { + val->flags |= flags_inline; + } else if ((val->flags & (SymValFunc::flagInline | SymValFunc::flagInlineRef)) != flags_inline) { lex.cur().error("inline mode for `"s + func_name.str + "` changed with respect to a previous declaration"); } } + if (is_get_method) { + auto val = dynamic_cast(func_sym->value); + if (!val) { + lex.cur().error("cannot set unknown function `"s + func_name.str + "` as a get method"); + } + val->flags |= SymValFunc::flagGetMethod; + glob_get_methods.push_back(func_sym); + } if (verbosity >= 1) { std::cerr << "new type of function " << func_name.str << " : " << func_type << std::endl; } @@ -1697,6 +1943,8 @@ void parse_pragma(Lexer& lex) { pragma_allow_post_modification.enable(lex.cur().loc); } else if (pragma_name == pragma_compute_asm_ltr.name()) { pragma_compute_asm_ltr.enable(lex.cur().loc); + } else if (pragma_name == pragma_remove_unused_functions.name()) { + pragma_remove_unused_functions.enable(lex.cur().loc); } else { lex.cur().error(std::string{"unknown pragma `"} + pragma_name + "`"); } @@ -1728,7 +1976,12 @@ void parse_include(Lexer& lex, const FileDescr* fdescr) { bool parse_source(std::istream* is, FileDescr* fdescr) { SourceReader reader{is, fdescr}; - Lexer lex{reader, true, ";,()[] ~."}; + Lexer lex{reader, ";,()[] ~."}; + // previously, FunC had lisp-style comments, + // but Tolk supports traditional (slash) comments alongside (lisp-style will be deleted soon) + lex.set_comment_tokens(";;", "{-", "-}"); + lex.set_comment2_tokens("//", "/*", "*/"); + lex.start_parsing(); while (lex.tp() != _Eof) { if (lex.tp() == _PragmaHashtag) { parse_pragma(lex); diff --git a/tolk/symtable.cpp b/tolk/symtable.cpp index ea2a1f91..ee6d6aca 100644 --- a/tolk/symtable.cpp +++ b/tolk/symtable.cpp @@ -149,7 +149,11 @@ SymDef* define_global_symbol(sym_idx_t name_idx, bool force_new, const SrcLocati if (found) { return force_new && found->value ? nullptr : found; } - return global_sym_def[name_idx] = new SymDef(0, name_idx, loc); + found = global_sym_def[name_idx] = new SymDef(0, name_idx, loc); +#ifdef TOLK_DEBUG + found->sym_name = found->name(); +#endif + return found; } SymDef* define_symbol(sym_idx_t name_idx, bool force_new, const SrcLocation& loc) { @@ -173,6 +177,10 @@ SymDef* define_symbol(sym_idx_t name_idx, bool force_new, const SrcLocation& loc } found = sym_def[name_idx] = new SymDef(scope_level, name_idx, loc); symbol_stack.push_back(std::make_pair(scope_level, SymDef{0, name_idx})); +#ifdef TOLK_DEBUG + found->sym_name = found->name(); + symbol_stack.back().second.sym_name = found->name(); +#endif return found; } diff --git a/tolk/symtable.h b/tolk/symtable.h index c0a0912a..68a4a1da 100644 --- a/tolk/symtable.h +++ b/tolk/symtable.h @@ -148,6 +148,9 @@ struct SymDef { sym_idx_t sym_idx; SymValBase* value; SrcLocation loc; +#ifdef TOLK_DEBUG + std::string sym_name; +#endif SymDef(int lvl, sym_idx_t idx, const SrcLocation& _loc = {}, SymValBase* val = 0) : level(lvl), sym_idx(idx), value(val), loc(_loc) { } diff --git a/tolk/tolk-wasm.cpp b/tolk/tolk-wasm.cpp index a7ca37b4..6ffc798e 100644 --- a/tolk/tolk-wasm.cpp +++ b/tolk/tolk-wasm.cpp @@ -31,6 +31,7 @@ #include "td/utils/Status.h" #include #include +#include "vm/boc.h" td::Result compile_internal(char *config_json) { TRY_RESULT(input_json, td::json_decode(td::MutableSlice(config_json))) diff --git a/tolk/tolk.cpp b/tolk/tolk.cpp index eb15155a..1b8a17a1 100644 --- a/tolk/tolk.cpp +++ b/tolk/tolk.cpp @@ -38,9 +38,74 @@ bool stack_layout_comments, op_rewrite_comments, program_envelope, asm_preamble; bool interactive = false; GlobalPragma pragma_allow_post_modification{"allow-post-modification"}; GlobalPragma pragma_compute_asm_ltr{"compute-asm-ltr"}; +GlobalPragma pragma_remove_unused_functions{"remove-unused-functions"}; std::string generated_from, boc_output_filename; ReadCallback::Callback read_callback; +// returns argument type of a function +// note, that when a function has multiple arguments, its arg type is a tensor (no arguments — an empty tensor) +// in other words, `f(int a, int b)` and `f((int,int) ab)` is the same when we speak about types +const TypeExpr *SymValFunc::get_arg_type() const { + if (!sym_type) + return nullptr; + + tolk_assert(sym_type->constr == TypeExpr::te_Map || sym_type->constr == TypeExpr::te_ForAll); + const TypeExpr *te_map = sym_type->constr == TypeExpr::te_ForAll ? sym_type->args[0] : sym_type; + const TypeExpr *arg_type = te_map->args[0]; + + while (arg_type->constr == TypeExpr::te_Indirect) { + arg_type = arg_type->args[0]; + } + return arg_type; +} + + +bool SymValCodeFunc::does_need_codegen() const { + // when a function is declared, but not referenced from code in any way, don't generate its body + if (!is_really_used && pragma_remove_unused_functions.enabled()) { + return false; + } + // when a function is referenced like `var a = some_fn;` (or in some other non-call way), its continuation should exist + if (flags & flagUsedAsNonCall) { + return true; + } + // when a function f() is just `return anotherF(...args)`, it doesn't need to be codegenerated at all, + // since all its usages are inlined + return !is_just_wrapper_for_another_f(); + // in the future, we may want to implement a true AST inlining for `inline` functions also +} + +void GlobalPragma::enable(SrcLocation loc) { + if (deprecated_from_v_) { + loc.show_warning(PSTRING() << "#pragma " << name_ << + " is deprecated since Tolk v" << deprecated_from_v_ << + ". Please, remove this line from your code."); + return; + } + + enabled_ = true; + locs_.push_back(std::move(loc)); +} + +void GlobalPragma::check_enable_in_libs() { + if (locs_.empty()) { + return; + } + for (const SrcLocation& loc : locs_) { + if (loc.fdescr->is_main) { + return; + } + } + locs_[0].show_warning(PSTRING() << "#pragma " << name_ + << " is enabled in included libraries, it may change the behavior of your code. " + << "Add this #pragma to the main source file to suppress this warning."); +} + +void GlobalPragma::always_on_and_deprecated(const char *deprecated_from_v) { + deprecated_from_v_ = deprecated_from_v; + enabled_ = true; +} + td::Result fs_read_callback(ReadCallback::Kind kind, const char* query) { switch (kind) { case ReadCallback::Kind::ReadFile: { @@ -62,6 +127,55 @@ td::Result fs_read_callback(ReadCallback::Kind kind, const char* qu } } +void mark_function_used_dfs(const std::unique_ptr& op); + +void mark_function_used(SymValCodeFunc* func_val) { + if (!func_val->code || func_val->is_really_used) { // already handled + return; + } + + func_val->is_really_used = true; + mark_function_used_dfs(func_val->code->ops); +} + +void mark_global_var_used(SymValGlobVar* glob_val) { + glob_val->is_really_used = true; +} + +void mark_function_used_dfs(const std::unique_ptr& op) { + if (!op) { + return; + } + // op->fun_ref, despite its name, may actually ref global var + // note, that for non-calls, e.g. `var a = some_fn` (Op::_Let), some_fn is Op::_GlobVar + // (in other words, fun_ref exists not only for direct Op::_Call, but for non-call references also) + if (op->fun_ref) { + if (auto* func_val = dynamic_cast(op->fun_ref->value)) { + mark_function_used(func_val); + } else if (auto* glob_val = dynamic_cast(op->fun_ref->value)) { + mark_global_var_used(glob_val); + } else if (auto* asm_val = dynamic_cast(op->fun_ref->value)) { + } else { + tolk_assert(false); + } + } + mark_function_used_dfs(op->next); + mark_function_used_dfs(op->block0); + mark_function_used_dfs(op->block1); +} + +void mark_used_symbols() { + for (SymDef* func_sym : glob_func) { + auto* func_val = dynamic_cast(func_sym->value); + std::string name = symbols.get_name(func_sym->sym_idx); + if (func_val->method_id.not_null() || + name == "main" || name == "recv_internal" || name == "recv_external" || + name == "run_ticktock" || name == "split_prepare" || name == "split_install") { + mark_function_used(func_val); + } + } +} + /* * * OUTPUT CODE GENERATOR @@ -76,8 +190,7 @@ void generate_output_func(SymDef* func_sym, std::ostream &outs, std::ostream &er errs << "\n\n=========================\nfunction " << name << " : " << func_val->get_type() << std::endl; } if (!func_val->code) { - errs << "( function `" << name << "` undefined )\n"; - throw ParseError(func_sym->loc, name); + throw ParseError(func_sym->loc, "function `" + name + "` is just declared, not implemented"); } else { CodeBlob& code = *(func_val->code); if (verbosity >= 3) { @@ -122,12 +235,10 @@ void generate_output_func(SymDef* func_sym, std::ostream &outs, std::ostream &er if (verbosity >= 2) { errs << "\n---------- resulting code for " << name << " -------------\n"; } - bool inline_func = (func_val->flags & 1); - bool inline_ref = (func_val->flags & 2); const char* modifier = ""; - if (inline_func) { + if (func_val->is_inline()) { modifier = "INLINE"; - } else if (inline_ref) { + } else if (func_val->is_inline_ref()) { modifier = "REF"; } outs << std::string(indent * 2, ' ') << name << " PROC" << modifier << ":<{\n"; @@ -138,12 +249,10 @@ void generate_output_func(SymDef* func_sym, std::ostream &outs, std::ostream &er if (opt_level < 2) { mode |= Stack::_DisableOpt; } - auto fv = dynamic_cast(func_sym->value); - // Flags: 1 - inline, 2 - inline_ref - if (fv && (fv->flags & 1) && code.ops->noreturn()) { + if (func_val->is_inline() && code.ops->noreturn()) { mode |= Stack::_InlineFunc; } - if (fv && (fv->flags & 3)) { + if (func_val->is_inline() || func_val->is_inline_ref()) { mode |= Stack::_InlineAny; } code.generate_code(outs, mode, indent + 1); @@ -162,9 +271,17 @@ int generate_output(std::ostream &outs, std::ostream &errs) { if (program_envelope) { outs << "PROGRAM{\n"; } + mark_used_symbols(); for (SymDef* func_sym : glob_func) { SymValCodeFunc* func_val = dynamic_cast(func_sym->value); tolk_assert(func_val); + if (!func_val->does_need_codegen()) { + if (verbosity >= 2) { + errs << func_sym->name() << ": code not generated, function does not need codegen\n"; + } + continue; + } + std::string name = symbols.get_name(func_sym->sym_idx); outs << std::string(indent * 2, ' '); if (func_val->method_id.is_null()) { @@ -174,12 +291,23 @@ int generate_output(std::ostream &outs, std::ostream &errs) { } } for (SymDef* gvar_sym : glob_vars) { - tolk_assert(dynamic_cast(gvar_sym->value)); + auto* glob_val = dynamic_cast(gvar_sym->value); + tolk_assert(glob_val); + if (!glob_val->is_really_used && pragma_remove_unused_functions.enabled()) { + if (verbosity >= 2) { + errs << gvar_sym->name() << ": variable not generated, it's unused\n"; + } + continue; + } std::string name = symbols.get_name(gvar_sym->sym_idx); outs << std::string(indent * 2, ' ') << "DECLGLOBVAR " << name << "\n"; } int errors = 0; for (SymDef* func_sym : glob_func) { + SymValCodeFunc* func_val = dynamic_cast(func_sym->value); + if (!func_val->does_need_codegen()) { + continue; + } try { generate_output_func(func_sym, outs, errs); } catch (Error& err) { @@ -217,6 +345,8 @@ int tolk_proceed(const std::vector &sources, std::ostream &outs, st define_keywords(); define_builtins(); + pragma_allow_post_modification.always_on_and_deprecated("0.5.0"); + pragma_compute_asm_ltr.always_on_and_deprecated("0.5.0"); int ok = 0, proc = 0; try { @@ -235,8 +365,7 @@ int tolk_proceed(const std::vector &sources, std::ostream &outs, st if (!proc) { throw Fatal{"no source files, no output"}; } - pragma_allow_post_modification.check_enable_in_libs(); - pragma_compute_asm_ltr.check_enable_in_libs(); + pragma_remove_unused_functions.check_enable_in_libs(); return generate_output(outs, errs); } catch (Fatal& fatal) { errs << "fatal: " << fatal << std::endl; diff --git a/tolk/tolk.h b/tolk/tolk.h index 66441099..9086620b 100644 --- a/tolk/tolk.h +++ b/tolk/tolk.h @@ -106,12 +106,15 @@ enum Keyword { _Forall, _Asm, _Impure, + _Pure, _Global, _Extern, _Inline, _InlineRef, + _Builtin, _AutoApply, _MethodId, + _Get, _Operator, _Infix, _Infixl, @@ -147,8 +150,8 @@ class IdSc { */ struct TypeExpr { - enum te_type { te_Unknown, te_Var, te_Indirect, te_Atomic, te_Tensor, te_Tuple, te_Map, te_Type, te_ForAll } constr; - enum { + enum te_type { te_Unknown, te_Var, te_Indirect, te_Atomic, te_Tensor, te_Tuple, te_Map, te_ForAll } constr; + enum AtomicType { _Int = Keyword::_Int, _Cell = Keyword::_Cell, _Slice = Keyword::_Slice, @@ -214,9 +217,11 @@ struct TypeExpr { void compute_width(); bool recompute_width(); void show_width(std::ostream& os); - std::ostream& print(std::ostream& os, int prio = 0); + std::ostream& print(std::ostream& os, int prio = 0) const; void replace_with(TypeExpr* te2); int extract_components(std::vector& comp_list); + bool equals_to(const TypeExpr* rhs) const; + bool has_unknown_inside() const; static int holes, type_vars; static TypeExpr* new_hole() { return new TypeExpr{te_Unknown, ++holes}; @@ -528,7 +533,7 @@ class ListIterator { struct Stack; struct Op { - enum { + enum OpKind { _Undef, _Nop, _Call, @@ -547,13 +552,13 @@ struct Op { _Repeat, _Again, _TryCatch, - _SliceConst + _SliceConst, }; - int cl; - enum { _Disabled = 1, _Reachable = 2, _NoReturn = 4, _ImpureR = 8, _ImpureW = 16, _Impure = 24 }; + OpKind cl; + enum { _Disabled = 1, _NoReturn = 4, _Impure = 24 }; int flags; std::unique_ptr next; - SymDef* fun_ref; + SymDef* fun_ref; // despite its name, it may actually ref global var; applicable not only to Op::_Call, but for other kinds also SrcLocation where; VarDescrList var_info; std::vector args; @@ -561,41 +566,41 @@ struct Op { std::unique_ptr block0, block1; td::RefInt256 int_const; std::string str_const; - Op(const SrcLocation& _where = {}, int _cl = _Undef) : cl(_cl), flags(0), fun_ref(nullptr), where(_where) { + Op(const SrcLocation& _where = {}, OpKind _cl = _Undef) : cl(_cl), flags(0), fun_ref(nullptr), where(_where) { } - Op(const SrcLocation& _where, int _cl, const std::vector& _left) + Op(const SrcLocation& _where, OpKind _cl, const std::vector& _left) : cl(_cl), flags(0), fun_ref(nullptr), where(_where), left(_left) { } - Op(const SrcLocation& _where, int _cl, std::vector&& _left) + Op(const SrcLocation& _where, OpKind _cl, std::vector&& _left) : cl(_cl), flags(0), fun_ref(nullptr), where(_where), left(std::move(_left)) { } - Op(const SrcLocation& _where, int _cl, const std::vector& _left, td::RefInt256 _const) + Op(const SrcLocation& _where, OpKind _cl, const std::vector& _left, td::RefInt256 _const) : cl(_cl), flags(0), fun_ref(nullptr), where(_where), left(_left), int_const(_const) { } - Op(const SrcLocation& _where, int _cl, const std::vector& _left, std::string _const) + Op(const SrcLocation& _where, OpKind _cl, const std::vector& _left, std::string _const) : cl(_cl), flags(0), fun_ref(nullptr), where(_where), left(_left), str_const(_const) { } - Op(const SrcLocation& _where, int _cl, const std::vector& _left, const std::vector& _right, + Op(const SrcLocation& _where, OpKind _cl, const std::vector& _left, const std::vector& _right, SymDef* _fun = nullptr) : cl(_cl), flags(0), fun_ref(_fun), where(_where), left(_left), right(_right) { } - Op(const SrcLocation& _where, int _cl, std::vector&& _left, std::vector&& _right, + Op(const SrcLocation& _where, OpKind _cl, std::vector&& _left, std::vector&& _right, SymDef* _fun = nullptr) : cl(_cl), flags(0), fun_ref(_fun), where(_where), left(std::move(_left)), right(std::move(_right)) { } - bool disabled() const { - return flags & _Disabled; - } - bool enabled() const { - return !disabled(); - } - void disable() { - flags |= _Disabled; - } - bool unreachable() { - return !(flags & _Reachable); - } - void flags_set_clear(int set, int clear); + + bool disabled() const { return flags & _Disabled; } + void set_disabled() { flags |= _Disabled; } + void set_disabled(bool flag); + + bool noreturn() const { return flags & _NoReturn; } + bool set_noreturn() { flags |= _NoReturn; return true; } + bool set_noreturn(bool flag); + + bool impure() const { return flags & _Impure; } + void set_impure(const CodeBlob &code); + void set_impure(const CodeBlob &code, bool flag); + void show(std::ostream& os, const std::vector& vars, std::string pfx = "", int mode = 0) const; void show_var_list(std::ostream& os, const std::vector& idx_list, const std::vector& vars) const; void show_var_list(std::ostream& os, const std::vector& list, const std::vector& vars) const; @@ -611,17 +616,10 @@ struct Op { bool set_var_info_except(VarDescrList&& new_var_info, const std::vector& var_list); void prepare_args(VarDescrList values); VarDescrList fwd_analyze(VarDescrList values); - bool set_noreturn(bool nr); bool mark_noreturn(); - bool noreturn() const { - return flags & _NoReturn; - } bool is_empty() const { return cl == _Nop && !next; } - bool is_pure() const { - return !(flags & _Impure); - } bool generate_code_step(Stack& stack); void generate_code_all(Stack& stack); Op& last() { @@ -682,7 +680,7 @@ typedef std::vector FormalArgList; struct AsmOpList; struct CodeBlob { - enum { _AllowPostModification = 1, _ComputeAsmLtr = 2 }; + enum { _ForbidImpure = 4 }; int var_cnt, in_var_cnt, op_cnt; TypeExpr* ret_type; std::string name; @@ -726,7 +724,6 @@ struct CodeBlob { pop_cur(); } void simplify_var_types(); - void flags_set_clear(int set, int clear); void prune_unreachable_code(); void fwd_analyze(); void mark_noreturn(); @@ -748,48 +745,75 @@ struct CodeBlob { struct SymVal : SymValBase { TypeExpr* sym_type; - td::RefInt256 method_id; - bool impure; bool auto_apply{false}; - short flags; // +1 = inline, +2 = inline_ref - SymVal(int _type, int _idx, TypeExpr* _stype = nullptr, bool _impure = false) - : SymValBase(_type, _idx), sym_type(_stype), impure(_impure), flags(0) { + SymVal(int _type, int _idx, TypeExpr* _stype = nullptr) + : SymValBase(_type, _idx), sym_type(_stype) { } ~SymVal() override = default; TypeExpr* get_type() const { return sym_type; } - virtual const std::vector* get_arg_order() const { - return nullptr; - } - virtual const std::vector* get_ret_order() const { - return nullptr; - } }; struct SymValFunc : SymVal { + enum SymValFlag { + flagInline = 1, // function marked `inline` + flagInlineRef = 2, // function marked `inline_ref` + flagWrapsAnotherF = 4, // (T) thisF(...args) { return anotherF(...args); } (calls to thisF will be replaced) + flagUsedAsNonCall = 8, // used not only as `f()`, but as a 1-st class function (assigned to var, pushed to tuple, etc.) + flagMarkedAsPure = 16, // declared as `pure`, can't call impure and access globals, unused invocations are optimized out + flagBuiltinFunction = 32, // was created via `define_builtin_func()`, not from source code + flagGetMethod = 64, // was declared via `get T func()`, method_id is auto-assigned + }; + + td::RefInt256 method_id; // todo why int256? it's small + int flags{0}; std::vector arg_order, ret_order; +#ifdef TOLK_DEBUG + std::string name; // seeing function name in debugger makes it much easier to delve into Tolk sources +#endif ~SymValFunc() override = default; - SymValFunc(int val, TypeExpr* _ft, bool _impure = false) : SymVal(_Func, val, _ft, _impure) { - } - SymValFunc(int val, TypeExpr* _ft, std::initializer_list _arg_order, std::initializer_list _ret_order = {}, - bool _impure = false) - : SymVal(_Func, val, _ft, _impure), arg_order(_arg_order), ret_order(_ret_order) { + SymValFunc(int val, TypeExpr* _ft, bool marked_as_pure) + : SymVal(_Func, val, _ft), flags(marked_as_pure ? flagMarkedAsPure : 0) {} + SymValFunc(int val, TypeExpr* _ft, std::initializer_list _arg_order, std::initializer_list _ret_order, bool marked_as_pure) + : SymVal(_Func, val, _ft), flags(marked_as_pure ? flagMarkedAsPure : 0), arg_order(_arg_order), ret_order(_ret_order) { } - const std::vector* get_arg_order() const override { + const std::vector* get_arg_order() const { return arg_order.empty() ? nullptr : &arg_order; } - const std::vector* get_ret_order() const override { + const std::vector* get_ret_order() const { return ret_order.empty() ? nullptr : &ret_order; } + const TypeExpr* get_arg_type() const; + + bool is_inline() const { + return flags & flagInline; + } + bool is_inline_ref() const { + return flags & flagInlineRef; + } + bool is_just_wrapper_for_another_f() const { + return flags & flagWrapsAnotherF; + } + bool is_marked_as_pure() const { + return flags & flagMarkedAsPure; + } + bool is_builtin() const { + return flags & flagBuiltinFunction; + } + bool is_get_method() const { + return flags & flagGetMethod; + } }; struct SymValCodeFunc : SymValFunc { CodeBlob* code; + bool is_really_used{false}; // calculated via dfs; unused functions are not codegenerated ~SymValCodeFunc() override = default; - SymValCodeFunc(int val, TypeExpr* _ft, bool _impure = false) : SymValFunc(val, _ft, _impure), code(nullptr) { + SymValCodeFunc(int val, TypeExpr* _ft, bool marked_as_pure) : SymValFunc(val, _ft, marked_as_pure), code(nullptr) { } + bool does_need_codegen() const; }; struct SymValType : SymValBase { @@ -805,6 +829,10 @@ struct SymValType : SymValBase { struct SymValGlobVar : SymValBase { TypeExpr* sym_type; int out_idx{0}; + bool is_really_used{false}; // calculated via dfs from used functions; unused globals are not codegenerated +#ifdef TOLK_DEBUG + std::string name; // seeing variable name in debugger makes it much easier to delve into Tolk sources +#endif SymValGlobVar(int val, TypeExpr* gvtype, int oidx = 0) : SymValBase(_GlobVar, val), sym_type(gvtype), out_idx(oidx) { } @@ -839,7 +867,7 @@ struct SymValConst : SymValBase { }; extern int glob_func_cnt, undef_func_cnt, glob_var_cnt; -extern std::vector glob_func, glob_vars; +extern std::vector glob_func, glob_vars, glob_get_methods; extern std::set prohibited_var_names; /* @@ -891,7 +919,7 @@ extern std::stack inclusion_locations; */ struct Expr { - enum { + enum ExprCls { _None, _Apply, _VarApply, @@ -900,18 +928,18 @@ struct Expr { _Tensor, _Const, _Var, - _Glob, + _GlobFunc, _GlobVar, _Letop, _LetFirst, _Hole, _Type, _CondExpr, - _SliceConst + _SliceConst, }; - int cls; + ExprCls cls; int val{0}; - enum { _IsType = 1, _IsRvalue = 2, _IsLvalue = 4, _IsHole = 8, _IsNewVar = 16, _IsImpure = 32 }; + enum { _IsType = 1, _IsRvalue = 2, _IsLvalue = 4, _IsImpure = 32, _IsInsideParenthesis = 64 }; int flags{0}; SrcLocation here; td::RefInt256 intval; @@ -919,19 +947,19 @@ struct Expr { SymDef* sym{nullptr}; TypeExpr* e_type{nullptr}; std::vector args; - Expr(int c = _None) : cls(c) { + explicit Expr(ExprCls c = _None) : cls(c) { } - Expr(int c, const SrcLocation& loc) : cls(c), here(loc) { + Expr(ExprCls c, const SrcLocation& loc) : cls(c), here(loc) { } - Expr(int c, std::vector _args) : cls(c), args(std::move(_args)) { + Expr(ExprCls c, std::vector _args) : cls(c), args(std::move(_args)) { } - Expr(int c, std::initializer_list _arglist) : cls(c), args(std::move(_arglist)) { + Expr(ExprCls c, std::initializer_list _arglist) : cls(c), args(std::move(_arglist)) { } - Expr(int c, SymDef* _sym, std::initializer_list _arglist) : cls(c), sym(_sym), args(std::move(_arglist)) { + Expr(ExprCls c, SymDef* _sym, std::initializer_list _arglist) : cls(c), sym(_sym), args(std::move(_arglist)) { } - Expr(int c, SymDef* _sym, std::vector _arglist) : cls(c), sym(_sym), args(std::move(_arglist)) { + Expr(ExprCls c, SymDef* _sym, std::vector _arglist) : cls(c), sym(_sym), args(std::move(_arglist)) { } - Expr(int c, sym_idx_t name_idx, std::initializer_list _arglist); + Expr(ExprCls c, sym_idx_t name_idx, std::initializer_list _arglist); ~Expr() { for (auto& arg_ptr : args) { delete arg_ptr; @@ -953,6 +981,9 @@ struct Expr { bool is_type() const { return flags & _IsType; } + bool is_inside_parenthesis() const { + return flags & _IsInsideParenthesis; + } bool is_type_apply() const { return cls == _TypeApply; } @@ -972,7 +1003,6 @@ struct Expr { int define_new_vars(CodeBlob& code); int predefine_vars(); std::vector pre_compile(CodeBlob& code, std::vector>* lval_globs = nullptr) const; - static std::vector pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rhs, const SrcLocation& here); var_idx_t new_tmp(CodeBlob& code) const; std::vector new_tmp_vect(CodeBlob& code) const { return {new_tmp(code)}; @@ -993,9 +1023,9 @@ using Const = td::RefInt256; struct AsmOp { enum Type { a_none, a_xchg, a_push, a_pop, a_const, a_custom, a_magic }; - int t{a_none}; + Type t{a_none}; int indent{0}; - int a, b, c; + int a, b; bool gconst{false}; std::string op; td::RefInt256 origin; @@ -1005,26 +1035,22 @@ struct AsmOp { } }; AsmOp() = default; - AsmOp(int _t) : t(_t) { + AsmOp(Type _t) : t(_t) { } - AsmOp(int _t, std::string _op) : t(_t), op(std::move(_op)) { + AsmOp(Type _t, std::string _op) : t(_t), op(std::move(_op)) { } - AsmOp(int _t, int _a) : t(_t), a(_a) { + AsmOp(Type _t, int _a) : t(_t), a(_a) { } - AsmOp(int _t, int _a, std::string _op) : t(_t), a(_a), op(std::move(_op)) { + AsmOp(Type _t, int _a, std::string _op) : t(_t), a(_a), op(std::move(_op)) { } - AsmOp(int _t, int _a, int _b) : t(_t), a(_a), b(_b) { + AsmOp(Type _t, int _a, int _b) : t(_t), a(_a), b(_b) { } - AsmOp(int _t, int _a, int _b, std::string _op) : t(_t), a(_a), b(_b), op(std::move(_op)) { + AsmOp(Type _t, int _a, int _b, std::string _op) : t(_t), a(_a), b(_b), op(std::move(_op)) { compute_gconst(); } - AsmOp(int _t, int _a, int _b, std::string _op, td::RefInt256 x) : t(_t), a(_a), b(_b), op(std::move(_op)), origin(x) { + AsmOp(Type _t, int _a, int _b, std::string _op, td::RefInt256 x) : t(_t), a(_a), b(_b), op(std::move(_op)), origin(x) { compute_gconst(); } - AsmOp(int _t, int _a, int _b, int _c) : t(_t), a(_a), b(_b), c(_c) { - } - AsmOp(int _t, int _a, int _b, int _c, std::string _op) : t(_t), a(_a), b(_b), c(_c), op(std::move(_op)) { - } void out(std::ostream& os) const; void out_indent_nl(std::ostream& os, bool no_nl = false) const; std::string to_string() const; @@ -1680,8 +1706,8 @@ inline simple_compile_func_t make_simple_compile(AsmOp op) { return [op](std::vector& out, std::vector& in, const SrcLocation&) -> AsmOp { return op; }; } -inline compile_func_t make_ext_compile(std::vector ops) { - return [ops = std::move(ops)](AsmOpList & dest, std::vector & out, std::vector & in)->bool { +inline compile_func_t make_ext_compile(std::vector&& ops) { + return [ops = std::move(ops)](AsmOpList& dest, std::vector& out, std::vector& in)->bool { return dest.append(ops); }; } @@ -1696,25 +1722,22 @@ struct SymValAsmFunc : SymValFunc { compile_func_t ext_compile; td::uint64 crc; ~SymValAsmFunc() override = default; - SymValAsmFunc(TypeExpr* ft, const AsmOp& _macro, bool impure = false) - : SymValFunc(-1, ft, impure), simple_compile(make_simple_compile(_macro)) { + SymValAsmFunc(TypeExpr* ft, std::vector&& _macro, bool marked_as_pure) + : SymValFunc(-1, ft, marked_as_pure), ext_compile(make_ext_compile(std::move(_macro))) { } - SymValAsmFunc(TypeExpr* ft, std::vector _macro, bool impure = false) - : SymValFunc(-1, ft, impure), ext_compile(make_ext_compile(std::move(_macro))) { + SymValAsmFunc(TypeExpr* ft, simple_compile_func_t _compile, bool marked_as_pure) + : SymValFunc(-1, ft, marked_as_pure), simple_compile(std::move(_compile)) { } - SymValAsmFunc(TypeExpr* ft, simple_compile_func_t _compile, bool impure = false) - : SymValFunc(-1, ft, impure), simple_compile(std::move(_compile)) { - } - SymValAsmFunc(TypeExpr* ft, compile_func_t _compile, bool impure = false) - : SymValFunc(-1, ft, impure), ext_compile(std::move(_compile)) { + SymValAsmFunc(TypeExpr* ft, compile_func_t _compile, bool marked_as_pure) + : SymValFunc(-1, ft, marked_as_pure), ext_compile(std::move(_compile)) { } SymValAsmFunc(TypeExpr* ft, simple_compile_func_t _compile, std::initializer_list arg_order, - std::initializer_list ret_order = {}, bool impure = false) - : SymValFunc(-1, ft, arg_order, ret_order, impure), simple_compile(std::move(_compile)) { + std::initializer_list ret_order = {}, bool marked_as_pure = false) + : SymValFunc(-1, ft, arg_order, ret_order, marked_as_pure), simple_compile(std::move(_compile)) { } SymValAsmFunc(TypeExpr* ft, compile_func_t _compile, std::initializer_list arg_order, - std::initializer_list ret_order = {}, bool impure = false) - : SymValFunc(-1, ft, arg_order, ret_order, impure), ext_compile(std::move(_compile)) { + std::initializer_list ret_order = {}, bool marked_as_pure = false) + : SymValFunc(-1, ft, arg_order, ret_order, marked_as_pure), ext_compile(std::move(_compile)) { } bool compile(AsmOpList& dest, std::vector& out, std::vector& in, const SrcLocation& where) const; }; @@ -1747,30 +1770,17 @@ class GlobalPragma { bool enabled() const { return enabled_; } - void enable(SrcLocation loc) { - enabled_ = true; - locs_.push_back(std::move(loc)); - } - void check_enable_in_libs() { - if (locs_.empty()) { - return; - } - for (const SrcLocation& loc : locs_) { - if (loc.fdescr->is_main) { - return; - } - } - locs_[0].show_warning(PSTRING() << "#pragma " << name_ - << " is enabled in included libraries, it may change the behavior of your code. " - << "Add this #pragma to the main source file to suppress this warning."); - } + void enable(SrcLocation loc); + void check_enable_in_libs(); + void always_on_and_deprecated(const char *deprecated_from_v); private: std::string name_; bool enabled_ = false; + const char *deprecated_from_v_ = nullptr; std::vector locs_; }; -extern GlobalPragma pragma_allow_post_modification, pragma_compute_asm_ltr; +extern GlobalPragma pragma_allow_post_modification, pragma_compute_asm_ltr, pragma_remove_unused_functions; /* * diff --git a/tolk/unify-types.cpp b/tolk/unify-types.cpp index 4e28dc83..848e454a 100644 --- a/tolk/unify-types.cpp +++ b/tolk/unify-types.cpp @@ -113,6 +113,39 @@ int TypeExpr::extract_components(std::vector& comp_list) { return res; } +bool TypeExpr::equals_to(const TypeExpr *rhs) const { + const TypeExpr *l = this; + const TypeExpr *r = rhs; + while (l->constr == te_Indirect) + l = l->args[0]; + while (r->constr == te_Indirect) + r = r->args[0]; + + bool eq = l->constr == r->constr && l->value == r->value && + l->minw == r->minw && l->maxw == r->maxw && + l->was_forall_var == r->was_forall_var && + l->args.size() == r->args.size(); + if (!eq) + return false; + + for (int i = 0; i < static_cast(l->args.size()); ++i) { + if (!l->args[i]->equals_to(r->args[i])) + return false; + } + return true; +} + +bool TypeExpr::has_unknown_inside() const { + if (constr == te_Unknown) + return true; + + for (const TypeExpr* inner : args) { + if (inner->has_unknown_inside()) + return true; + } + return false; +} + TypeExpr* TypeExpr::new_map(TypeExpr* from, TypeExpr* to) { return new TypeExpr{te_Map, std::vector{from, to}}; } @@ -207,7 +240,7 @@ std::ostream& operator<<(std::ostream& os, TypeExpr* type_expr) { return type_expr->print(os); } -std::ostream& TypeExpr::print(std::ostream& os, int lex_level) { +std::ostream& TypeExpr::print(std::ostream& os, int lex_level) const { switch (constr) { case te_Unknown: return os << "??" << value; diff --git a/tonlib/tonlib/tonlib-cli.cpp b/tonlib/tonlib/tonlib-cli.cpp index 4567478e..8fddedd4 100644 --- a/tonlib/tonlib/tonlib-cli.cpp +++ b/tonlib/tonlib/tonlib-cli.cpp @@ -386,7 +386,7 @@ class TonlibCli : public td::actor::Actor { td::TerminalIO::out() << "sendfile \tLoad a serialized message from and send it to server\n"; td::TerminalIO::out() << "setconfig|validateconfig [] [] [] - set or validate " "lite server config\n"; - td::TerminalIO::out() << "runmethod ...\tRuns GET method of account " + td::TerminalIO::out() << "runmethod ...\tRuns GET method of account " " with specified parameters\n"; td::TerminalIO::out() << "getstate \tget state of wallet with requested key\n"; td::TerminalIO::out() << "getstatebytransaction \tget state of wallet with requested key after transaction with local time and hash (base64url)\n";