1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-03-09 15:40:10 +00:00

[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.
This commit is contained in:
tolk-vm 2024-10-31 10:54:05 +04:00
parent 82648ebd6a
commit ebbab54cda
No known key found for this signature in database
GPG key ID: 7905DD7FE0324B12
21 changed files with 1345 additions and 789 deletions

View file

@ -81,14 +81,14 @@ int fixed248::acot(int x) inline_ref;
;; random number uniformly distributed in [0..1) ;; random number uniformly distributed in [0..1)
;; fixed248 random(); ;; fixed248 random();
int fixed248::random() impure inline; int fixed248::random() inline;
;; random number with standard normal distribution (2100 gas on average) ;; random number with standard normal distribution (2100 gas on average)
;; fixed248 nrand(); ;; 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) ;; 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()) ;; (fails chi-squared test, but it is shorter and faster than fixed248::nrand())
;; fixed248 nrand_fast(); ;; fixed248 nrand_fast();
int fixed248::nrand_fast() impure inline; int fixed248::nrand_fast() inline;
-} ;; end (declarations) -} ;; end (declarations)
@ -880,7 +880,7 @@ int fixed248::acot(int x) inline_ref {
;; generated by Kinderman--Monahan ratio method modified by J.Leva ;; generated by Kinderman--Monahan ratio method modified by J.Leva
;; spends ~ 2k..3k gas on average ;; spends ~ 2k..3k gas on average
;; fixed252 nrand(); ;; 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); 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 ;; 4/sqrt(e*Pi) = 1.369 loop iterations on average
do { do {
@ -910,7 +910,7 @@ int nrand_f252() impure inline_ref {
;; generates a random number approximately distributed according to the standard normal distribution ;; 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 ;; much faster than nrand_f252(), should be suitable for most purposes when only several random numbers are needed
;; fixed252 nrand_fast(); ;; fixed252 nrand_fast();
int nrand_fast_f252() impure inline_ref { int nrand_fast_f252() inline_ref {
int t = touch(-3) << 253; ;; -6. as fixed252 int t = touch(-3) << 253; ;; -6. as fixed252
repeat (12) { repeat (12) {
t += random() / 16; ;; add together 12 uniformly random numbers 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) ;; random number uniformly distributed in [0..1)
;; fixed248 random(); ;; fixed248 random();
int fixed248::random() impure inline { int fixed248::random() inline {
return random() >> 8; return random() >> 8;
} }
;; random number with standard normal distribution ;; random number with standard normal distribution
;; fixed248 nrand(); ;; fixed248 nrand();
int fixed248::nrand() impure inline { int fixed248::nrand() inline {
return nrand_f252() ~>> 4; return nrand_f252() ~>> 4;
} }
;; generates a random number approximately distributed according to the standard normal distribution ;; generates a random number approximately distributed according to the standard normal distribution
;; fixed248 nrand_fast(); ;; fixed248 nrand_fast();
int fixed248::nrand_fast() impure inline { int fixed248::nrand_fast() inline {
return nrand_fast_f252() ~>> 4; return nrand_fast_f252() ~>> 4;
} }

File diff suppressed because it is too large Load diff

View file

@ -926,7 +926,7 @@ bool TestNode::show_help(std::string command) {
"saveaccount[code|data] <filename> <addr> [<block-id-ext>]\tSaves into specified file the most recent state " "saveaccount[code|data] <filename> <addr> [<block-id-ext>]\tSaves into specified file the most recent state "
"(StateInit) or just the code or data of specified account; <addr> is in " "(StateInit) or just the code or data of specified account; <addr> is in "
"[<workchain>:]<hex-or-base64-addr> format\n" "[<workchain>:]<hex-or-base64-addr> format\n"
"runmethod[full] <addr> [<block-id-ext>] <method-id> <params>...\tRuns GET method <method-id> of account " "runmethod[full] <addr> [<block-id-ext>] <name> <params>...\tRuns GET method <name> of account "
"<addr> " "<addr> "
"with specified parameters\n" "with specified parameters\n"
"dnsresolve [<block-id-ext>] <domain> [<category>]\tResolves a domain starting from root dns smart contract\n" "dnsresolve [<block-id-ext>] <domain> [<category>]\tResolves a domain starting from root dns smart contract\n"

View file

@ -24,6 +24,10 @@ target_link_libraries(tolk PUBLIC git ton_crypto) # todo replace with ton_crypt
if (WINGETOPT_FOUND) if (WINGETOPT_FOUND)
target_link_libraries_system(tolk wingetopt) target_link_libraries_system(tolk wingetopt)
endif () 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) if (USE_EMSCRIPTEN)
add_executable(tolkfiftlib tolk-wasm.cpp ${TOLK_SOURCE}) add_executable(tolkfiftlib tolk-wasm.cpp ${TOLK_SOURCE})

View file

@ -221,15 +221,6 @@ void VarDescrList::show(std::ostream& os) const {
os << " ]\n"; 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<TmpVar>& vars) { void Op::split_vars(const std::vector<TmpVar>& vars) {
split_var_list(left, vars); split_var_list(left, vars);
split_var_list(right, vars); split_var_list(right, vars);
@ -294,7 +285,7 @@ void Op::show(std::ostream& os, const std::vector<TmpVar>& vars, std::string pfx
if (noreturn()) { if (noreturn()) {
dis += "<noret> "; dis += "<noret> ";
} }
if (!is_pure()) { if (impure()) {
dis += "<impure> "; dis += "<impure> ";
} }
switch (cl) { switch (cl) {
@ -467,12 +458,6 @@ void Op::show_block(std::ostream& os, const Op* block, const std::vector<TmpVar>
os << pfx << "}"; 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) { std::ostream& operator<<(std::ostream& os, const CodeBlob& code) {
code.print(os); code.print(os);
return os; return os;

View file

@ -360,10 +360,10 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) {
case _Tuple: case _Tuple:
case _UnTuple: { case _UnTuple: {
// left = EXEC right; // 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 // all variables in `left` are not needed
if (edit) { if (edit) {
disable(); set_disabled();
} }
return std_compute_used_vars(true); return std_compute_used_vars(true);
} }
@ -372,7 +372,7 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) {
case _SetGlob: { case _SetGlob: {
// GLOB = right // GLOB = right
if (right.empty() && edit) { if (right.empty() && edit) {
disable(); set_disabled();
} }
return std_compute_used_vars(right.empty()); return std_compute_used_vars(right.empty());
} }
@ -399,7 +399,7 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) {
} }
if (!cnt && edit) { if (!cnt && edit) {
// all variables in `left` are not needed // all variables in `left` are not needed
disable(); set_disabled();
} }
return set_var_info(std::move(new_var_info)); 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) { void Op::set_disabled(bool flag) {
if (nr) { if (flag) {
flags |= _Disabled;
} else {
flags &= ~_Disabled;
}
}
bool Op::set_noreturn(bool flag) {
if (flag) {
flags |= _NoReturn; flags |= _NoReturn;
} else { } else {
flags &= ~_NoReturn; 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() { bool Op::mark_noreturn() {
switch (cl) { switch (cl) {
case _Nop: case _Nop:
@ -888,13 +918,14 @@ bool Op::mark_noreturn() {
case _Call: case _Call:
return set_noreturn(next->mark_noreturn()); return set_noreturn(next->mark_noreturn());
case _Return: case _Return:
return set_noreturn(true); return set_noreturn();
case _If: case _If:
case _TryCatch: case _TryCatch:
// note, that & | (not && ||) here and below is mandatory to invoke both left and right calls
return set_noreturn((static_cast<int>(block0->mark_noreturn()) & static_cast<int>(block1 && block1->mark_noreturn())) | static_cast<int>(next->mark_noreturn())); return set_noreturn((static_cast<int>(block0->mark_noreturn()) & static_cast<int>(block1 && block1->mark_noreturn())) | static_cast<int>(next->mark_noreturn()));
case _Again: case _Again:
block0->mark_noreturn(); block0->mark_noreturn();
return set_noreturn(true); return set_noreturn();
case _Until: case _Until:
return set_noreturn(static_cast<int>(block0->mark_noreturn()) | static_cast<int>(next->mark_noreturn())); return set_noreturn(static_cast<int>(block0->mark_noreturn()) | static_cast<int>(next->mark_noreturn()));
case _While: case _While:

View file

@ -317,6 +317,9 @@ void AsmOpList::show_var_ext(std::ostream& os, std::pair<var_idx_t, const_idx_t>
os << '_' << i; os << '_' << i;
} else { } else {
var_names_->at(i).show(os, 2); 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()) { if ((unsigned)j < constants_.size() && constants_[j].not_null()) {
os << '=' << constants_[j]; os << '=' << constants_[j];

View file

@ -26,10 +26,10 @@ using namespace std::literals::string_literals;
*/ */
int glob_func_cnt, undef_func_cnt, glob_var_cnt, const_cnt; int glob_func_cnt, undef_func_cnt, glob_var_cnt, const_cnt;
std::vector<SymDef*> glob_func, glob_vars; std::vector<SymDef*> glob_func, glob_vars, glob_get_methods;
std::set<std::string> prohibited_var_names; std::set<std::string> 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() == '_') { if (name.back() == '_') {
prohibited_var_names.insert(name); 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::cerr << "fatal: global function `" << name << "` already defined" << std::endl;
std::exit(1); std::exit(1);
} }
func_val->flags |= SymValFunc::flagBuiltinFunction;
def->value = func_val;
#ifdef TOLK_DEBUG
dynamic_cast<SymValAsmFunc*>(def->value)->name = name;
#endif
return def; return def;
} }
template <typename T> SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const simple_compile_func_t& func, bool impure = false) {
SymDef* define_builtin_func(std::string name, TypeExpr* func_type, const T& func, bool impure = false) { return define_builtin_func_impl(name, new SymValAsmFunc{func_type, func, !impure});
SymDef* def = predefine_builtin_func(name, func_type);
def->value = new SymValAsmFunc{func_type, func, impure};
return def;
} }
template <typename T> SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const compile_func_t& func, bool impure = false) {
SymDef* define_builtin_func(std::string name, TypeExpr* func_type, const T& func, std::initializer_list<int> arg_order, 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<int> arg_order,
std::initializer_list<int> ret_order = {}, bool impure = false) { std::initializer_list<int> ret_order = {}, bool impure = false) {
SymDef* def = predefine_builtin_func(name, func_type); return define_builtin_func_impl(name, new SymValAsmFunc{func_type, func, arg_order, ret_order, !impure});
def->value = new SymValAsmFunc{func_type, func, arg_order, ret_order, impure};
return def;
} }
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<int> arg_order,
std::initializer_list<int> 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<int> arg_order, std::initializer_list<int> ret_order = {}, std::initializer_list<int> arg_order, std::initializer_list<int> ret_order = {},
bool impure = false) { bool impure = false) {
SymDef* def = predefine_builtin_func(name, func_type); return define_builtin_func_impl(name, new SymValAsmFunc{func_type, make_simple_compile(macro), arg_order, ret_order, !impure});
def->value = new SymValAsmFunc{func_type, make_simple_compile(macro), arg_order, ret_order, impure};
return def;
} }
SymDef* force_autoapply(SymDef* def) { SymDef* force_autoapply(SymDef* def) {
@ -262,7 +272,7 @@ int emulate_lshift(int a, int b) {
} }
int t = ((b & VarDescr::_NonZero) ? VarDescr::_Even : 0); int t = ((b & VarDescr::_NonZero) ? VarDescr::_Even : 0);
t |= b & VarDescr::_Finite; 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) { 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); int t = ((b & VarDescr::_NonZero) ? VarDescr::_Even : 0);
t |= b & VarDescr::_Finite; 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) { 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 Int3 = TypeExpr::new_tensor({Int, Int, Int});
auto TupleInt = TypeExpr::new_tensor({Tuple, Int}); auto TupleInt = TypeExpr::new_tensor({Tuple, Int});
auto SliceInt = TypeExpr::new_tensor({Slice, Int}); auto SliceInt = TypeExpr::new_tensor({Slice, Int});
auto X = TypeExpr::new_var(); auto X = TypeExpr::new_var(0);
auto Y = TypeExpr::new_var(); auto Y = TypeExpr::new_var(1);
auto Z = TypeExpr::new_var(); auto Z = TypeExpr::new_var(2);
auto XY = TypeExpr::new_tensor({X, Y}); auto XY = TypeExpr::new_tensor({X, Y});
auto arith_bin_op = TypeExpr::new_map(Int2, Int); auto arith_bin_op = TypeExpr::new_map(Int2, Int);
auto arith_un_op = TypeExpr::new_map(Int, Int); auto arith_un_op = TypeExpr::new_map(Int, Int);

View file

@ -437,6 +437,7 @@ bool Op::generate_code_step(Stack& stack) {
if (disabled()) { if (disabled()) {
return true; return true;
} }
// fun_ref can be nullptr for Op::_CallInd (invoke a variable, not a function)
SymValFunc* func = (fun_ref ? dynamic_cast<SymValFunc*>(fun_ref->value) : nullptr); SymValFunc* func = (fun_ref ? dynamic_cast<SymValFunc*>(fun_ref->value) : nullptr);
auto arg_order = (func ? func->get_arg_order() : nullptr); auto arg_order = (func ? func->get_arg_order() : nullptr);
auto ret_order = (func ? func->get_ret_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) { if (cl == _CallInd) {
exec_callxargs((int)right.size() - 1, (int)left.size()); exec_callxargs((int)right.size() - 1, (int)left.size());
} else if (auto asm_fv = dynamic_cast<const SymValAsmFunc*>(fun_ref->value)) {
std::vector<VarDescr> 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 { } else {
auto func = dynamic_cast<const SymValAsmFunc*>(fun_ref->value); auto fv = dynamic_cast<const SymValCodeFunc*>(fun_ref->value);
if (func) { // todo can be fv == nullptr?
std::vector<VarDescr> res; std::string name = symbols.get_name(fun_ref->sym_idx);
res.reserve(left.size()); if (fv && (fv->is_inline() || fv->is_inline_ref())) {
for (var_idx_t i : left) { stack.o << AsmOp::Custom(name + " INLINECALLDICT", (int)right.size(), (int)left.size());
res.emplace_back(i); } else if (fv && fv->code && fv->code->require_callxargs) {
} stack.o << AsmOp::Custom(name + (" PREPAREDICT"), 0, 2);
func->compile(stack.o, res, args, where); // compile res := f (args) exec_callxargs((int)right.size() + 1, (int)left.size());
} else { } else {
auto fv = dynamic_cast<const SymValCodeFunc*>(fun_ref->value); stack.o << AsmOp::Custom(name + " CALLDICT", (int)right.size(), (int)left.size());
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.s.resize(k); stack.s.resize(k);

View file

@ -35,7 +35,7 @@ Expr* Expr::copy() const {
return res; return res;
} }
Expr::Expr(int c, sym_idx_t name_idx, std::initializer_list<Expr*> _arglist) : cls(c), args(std::move(_arglist)) { Expr::Expr(ExprCls c, sym_idx_t name_idx, std::initializer_list<Expr*> _arglist) : cls(c), args(std::move(_arglist)) {
sym = lookup_symbol(name_idx); sym = lookup_symbol(name_idx);
if (!sym) { if (!sym) {
} }
@ -227,11 +227,11 @@ var_idx_t Expr::new_tmp(CodeBlob& code) const {
void add_set_globs(CodeBlob& code, std::vector<std::pair<SymDef*, var_idx_t>>& globs, const SrcLocation& here) { void add_set_globs(CodeBlob& code, std::vector<std::pair<SymDef*, var_idx_t>>& globs, const SrcLocation& here) {
for (const auto& p : globs) { for (const auto& p : globs) {
auto& op = code.emplace_back(here, Op::_SetGlob, std::vector<var_idx_t>{}, std::vector<var_idx_t>{ p.second }, p.first); auto& op = code.emplace_back(here, Op::_SetGlob, std::vector<var_idx_t>{}, std::vector<var_idx_t>{ p.second }, p.first);
op.flags |= Op::_Impure; op.set_impure(code);
} }
} }
std::vector<var_idx_t> Expr::pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rhs, const SrcLocation& here) { std::vector<var_idx_t> pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rhs, const SrcLocation& here) {
while (lhs->is_type_apply()) { while (lhs->is_type_apply()) {
lhs = lhs->args.at(0); lhs = lhs->args.at(0);
} }
@ -247,7 +247,7 @@ std::vector<var_idx_t> Expr::pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rh
auto unpacked_type = rhs->e_type->args.at(0); auto unpacked_type = rhs->e_type->args.at(0);
std::vector<var_idx_t> tmp{code.create_tmp_var(unpacked_type, &rhs->here)}; std::vector<var_idx_t> tmp{code.create_tmp_var(unpacked_type, &rhs->here)};
code.emplace_back(lhs->here, Op::_UnTuple, tmp, std::move(right)); 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_val(tmp[0]);
tvar->set_location(rhs->here); tvar->set_location(rhs->here);
tvar->e_type = unpacked_type; tvar->e_type = unpacked_type;
@ -265,43 +265,35 @@ std::vector<var_idx_t> Expr::pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rh
return right; return right;
} }
std::vector<var_idx_t> pre_compile_tensor(const std::vector<Expr *> args, CodeBlob &code, std::vector<var_idx_t> pre_compile_tensor(const std::vector<Expr *>& args, CodeBlob &code,
std::vector<std::pair<SymDef*, var_idx_t>> *lval_globs, std::vector<std::pair<SymDef*, var_idx_t>> *lval_globs) {
std::vector<int> arg_order) { const size_t n = args.size();
if (arg_order.empty()) { if (n == 0) { // just `()`
arg_order.resize(args.size()); return {};
std::iota(arg_order.begin(), arg_order.end(), 0);
} }
tolk_assert(args.size() == arg_order.size()); if (n == 1) { // just `(x)`: even if x is modified (e.g. `f(x=x+2)`), there are no next arguments
std::vector<std::vector<var_idx_t>> res_lists(args.size()); return args[0]->pre_compile(code, lval_globs);
}
std::vector<std::vector<var_idx_t>> res_lists(n);
struct ModifiedVar { struct ModifiedVar {
size_t i, j; size_t i, j;
Op* op; std::unique_ptr<Op>* cur_ops; // `LET tmp = v_ij` will be inserted before this
}; };
auto modified_vars = std::make_shared<std::vector<ModifiedVar>>(); std::vector<ModifiedVar> modified_vars;
for (size_t i : arg_order) { for (size_t i = 0; i < n; ++i) {
res_lists[i] = args[i]->pre_compile(code, lval_globs); res_lists[i] = args[i]->pre_compile(code, lval_globs);
for (size_t j = 0; j < res_lists[i].size(); ++j) { for (size_t j = 0; j < res_lists[i].size(); ++j) {
TmpVar& var = code.vars.at(res_lists[i][j]); TmpVar& var = code.vars.at(res_lists[i][j]);
if (code.flags & CodeBlob::_AllowPostModification) { if (!lval_globs && (var.cls & TmpVar::_Named)) {
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 {
Op *op = &code.emplace_back(nullptr, Op::_Let, std::vector<var_idx_t>(), std::vector<var_idx_t>()); if (!done) {
op->flags |= Op::_Disabled; done = true;
var.on_modification.push_back([modified_vars, i, j, op, done = false](const SrcLocation &here) mutable { modified_vars.push_back({i, j, cur_ops});
if (!done) { }
done = true; });
modified_vars->push_back({i, j, op});
}
});
} else {
var.on_modification.push_back([](const SrcLocation &) {
});
}
} else { } else {
var.on_modification.push_back([name = var.to_string()](const SrcLocation &here) { var.on_modification.push_back([](const SrcLocation &) {
throw ParseError{here, PSTRING() << "Modifying local variable " << name
<< " after using it in the same expression"};
}); });
} }
} }
@ -312,13 +304,16 @@ std::vector<var_idx_t> pre_compile_tensor(const std::vector<Expr *> args, CodeBl
code.vars.at(v).on_modification.pop_back(); code.vars.at(v).on_modification.pop_back();
} }
} }
for (const ModifiedVar &m : *modified_vars) { for (size_t idx = modified_vars.size(); idx--; ) {
var_idx_t& v = res_lists[m.i][m.j]; const ModifiedVar &m = modified_vars[idx];
var_idx_t v2 = code.create_tmp_var(code.vars[v].v_type, code.vars[v].where.get()); var_idx_t orig_v = res_lists[m.i][m.j];
m.op->left = {v2}; var_idx_t tmp_v = code.create_tmp_var(code.vars[orig_v].v_type, code.vars[orig_v].where.get());
m.op->right = {v}; std::unique_ptr<Op> op = std::make_unique<Op>(*code.vars[orig_v].where, Op::_Let);
m.op->flags &= ~Op::_Disabled; op->left = {tmp_v};
v = v2; 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<var_idx_t> res; std::vector<var_idx_t> res;
for (const auto& list : res_lists) { for (const auto& list : res_lists) {
@ -334,22 +329,33 @@ std::vector<var_idx_t> Expr::pre_compile(CodeBlob& code, std::vector<std::pair<S
} }
switch (cls) { switch (cls) {
case _Tensor: { case _Tensor: {
return pre_compile_tensor(args, code, lval_globs, {}); return pre_compile_tensor(args, code, lval_globs);
} }
case _Apply: { case _Apply: {
tolk_assert(sym); tolk_assert(sym);
auto func = dynamic_cast<SymValFunc*>(sym->value);
std::vector<var_idx_t> res; std::vector<var_idx_t> res;
if (func && func->arg_order.size() == args.size() && !(code.flags & CodeBlob::_ComputeAsmLtr)) { SymDef* applied_sym = sym;
//std::cerr << "!!! reordering " << args.size() << " arguments of " << sym->name() << std::endl; auto func = dynamic_cast<SymValFunc*>(applied_sym->value);
res = pre_compile_tensor(args, code, lval_globs, func->arg_order); // 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>& op_call = dynamic_cast<SymValCodeFunc*>(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<var_idx_t> 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 { } else {
res = pre_compile_tensor(args, code, lval_globs, {}); res = pre_compile_tensor(args, code, lval_globs);
} }
auto rvect = new_tmp_vect(code); 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) { if (flags & _IsImpure) {
op.flags |= Op::_Impure; op.set_impure(code);
} }
return rvect; return rvect;
} }
@ -362,12 +368,12 @@ std::vector<var_idx_t> Expr::pre_compile(CodeBlob& code, std::vector<std::pair<S
} }
return {val}; return {val};
case _VarApply: case _VarApply:
if (args[0]->cls == _Glob) { if (args[0]->cls == _GlobFunc) {
auto res = args[1]->pre_compile(code); auto res = args[1]->pre_compile(code);
auto rvect = new_tmp_vect(code); auto rvect = new_tmp_vect(code);
auto& op = code.emplace_back(here, Op::_Call, rvect, std::move(res), args[0]->sym); auto& op = code.emplace_back(here, Op::_Call, rvect, std::move(res), args[0]->sym);
if (args[0]->flags & _IsImpure) { if (args[0]->flags & _IsImpure) {
op.flags |= Op::_Impure; op.set_impure(code);
} }
return rvect; return rvect;
} else { } else {
@ -386,8 +392,14 @@ std::vector<var_idx_t> Expr::pre_compile(CodeBlob& code, std::vector<std::pair<S
code.emplace_back(here, Op::_IntConst, rvect, intval); code.emplace_back(here, Op::_IntConst, rvect, intval);
return rvect; return rvect;
} }
case _Glob: case _GlobFunc:
case _GlobVar: { case _GlobVar: {
if (auto fun_ref = dynamic_cast<SymValFunc*>(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); auto rvect = new_tmp_vect(code);
if (lval_globs) { if (lval_globs) {
lval_globs->push_back({ sym, rvect[0] }); lval_globs->push_back({ sym, rvect[0] });

View file

@ -109,10 +109,13 @@ void define_keywords() {
.add_keyword("global", Keyword::_Global) .add_keyword("global", Keyword::_Global)
.add_keyword("asm", Keyword::_Asm) .add_keyword("asm", Keyword::_Asm)
.add_keyword("impure", Keyword::_Impure) .add_keyword("impure", Keyword::_Impure)
.add_keyword("pure", Keyword::_Pure)
.add_keyword("inline", Keyword::_Inline) .add_keyword("inline", Keyword::_Inline)
.add_keyword("inline_ref", Keyword::_InlineRef) .add_keyword("inline_ref", Keyword::_InlineRef)
.add_keyword("builtin", Keyword::_Builtin)
.add_keyword("auto_apply", Keyword::_AutoApply) .add_keyword("auto_apply", Keyword::_AutoApply)
.add_keyword("method_id", Keyword::_MethodId) .add_keyword("method_id", Keyword::_MethodId)
.add_keyword("get", Keyword::_Get)
.add_keyword("operator", Keyword::_Operator) .add_keyword("operator", Keyword::_Operator)
.add_keyword("infix", Keyword::_Infix) .add_keyword("infix", Keyword::_Infix)
.add_keyword("infixl", Keyword::_Infixl) .add_keyword("infixl", Keyword::_Infixl)

View file

@ -122,8 +122,7 @@ int Lexem::set(std::string _str, const SrcLocation& _loc, int _tp, int _val) {
return classify(); return classify();
} }
Lexer::Lexer(SourceReader& _src, bool init, std::string active_chars, std::string eol_cmts, std::string open_cmts, Lexer::Lexer(SourceReader& _src, std::string active_chars, std::string quote_chars, std::string multiline_quote)
std::string close_cmts, std::string quote_chars, std::string multiline_quote)
: src(_src), eof(false), lexem("", src.here(), Lexem::Undefined), peek_lexem("", {}, Lexem::Undefined), : src(_src), eof(false), lexem("", src.here(), Lexem::Undefined), peek_lexem("", {}, Lexem::Undefined),
multiline_quote(std::move(multiline_quote)) { multiline_quote(std::move(multiline_quote)) {
std::memset(char_class, 0, sizeof(char_class)); 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; 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) { for (int c : quote_chars) {
if (c > ' ' && c <= 0x7f) { if (c > ' ' && c <= 0x7f) {
char_class[(unsigned)c] |= cc::quote_char; 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<int, 3>& arr, std::string setup) { void Lexer::set_spec(std::array<int, 3>& arr, std::string setup) {
@ -202,31 +211,41 @@ const Lexem& Lexer::next() {
return lexem.clear(src.here(), Lexem::Eof); return lexem.clear(src.here(), Lexem::Eof);
} }
long long comm = 1; 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()) { while (!src.seek_eof()) {
int cc = src.cur_char(), nc = src.next_char(); int cc = src.cur_char(), nc = src.next_char();
if (cc == eol_cmt[0] || (cc == eol_cmt[1] && nc == eol_cmt[2])) { // note, that in practice, [0]-th element is -256, condition for [0]-th is always false
src.load_line(); // todo rewrite this all in the future
} else if (cc == cmt_op[1] && nc == cmt_op[2]) { 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); src.advance(2);
comm = comm * 2 + 1; 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); src.advance(1);
comm *= 2; comm *= 2;
} else if (comm == 1) { } else if (comm == 1) {
break; break; // means that we are not inside a comment
} else if (cc == cmt_cl[1] && nc == cmt_cl[2]) { } else if (cc == cmt_cl[1] && nc == cmt_cl[2] || cc == cmt_cl2[1] && nc == cmt_cl2[2]) {
if (!(comm & 1)) { 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] + 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); src.advance(2);
} else if (cc == cmt_cl[0]) { } else if (cc == cmt_cl[0] || cc == cmt_cl2[0]) { // always false
if (!(comm & 1)) { if (!(comm & 1)) {
src.error(std::string{"a `"} + (char)cmt_op[1] + (char)cmt_op[2] + "` comment closed by `" + (char)cmt_cl[0] + 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); src.advance(1);
} else { } else {
src.advance(1); src.advance(1);
@ -238,11 +257,7 @@ const Lexem& Lexer::next() {
if (src.seek_eof()) { if (src.seek_eof()) {
eof = true; eof = true;
if (comm > 1) { if (comm > 1) {
if (comm & 1) { src.error("comment extends past end of file");
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");
}
} }
return lexem.clear(src.here(), Lexem::Eof); return lexem.clear(src.here(), Lexem::Eof);
} }

View file

@ -66,7 +66,8 @@ class Lexer {
bool eof; bool eof;
Lexem lexem, peek_lexem; Lexem lexem, peek_lexem;
unsigned char char_class[128]; unsigned char char_class[128];
std::array<int, 3> eol_cmt, cmt_op, cmt_cl; std::array<int, 3> eol_cmt, cmt_op, cmt_cl; // for ;; {- -}
std::array<int, 3> eol_cmt2, cmt_op2, cmt_cl2; // for // /* */
std::string multiline_quote; std::string multiline_quote;
enum cc { left_active = 2, right_active = 1, active = 3, allow_repeat = 4, quote_char = 8 }; 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 { bool eof_found() const {
return eof; return eof;
} }
Lexer(SourceReader& _src, bool init = false, std::string active_chars = ";,() ~.", std::string eol_cmts = ";;", explicit Lexer(SourceReader& _src, std::string active_chars = ";,() ~.",
std::string open_cmts = "{-", std::string close_cmts = "-}", std::string quote_chars = "\"", std::string quote_chars = "\"", std::string multiline_quote = "\"\"\"");
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& next();
const Lexem& cur() const { const Lexem& cur() const {
return lexem; return lexem;

View file

@ -48,6 +48,109 @@ inline bool is_special_ident(sym_idx_t idx) {
return symbols.get_subclass(idx) != IdSc::undef; 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 * PARSE SOURCE
@ -220,6 +323,9 @@ void parse_global_var_decl(Lexer& lex) {
} }
} else { } else {
sym_def->value = new SymValGlobVar{glob_var_cnt++, var_type}; sym_def->value = new SymValGlobVar{glob_var_cnt++, var_type};
#ifdef TOLK_DEBUG
dynamic_cast<SymValGlobVar*>(sym_def->value)->name = lex.cur().str;
#endif
glob_vars.push_back(sym_def); glob_vars.push_back(sym_def);
} }
lex.next(); lex.next();
@ -253,15 +359,9 @@ void parse_const_decl(Lexer& lex) {
} }
lex.next(); lex.next();
CodeBlob code; 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 // Handles processing and resolution of literals and consts
auto x = parse_expr(lex, code, false); // also does lex.next() ! 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"); lex.cur().error("expression is not strictly Rvalue");
} }
if ((wanted_type == Expr::_Const) && (x->cls == Expr::_Apply)) 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}; new_value = new SymValConst{const_cnt++, x->intval};
} else if (x->cls == Expr::_SliceConst) { // Slice constant (string) } else if (x->cls == Expr::_SliceConst) { // Slice constant (string)
new_value = new SymValConst{const_cnt++, x->strval}; 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<var_idx_t>()); code.emplace_back(loc, Op::_Import, std::vector<var_idx_t>());
auto tmp_vars = x->pre_compile(code); auto tmp_vars = x->pre_compile(code);
code.emplace_back(loc, Op::_Return, std::move(tmp_vars)); code.emplace_back(loc, Op::_Return, std::move(tmp_vars));
@ -372,27 +472,22 @@ void parse_global_var_decls(Lexer& lex) {
lex.expect(';'); lex.expect(';');
} }
SymValCodeFunc* make_new_glob_func(SymDef* func_sym, TypeExpr* func_type, bool impure = false) { SymValCodeFunc* make_new_glob_func(SymDef* func_sym, TypeExpr* func_type, bool marked_as_pure) {
SymValCodeFunc* res = new SymValCodeFunc{glob_func_cnt, func_type, impure}; 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; func_sym->value = res;
glob_func.push_back(func_sym); glob_func.push_back(func_sym);
glob_func_cnt++; glob_func_cnt++;
return res; return res;
} }
bool check_global_func(const Lexem& cur, sym_idx_t func_name = 0) { bool check_global_func(const Lexem& cur, sym_idx_t func_name) {
if (!func_name) {
func_name = cur.val;
}
SymDef* def = lookup_symbol(func_name); SymDef* def = lookup_symbol(func_name);
if (!def) { if (!def) {
cur.loc.show_error(std::string{"undefined function `"} + symbols.get_name(func_name) + cur.error("undefined symbol `" + symbols.get_name(func_name) + "`");
"`, defining a global function of unknown type"); return false;
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;
} }
SymVal* val = dynamic_cast<SymVal*>(def->value); SymVal* val = dynamic_cast<SymVal*>(def->value);
if (!val) { 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* make_func_apply(Expr* fun, Expr* x) {
Expr* res; Expr* res{nullptr};
if (fun->cls == Expr::_Glob) { if (fun->cls == Expr::_GlobFunc) {
if (x->cls == Expr::_Tensor) { if (x->cls == Expr::_Tensor) {
res = new Expr{Expr::_Apply, fun->sym, x->args}; res = new Expr{Expr::_Apply, fun->sym, x->args};
} else { } else {
@ -445,6 +540,7 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) {
} }
Expr* res = parse_expr(lex, code, nv); Expr* res = parse_expr(lex, code, nv);
if (lex.tp() == ')') { if (lex.tp() == ')') {
res->flags |= Expr::_IsInsideParenthesis;
lex.expect(clbr); lex.expect(clbr);
return res; return res;
} }
@ -571,7 +667,7 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) {
if (t == '_') { if (t == '_') {
Expr* res = new Expr{Expr::_Hole, lex.cur().loc}; Expr* res = new Expr{Expr::_Hole, lex.cur().loc};
res->val = -1; res->val = -1;
res->flags = (Expr::_IsLvalue | Expr::_IsHole | Expr::_IsNewVar); res->flags = Expr::_IsLvalue;
res->e_type = TypeExpr::new_hole(); res->e_type = TypeExpr::new_hole();
lex.next(); lex.next();
return res; return res;
@ -633,15 +729,16 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) {
if (nv) { if (nv) {
res->val = ~lex.cur().val; res->val = ~lex.cur().val;
res->e_type = TypeExpr::new_hole(); 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; // std::cerr << "defined new variable " << lex.cur().str << " : " << res->e_type << std::endl;
} else { } else {
if (!sym) { if (!sym) {
check_global_func(lex.cur()); check_global_func(lex.cur(), lex.cur().val);
sym = lookup_symbol(lex.cur().val); sym = lookup_symbol(lex.cur().val);
} }
res->sym = sym; res->sym = sym;
SymVal* val = nullptr; SymVal* val = nullptr;
bool impure = false;
if (sym) { if (sym) {
val = dynamic_cast<SymVal*>(sym->value); val = dynamic_cast<SymVal*>(sym->value);
} }
@ -649,8 +746,9 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) {
lex.cur().error_at("undefined identifier `", "`"); lex.cur().error_at("undefined identifier `", "`");
} else if (val->type == SymVal::_Func) { } else if (val->type == SymVal::_Func) {
res->e_type = val->get_type(); res->e_type = val->get_type();
res->cls = Expr::_Glob; res->cls = Expr::_GlobFunc;
auto_apply = val->auto_apply; auto_apply = val->auto_apply;
impure = !dynamic_cast<SymValFunc*>(val)->is_marked_as_pure();
} else if (val->idx < 0) { } else if (val->idx < 0) {
lex.cur().error_at("accessing variable `", "` being defined"); lex.cur().error_at("accessing variable `", "` being defined");
} else { } 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 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; // 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) { if (auto_apply) {
int impure = res->flags & Expr::_IsImpure; 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 = new Expr{Expr::_Apply, name, {obj, x}};
} }
res->here = loc; 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()); res->deduce_type(lex.cur());
if (modify) { if (modify) {
auto tmp = res; 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* parse_expr30(Lexer& lex, CodeBlob& code, bool nv) {
Expr* res = parse_expr75(lex, code, nv); Expr* res = parse_expr75(lex, code, nv);
while (lex.tp() == '*' || lex.tp() == '/' || lex.tp() == '%' || lex.tp() == _DivMod || lex.tp() == _DivC || 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()); res->chk_rvalue(lex.cur());
int t = lex.tp(); int t = lex.tp();
sym_idx_t name = symbols.lookup_add(std::string{"_"} + lex.cur().str + "_"); 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; return res;
} }
// parse [-] E { (+ | - | `|` | ^) E } // parse [-] E { (+ | -) E }
Expr* parse_expr20(Lexer& lex, CodeBlob& code, bool nv) { Expr* parse_expr20(Lexer& lex, CodeBlob& code, bool nv) {
Expr* res; Expr* res;
int t = lex.tp(); int t = lex.tp();
@ -825,7 +923,7 @@ Expr* parse_expr20(Lexer& lex, CodeBlob& code, bool nv) {
} else { } else {
res = parse_expr30(lex, code, nv); 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()); res->chk_rvalue(lex.cur());
t = lex.tp(); t = lex.tp();
sym_idx_t name = symbols.lookup_add(std::string{"_"} + lex.cur().str + "_"); 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; return res;
} }
// parse E { ( << | >> | >>~ | >>^ ) E } // parse E { ( << | >> | ~>> | ^>> ) E }
Expr* parse_expr17(Lexer& lex, CodeBlob& code, bool nv) { Expr* parse_expr17(Lexer& lex, CodeBlob& code, bool nv) {
Expr* res = parse_expr20(lex, code, nv); Expr* res = parse_expr20(lex, code, nv);
while (lex.tp() == _Lshift || lex.tp() == _Rshift || lex.tp() == _RshiftC || lex.tp() == _RshiftR) { 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(); lex.next();
auto x = parse_expr20(lex, code, false); auto x = parse_expr20(lex, code, false);
x->chk_rvalue(lex.cur()); x->chk_rvalue(lex.cur());
diagnose_addition_in_bitshift(loc, name, x);
res = new Expr{Expr::_Apply, name, {res, x}}; res = new Expr{Expr::_Apply, name, {res, x}};
res->here = loc; res->here = loc;
res->set_val(t); res->set_val(t);
@ -886,9 +985,33 @@ Expr* parse_expr15(Lexer& lex, CodeBlob& code, bool nv) {
return res; 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 ] // parse E [ ? E : E ]
Expr* parse_expr13(Lexer& lex, CodeBlob& code, bool nv) { 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() == '?') { if (lex.tp() == '?') {
res->chk_rvalue(lex.cur()); res->chk_rvalue(lex.cur());
SrcLocation loc{lex.cur().loc}; 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('{'); lex.expect('{');
CodeBlob* blob = new CodeBlob{ret_type}; CodeBlob* blob = new CodeBlob{ret_type};
if (pragma_allow_post_modification.enabled()) { if (marked_as_pure) {
blob->flags |= CodeBlob::_AllowPostModification; blob->flags |= CodeBlob::_ForbidImpure;
}
if (pragma_compute_asm_ltr.enabled()) {
blob->flags |= CodeBlob::_ComputeAsmLtr;
} }
blob->import_params(std::move(arg_list)); blob->import_params(std::move(arg_list));
blk_fl::val res = blk_fl::init; 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, 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; auto loc = lex.cur().loc;
lex.expect(_Asm); lex.expect(_Asm);
int cnt = (int)arg_list.size(); 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) { for (const AsmOp& asm_op : asm_ops) {
crc_s += asm_op.op; crc_s += asm_op.op;
} }
crc_s.push_back(impure); crc_s.push_back(!marked_as_pure);
for (const int& x : arg_order) { for (const int& x : arg_order) {
crc_s += std::string((const char*) (&x), (const char*) (&x + 1)); crc_s += std::string((const char*) (&x), (const char*) (&x + 1));
} }
for (const int& x : ret_order) { for (const int& x : ret_order) {
crc_s += std::string((const char*) (&x), (const char*) (&x + 1)); 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->arg_order = std::move(arg_order);
res->ret_order = std::move(ret_order); res->ret_order = std::move(ret_order);
res->crc = td::crc64(crc_s); res->crc = td::crc64(crc_s);
@ -1420,12 +1540,90 @@ TypeExpr* compute_type_closure(TypeExpr* expr, const std::vector<TypeExpr*>& typ
return expr; 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<int>(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<SymValFunc*>(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<int>(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) { void parse_func_def(Lexer& lex) {
SrcLocation loc{lex.cur().loc}; SrcLocation loc{lex.cur().loc};
open_scope(lex); open_scope(lex);
std::vector<TypeExpr*> type_vars; std::vector<TypeExpr*> type_vars;
bool is_get_method = false;
if (lex.tp() == _Forall) { if (lex.tp() == _Forall) {
type_vars = parse_type_var_list(lex); type_vars = parse_type_var_list(lex);
} else if (lex.tp() == _Get) {
is_get_method = true;
lex.next();
} }
auto ret_type = parse_type(lex); auto ret_type = parse_type(lex);
if (lex.tp() != _Ident) { if (lex.tp() != _Ident) {
@ -1434,47 +1632,80 @@ void parse_func_def(Lexer& lex) {
Lexem func_name = lex.cur(); Lexem func_name = lex.cur();
lex.next(); lex.next();
FormalArgList arg_list = parse_formal_args(lex); FormalArgList arg_list = parse_formal_args(lex);
bool impure = (lex.tp() == _Impure); bool marked_as_pure = false;
if (impure) { 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(); lex.next();
} }
int f = 0; int flags_inline = 0;
if (lex.tp() == _Inline || lex.tp() == _InlineRef) { if (lex.tp() == _Inline) {
f = (lex.tp() == _Inline) ? 1 : 2; flags_inline = SymValFunc::flagInline;
lex.next();
} else if (lex.tp() == _InlineRef) {
flags_inline = SymValFunc::flagInlineRef;
lex.next(); lex.next();
} }
td::RefInt256 method_id; td::RefInt256 method_id;
std::string method_name;
if (lex.tp() == _MethodId) { if (lex.tp() == _MethodId) {
if (is_get_method) {
lex.cur().error("both `get` and `method_id` are not allowed");
}
lex.next(); lex.next();
if (lex.tp() == '(') { if (lex.tp() == '(') { // method_id(N)
lex.expect('('); lex.expect('(');
if (lex.tp() == Lexem::String) { method_id = td::string_to_int256(lex.cur().str);
method_name = lex.cur().str; lex.expect(Lexem::Number);
} else if (lex.tp() == Lexem::Number) { if (method_id.is_null()) {
method_name = lex.cur().str; lex.cur().error_at("invalid integer constant `", "`");
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"};
} }
lex.next();
lex.expect(')'); lex.expect(')');
} else { } else {
method_name = func_name.str; static bool warning_shown = false;
} if (!warning_shown) {
if (method_id.is_null()) { lex.cur().loc.show_warning("`method_id` specifier is deprecated, use `get` keyword.\nExample: `get int seqno() { ... }`");
unsigned crc = td::crc16(method_name); warning_shown = true;
method_id = td::make_refint((crc & 0xffff) | 0x10000); }
method_id = calculate_method_id_by_func_name(func_name.str);
} }
} }
if (lex.tp() != ';' && lex.tp() != '{' && lex.tp() != _Asm) { if (is_get_method) {
lex.expect('{', "function body block expected"); 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<const SymValFunc*>(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); TypeExpr* func_type = TypeExpr::new_map(extract_total_arg_type(arg_list), ret_type);
func_type = compute_type_closure(func_type, type_vars); 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<SymValFunc*>(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) { if (verbosity >= 1) {
std::cerr << "function " << func_name.str << " : " << func_type << std::endl; std::cerr << "function " << func_name.str << " : " << func_type << std::endl;
} }
@ -1495,7 +1726,7 @@ void parse_func_def(Lexer& lex) {
} }
} }
if (lex.tp() == ';') { 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(); lex.next();
} else if (lex.tp() == '{') { } else if (lex.tp() == '{') {
if (dynamic_cast<SymValAsmFunc*>(func_sym_val)) { if (dynamic_cast<SymValAsmFunc*>(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"); lex.cur().error("function `"s + func_name.str + "` has been already defined in an yet-unknown way");
} }
} else { } 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) { if (func_sym_code->code) {
lex.cur().error("redefinition of function `"s + func_name.str + "`"); 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->name = func_name.str;
code->loc = loc; code->loc = loc;
// code->print(std::cerr); // !!!DEBUG!!! // code->print(std::cerr); // !!!DEBUG!!!
func_sym_code->code = code; func_sym_code->code = code;
detect_if_function_just_wraps_another(func_sym_code, method_id);
} else { } else {
Lexem asm_lexem = lex.cur(); 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 (func_sym_val) {
if (dynamic_cast<SymValCodeFunc*>(func_sym_val)) { if (dynamic_cast<SymValCodeFunc*>(func_sym_val)) {
asm_lexem.error("function `"s + func_name.str + "` was already declared as an ordinary function"); 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; func_sym->value = asm_func;
} }
if (method_id.not_null()) { if (method_id.not_null()) {
auto val = dynamic_cast<SymVal*>(func_sym->value); auto val = dynamic_cast<SymValFunc*>(func_sym->value);
if (!val) { if (!val) {
lex.cur().error("cannot set method id for unknown function `"s + func_name.str + "`"); 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()); val->method_id->to_dec_string() + " to a different value " + method_id->to_dec_string());
} }
} }
if (f) { if (flags_inline) {
auto val = dynamic_cast<SymVal*>(func_sym->value); auto val = dynamic_cast<SymValFunc*>(func_sym->value);
if (!val) { if (!val) {
lex.cur().error("cannot set unknown function `"s + func_name.str + "` as an inline"); lex.cur().error("cannot set unknown function `"s + func_name.str + "` as an inline");
} }
if (!(val->flags & 3)) { if (!val->is_inline() && !val->is_inline_ref()) {
val->flags = (short)(val->flags | f); val->flags |= flags_inline;
} else if ((val->flags & 3) != f) { } 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"); 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<SymValFunc*>(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) { if (verbosity >= 1) {
std::cerr << "new type of function " << func_name.str << " : " << func_type << std::endl; 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); pragma_allow_post_modification.enable(lex.cur().loc);
} else if (pragma_name == pragma_compute_asm_ltr.name()) { } else if (pragma_name == pragma_compute_asm_ltr.name()) {
pragma_compute_asm_ltr.enable(lex.cur().loc); 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 { } else {
lex.cur().error(std::string{"unknown pragma `"} + pragma_name + "`"); 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) { bool parse_source(std::istream* is, FileDescr* fdescr) {
SourceReader reader{is, 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) { while (lex.tp() != _Eof) {
if (lex.tp() == _PragmaHashtag) { if (lex.tp() == _PragmaHashtag) {
parse_pragma(lex); parse_pragma(lex);

View file

@ -149,7 +149,11 @@ SymDef* define_global_symbol(sym_idx_t name_idx, bool force_new, const SrcLocati
if (found) { if (found) {
return force_new && found->value ? nullptr : 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) { 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); 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})); 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; return found;
} }

View file

@ -148,6 +148,9 @@ struct SymDef {
sym_idx_t sym_idx; sym_idx_t sym_idx;
SymValBase* value; SymValBase* value;
SrcLocation loc; SrcLocation loc;
#ifdef TOLK_DEBUG
std::string sym_name;
#endif
SymDef(int lvl, sym_idx_t idx, const SrcLocation& _loc = {}, SymValBase* val = 0) SymDef(int lvl, sym_idx_t idx, const SrcLocation& _loc = {}, SymValBase* val = 0)
: level(lvl), sym_idx(idx), value(val), loc(_loc) { : level(lvl), sym_idx(idx), value(val), loc(_loc) {
} }

View file

@ -31,6 +31,7 @@
#include "td/utils/Status.h" #include "td/utils/Status.h"
#include <sstream> #include <sstream>
#include <iomanip> #include <iomanip>
#include "vm/boc.h"
td::Result<std::string> compile_internal(char *config_json) { td::Result<std::string> compile_internal(char *config_json) {
TRY_RESULT(input_json, td::json_decode(td::MutableSlice(config_json))) TRY_RESULT(input_json, td::json_decode(td::MutableSlice(config_json)))

View file

@ -38,9 +38,74 @@ bool stack_layout_comments, op_rewrite_comments, program_envelope, asm_preamble;
bool interactive = false; bool interactive = false;
GlobalPragma pragma_allow_post_modification{"allow-post-modification"}; GlobalPragma pragma_allow_post_modification{"allow-post-modification"};
GlobalPragma pragma_compute_asm_ltr{"compute-asm-ltr"}; GlobalPragma pragma_compute_asm_ltr{"compute-asm-ltr"};
GlobalPragma pragma_remove_unused_functions{"remove-unused-functions"};
std::string generated_from, boc_output_filename; std::string generated_from, boc_output_filename;
ReadCallback::Callback read_callback; 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<std::string> fs_read_callback(ReadCallback::Kind kind, const char* query) { td::Result<std::string> fs_read_callback(ReadCallback::Kind kind, const char* query) {
switch (kind) { switch (kind) {
case ReadCallback::Kind::ReadFile: { case ReadCallback::Kind::ReadFile: {
@ -62,6 +127,55 @@ td::Result<std::string> fs_read_callback(ReadCallback::Kind kind, const char* qu
} }
} }
void mark_function_used_dfs(const std::unique_ptr<Op>& 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>& 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<SymValCodeFunc*>(op->fun_ref->value)) {
mark_function_used(func_val);
} else if (auto* glob_val = dynamic_cast<SymValGlobVar*>(op->fun_ref->value)) {
mark_global_var_used(glob_val);
} else if (auto* asm_val = dynamic_cast<SymValAsmFunc*>(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<SymValCodeFunc*>(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 * 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; errs << "\n\n=========================\nfunction " << name << " : " << func_val->get_type() << std::endl;
} }
if (!func_val->code) { if (!func_val->code) {
errs << "( function `" << name << "` undefined )\n"; throw ParseError(func_sym->loc, "function `" + name + "` is just declared, not implemented");
throw ParseError(func_sym->loc, name);
} else { } else {
CodeBlob& code = *(func_val->code); CodeBlob& code = *(func_val->code);
if (verbosity >= 3) { if (verbosity >= 3) {
@ -122,12 +235,10 @@ void generate_output_func(SymDef* func_sym, std::ostream &outs, std::ostream &er
if (verbosity >= 2) { if (verbosity >= 2) {
errs << "\n---------- resulting code for " << name << " -------------\n"; errs << "\n---------- resulting code for " << name << " -------------\n";
} }
bool inline_func = (func_val->flags & 1);
bool inline_ref = (func_val->flags & 2);
const char* modifier = ""; const char* modifier = "";
if (inline_func) { if (func_val->is_inline()) {
modifier = "INLINE"; modifier = "INLINE";
} else if (inline_ref) { } else if (func_val->is_inline_ref()) {
modifier = "REF"; modifier = "REF";
} }
outs << std::string(indent * 2, ' ') << name << " PROC" << modifier << ":<{\n"; 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) { if (opt_level < 2) {
mode |= Stack::_DisableOpt; mode |= Stack::_DisableOpt;
} }
auto fv = dynamic_cast<const SymValCodeFunc*>(func_sym->value); if (func_val->is_inline() && code.ops->noreturn()) {
// Flags: 1 - inline, 2 - inline_ref
if (fv && (fv->flags & 1) && code.ops->noreturn()) {
mode |= Stack::_InlineFunc; mode |= Stack::_InlineFunc;
} }
if (fv && (fv->flags & 3)) { if (func_val->is_inline() || func_val->is_inline_ref()) {
mode |= Stack::_InlineAny; mode |= Stack::_InlineAny;
} }
code.generate_code(outs, mode, indent + 1); code.generate_code(outs, mode, indent + 1);
@ -162,9 +271,17 @@ int generate_output(std::ostream &outs, std::ostream &errs) {
if (program_envelope) { if (program_envelope) {
outs << "PROGRAM{\n"; outs << "PROGRAM{\n";
} }
mark_used_symbols();
for (SymDef* func_sym : glob_func) { for (SymDef* func_sym : glob_func) {
SymValCodeFunc* func_val = dynamic_cast<SymValCodeFunc*>(func_sym->value); SymValCodeFunc* func_val = dynamic_cast<SymValCodeFunc*>(func_sym->value);
tolk_assert(func_val); 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); std::string name = symbols.get_name(func_sym->sym_idx);
outs << std::string(indent * 2, ' '); outs << std::string(indent * 2, ' ');
if (func_val->method_id.is_null()) { 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) { for (SymDef* gvar_sym : glob_vars) {
tolk_assert(dynamic_cast<SymValGlobVar*>(gvar_sym->value)); auto* glob_val = dynamic_cast<SymValGlobVar*>(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); std::string name = symbols.get_name(gvar_sym->sym_idx);
outs << std::string(indent * 2, ' ') << "DECLGLOBVAR " << name << "\n"; outs << std::string(indent * 2, ' ') << "DECLGLOBVAR " << name << "\n";
} }
int errors = 0; int errors = 0;
for (SymDef* func_sym : glob_func) { for (SymDef* func_sym : glob_func) {
SymValCodeFunc* func_val = dynamic_cast<SymValCodeFunc*>(func_sym->value);
if (!func_val->does_need_codegen()) {
continue;
}
try { try {
generate_output_func(func_sym, outs, errs); generate_output_func(func_sym, outs, errs);
} catch (Error& err) { } catch (Error& err) {
@ -217,6 +345,8 @@ int tolk_proceed(const std::vector<std::string> &sources, std::ostream &outs, st
define_keywords(); define_keywords();
define_builtins(); 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; int ok = 0, proc = 0;
try { try {
@ -235,8 +365,7 @@ int tolk_proceed(const std::vector<std::string> &sources, std::ostream &outs, st
if (!proc) { if (!proc) {
throw Fatal{"no source files, no output"}; throw Fatal{"no source files, no output"};
} }
pragma_allow_post_modification.check_enable_in_libs(); pragma_remove_unused_functions.check_enable_in_libs();
pragma_compute_asm_ltr.check_enable_in_libs();
return generate_output(outs, errs); return generate_output(outs, errs);
} catch (Fatal& fatal) { } catch (Fatal& fatal) {
errs << "fatal: " << fatal << std::endl; errs << "fatal: " << fatal << std::endl;

View file

@ -106,12 +106,15 @@ enum Keyword {
_Forall, _Forall,
_Asm, _Asm,
_Impure, _Impure,
_Pure,
_Global, _Global,
_Extern, _Extern,
_Inline, _Inline,
_InlineRef, _InlineRef,
_Builtin,
_AutoApply, _AutoApply,
_MethodId, _MethodId,
_Get,
_Operator, _Operator,
_Infix, _Infix,
_Infixl, _Infixl,
@ -147,8 +150,8 @@ class IdSc {
*/ */
struct TypeExpr { 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 te_type { te_Unknown, te_Var, te_Indirect, te_Atomic, te_Tensor, te_Tuple, te_Map, te_ForAll } constr;
enum { enum AtomicType {
_Int = Keyword::_Int, _Int = Keyword::_Int,
_Cell = Keyword::_Cell, _Cell = Keyword::_Cell,
_Slice = Keyword::_Slice, _Slice = Keyword::_Slice,
@ -214,9 +217,11 @@ struct TypeExpr {
void compute_width(); void compute_width();
bool recompute_width(); bool recompute_width();
void show_width(std::ostream& os); 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); void replace_with(TypeExpr* te2);
int extract_components(std::vector<TypeExpr*>& comp_list); int extract_components(std::vector<TypeExpr*>& comp_list);
bool equals_to(const TypeExpr* rhs) const;
bool has_unknown_inside() const;
static int holes, type_vars; static int holes, type_vars;
static TypeExpr* new_hole() { static TypeExpr* new_hole() {
return new TypeExpr{te_Unknown, ++holes}; return new TypeExpr{te_Unknown, ++holes};
@ -528,7 +533,7 @@ class ListIterator {
struct Stack; struct Stack;
struct Op { struct Op {
enum { enum OpKind {
_Undef, _Undef,
_Nop, _Nop,
_Call, _Call,
@ -547,13 +552,13 @@ struct Op {
_Repeat, _Repeat,
_Again, _Again,
_TryCatch, _TryCatch,
_SliceConst _SliceConst,
}; };
int cl; OpKind cl;
enum { _Disabled = 1, _Reachable = 2, _NoReturn = 4, _ImpureR = 8, _ImpureW = 16, _Impure = 24 }; enum { _Disabled = 1, _NoReturn = 4, _Impure = 24 };
int flags; int flags;
std::unique_ptr<Op> next; std::unique_ptr<Op> 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; SrcLocation where;
VarDescrList var_info; VarDescrList var_info;
std::vector<VarDescr> args; std::vector<VarDescr> args;
@ -561,41 +566,41 @@ struct Op {
std::unique_ptr<Op> block0, block1; std::unique_ptr<Op> block0, block1;
td::RefInt256 int_const; td::RefInt256 int_const;
std::string str_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<var_idx_t>& _left) Op(const SrcLocation& _where, OpKind _cl, const std::vector<var_idx_t>& _left)
: cl(_cl), flags(0), fun_ref(nullptr), where(_where), left(_left) { : cl(_cl), flags(0), fun_ref(nullptr), where(_where), left(_left) {
} }
Op(const SrcLocation& _where, int _cl, std::vector<var_idx_t>&& _left) Op(const SrcLocation& _where, OpKind _cl, std::vector<var_idx_t>&& _left)
: cl(_cl), flags(0), fun_ref(nullptr), where(_where), left(std::move(_left)) { : cl(_cl), flags(0), fun_ref(nullptr), where(_where), left(std::move(_left)) {
} }
Op(const SrcLocation& _where, int _cl, const std::vector<var_idx_t>& _left, td::RefInt256 _const) Op(const SrcLocation& _where, OpKind _cl, const std::vector<var_idx_t>& _left, td::RefInt256 _const)
: cl(_cl), flags(0), fun_ref(nullptr), where(_where), left(_left), int_const(_const) { : cl(_cl), flags(0), fun_ref(nullptr), where(_where), left(_left), int_const(_const) {
} }
Op(const SrcLocation& _where, int _cl, const std::vector<var_idx_t>& _left, std::string _const) Op(const SrcLocation& _where, OpKind _cl, const std::vector<var_idx_t>& _left, std::string _const)
: cl(_cl), flags(0), fun_ref(nullptr), where(_where), left(_left), str_const(_const) { : cl(_cl), flags(0), fun_ref(nullptr), where(_where), left(_left), str_const(_const) {
} }
Op(const SrcLocation& _where, int _cl, const std::vector<var_idx_t>& _left, const std::vector<var_idx_t>& _right, Op(const SrcLocation& _where, OpKind _cl, const std::vector<var_idx_t>& _left, const std::vector<var_idx_t>& _right,
SymDef* _fun = nullptr) SymDef* _fun = nullptr)
: cl(_cl), flags(0), fun_ref(_fun), where(_where), left(_left), right(_right) { : cl(_cl), flags(0), fun_ref(_fun), where(_where), left(_left), right(_right) {
} }
Op(const SrcLocation& _where, int _cl, std::vector<var_idx_t>&& _left, std::vector<var_idx_t>&& _right, Op(const SrcLocation& _where, OpKind _cl, std::vector<var_idx_t>&& _left, std::vector<var_idx_t>&& _right,
SymDef* _fun = nullptr) SymDef* _fun = nullptr)
: cl(_cl), flags(0), fun_ref(_fun), where(_where), left(std::move(_left)), right(std::move(_right)) { : cl(_cl), flags(0), fun_ref(_fun), where(_where), left(std::move(_left)), right(std::move(_right)) {
} }
bool disabled() const {
return flags & _Disabled; bool disabled() const { return flags & _Disabled; }
} void set_disabled() { flags |= _Disabled; }
bool enabled() const { void set_disabled(bool flag);
return !disabled();
} bool noreturn() const { return flags & _NoReturn; }
void disable() { bool set_noreturn() { flags |= _NoReturn; return true; }
flags |= _Disabled; bool set_noreturn(bool flag);
}
bool unreachable() { bool impure() const { return flags & _Impure; }
return !(flags & _Reachable); void set_impure(const CodeBlob &code);
} void set_impure(const CodeBlob &code, bool flag);
void flags_set_clear(int set, int clear);
void show(std::ostream& os, const std::vector<TmpVar>& vars, std::string pfx = "", int mode = 0) const; void show(std::ostream& os, const std::vector<TmpVar>& vars, std::string pfx = "", int mode = 0) const;
void show_var_list(std::ostream& os, const std::vector<var_idx_t>& idx_list, const std::vector<TmpVar>& vars) const; void show_var_list(std::ostream& os, const std::vector<var_idx_t>& idx_list, const std::vector<TmpVar>& vars) const;
void show_var_list(std::ostream& os, const std::vector<VarDescr>& list, const std::vector<TmpVar>& vars) const; void show_var_list(std::ostream& os, const std::vector<VarDescr>& list, const std::vector<TmpVar>& vars) const;
@ -611,17 +616,10 @@ struct Op {
bool set_var_info_except(VarDescrList&& new_var_info, const std::vector<var_idx_t>& var_list); bool set_var_info_except(VarDescrList&& new_var_info, const std::vector<var_idx_t>& var_list);
void prepare_args(VarDescrList values); void prepare_args(VarDescrList values);
VarDescrList fwd_analyze(VarDescrList values); VarDescrList fwd_analyze(VarDescrList values);
bool set_noreturn(bool nr);
bool mark_noreturn(); bool mark_noreturn();
bool noreturn() const {
return flags & _NoReturn;
}
bool is_empty() const { bool is_empty() const {
return cl == _Nop && !next; return cl == _Nop && !next;
} }
bool is_pure() const {
return !(flags & _Impure);
}
bool generate_code_step(Stack& stack); bool generate_code_step(Stack& stack);
void generate_code_all(Stack& stack); void generate_code_all(Stack& stack);
Op& last() { Op& last() {
@ -682,7 +680,7 @@ typedef std::vector<FormalArg> FormalArgList;
struct AsmOpList; struct AsmOpList;
struct CodeBlob { struct CodeBlob {
enum { _AllowPostModification = 1, _ComputeAsmLtr = 2 }; enum { _ForbidImpure = 4 };
int var_cnt, in_var_cnt, op_cnt; int var_cnt, in_var_cnt, op_cnt;
TypeExpr* ret_type; TypeExpr* ret_type;
std::string name; std::string name;
@ -726,7 +724,6 @@ struct CodeBlob {
pop_cur(); pop_cur();
} }
void simplify_var_types(); void simplify_var_types();
void flags_set_clear(int set, int clear);
void prune_unreachable_code(); void prune_unreachable_code();
void fwd_analyze(); void fwd_analyze();
void mark_noreturn(); void mark_noreturn();
@ -748,48 +745,75 @@ struct CodeBlob {
struct SymVal : SymValBase { struct SymVal : SymValBase {
TypeExpr* sym_type; TypeExpr* sym_type;
td::RefInt256 method_id;
bool impure;
bool auto_apply{false}; bool auto_apply{false};
short flags; // +1 = inline, +2 = inline_ref SymVal(int _type, int _idx, TypeExpr* _stype = nullptr)
SymVal(int _type, int _idx, TypeExpr* _stype = nullptr, bool _impure = false) : SymValBase(_type, _idx), sym_type(_stype) {
: SymValBase(_type, _idx), sym_type(_stype), impure(_impure), flags(0) {
} }
~SymVal() override = default; ~SymVal() override = default;
TypeExpr* get_type() const { TypeExpr* get_type() const {
return sym_type; return sym_type;
} }
virtual const std::vector<int>* get_arg_order() const {
return nullptr;
}
virtual const std::vector<int>* get_ret_order() const {
return nullptr;
}
}; };
struct SymValFunc : SymVal { 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<int> arg_order, ret_order; std::vector<int> 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() override = default;
SymValFunc(int val, TypeExpr* _ft, bool _impure = false) : SymVal(_Func, val, _ft, _impure) { 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<int> _arg_order, std::initializer_list<int> _ret_order = {}, SymValFunc(int val, TypeExpr* _ft, std::initializer_list<int> _arg_order, std::initializer_list<int> _ret_order, bool marked_as_pure)
bool _impure = false) : SymVal(_Func, val, _ft), flags(marked_as_pure ? flagMarkedAsPure : 0), arg_order(_arg_order), ret_order(_ret_order) {
: SymVal(_Func, val, _ft, _impure), arg_order(_arg_order), ret_order(_ret_order) {
} }
const std::vector<int>* get_arg_order() const override { const std::vector<int>* get_arg_order() const {
return arg_order.empty() ? nullptr : &arg_order; return arg_order.empty() ? nullptr : &arg_order;
} }
const std::vector<int>* get_ret_order() const override { const std::vector<int>* get_ret_order() const {
return ret_order.empty() ? nullptr : &ret_order; 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 { struct SymValCodeFunc : SymValFunc {
CodeBlob* code; CodeBlob* code;
bool is_really_used{false}; // calculated via dfs; unused functions are not codegenerated
~SymValCodeFunc() override = default; ~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 { struct SymValType : SymValBase {
@ -805,6 +829,10 @@ struct SymValType : SymValBase {
struct SymValGlobVar : SymValBase { struct SymValGlobVar : SymValBase {
TypeExpr* sym_type; TypeExpr* sym_type;
int out_idx{0}; 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) SymValGlobVar(int val, TypeExpr* gvtype, int oidx = 0)
: SymValBase(_GlobVar, val), sym_type(gvtype), out_idx(oidx) { : 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 int glob_func_cnt, undef_func_cnt, glob_var_cnt;
extern std::vector<SymDef*> glob_func, glob_vars; extern std::vector<SymDef*> glob_func, glob_vars, glob_get_methods;
extern std::set<std::string> prohibited_var_names; extern std::set<std::string> prohibited_var_names;
/* /*
@ -891,7 +919,7 @@ extern std::stack<SrcLocation> inclusion_locations;
*/ */
struct Expr { struct Expr {
enum { enum ExprCls {
_None, _None,
_Apply, _Apply,
_VarApply, _VarApply,
@ -900,18 +928,18 @@ struct Expr {
_Tensor, _Tensor,
_Const, _Const,
_Var, _Var,
_Glob, _GlobFunc,
_GlobVar, _GlobVar,
_Letop, _Letop,
_LetFirst, _LetFirst,
_Hole, _Hole,
_Type, _Type,
_CondExpr, _CondExpr,
_SliceConst _SliceConst,
}; };
int cls; ExprCls cls;
int val{0}; 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}; int flags{0};
SrcLocation here; SrcLocation here;
td::RefInt256 intval; td::RefInt256 intval;
@ -919,19 +947,19 @@ struct Expr {
SymDef* sym{nullptr}; SymDef* sym{nullptr};
TypeExpr* e_type{nullptr}; TypeExpr* e_type{nullptr};
std::vector<Expr*> args; std::vector<Expr*> 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<Expr*> _args) : cls(c), args(std::move(_args)) { Expr(ExprCls c, std::vector<Expr*> _args) : cls(c), args(std::move(_args)) {
} }
Expr(int c, std::initializer_list<Expr*> _arglist) : cls(c), args(std::move(_arglist)) { Expr(ExprCls c, std::initializer_list<Expr*> _arglist) : cls(c), args(std::move(_arglist)) {
} }
Expr(int c, SymDef* _sym, std::initializer_list<Expr*> _arglist) : cls(c), sym(_sym), args(std::move(_arglist)) { Expr(ExprCls c, SymDef* _sym, std::initializer_list<Expr*> _arglist) : cls(c), sym(_sym), args(std::move(_arglist)) {
} }
Expr(int c, SymDef* _sym, std::vector<Expr*> _arglist) : cls(c), sym(_sym), args(std::move(_arglist)) { Expr(ExprCls c, SymDef* _sym, std::vector<Expr*> _arglist) : cls(c), sym(_sym), args(std::move(_arglist)) {
} }
Expr(int c, sym_idx_t name_idx, std::initializer_list<Expr*> _arglist); Expr(ExprCls c, sym_idx_t name_idx, std::initializer_list<Expr*> _arglist);
~Expr() { ~Expr() {
for (auto& arg_ptr : args) { for (auto& arg_ptr : args) {
delete arg_ptr; delete arg_ptr;
@ -953,6 +981,9 @@ struct Expr {
bool is_type() const { bool is_type() const {
return flags & _IsType; return flags & _IsType;
} }
bool is_inside_parenthesis() const {
return flags & _IsInsideParenthesis;
}
bool is_type_apply() const { bool is_type_apply() const {
return cls == _TypeApply; return cls == _TypeApply;
} }
@ -972,7 +1003,6 @@ struct Expr {
int define_new_vars(CodeBlob& code); int define_new_vars(CodeBlob& code);
int predefine_vars(); int predefine_vars();
std::vector<var_idx_t> pre_compile(CodeBlob& code, std::vector<std::pair<SymDef*, var_idx_t>>* lval_globs = nullptr) const; std::vector<var_idx_t> pre_compile(CodeBlob& code, std::vector<std::pair<SymDef*, var_idx_t>>* lval_globs = nullptr) const;
static std::vector<var_idx_t> pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rhs, const SrcLocation& here);
var_idx_t new_tmp(CodeBlob& code) const; var_idx_t new_tmp(CodeBlob& code) const;
std::vector<var_idx_t> new_tmp_vect(CodeBlob& code) const { std::vector<var_idx_t> new_tmp_vect(CodeBlob& code) const {
return {new_tmp(code)}; return {new_tmp(code)};
@ -993,9 +1023,9 @@ using Const = td::RefInt256;
struct AsmOp { struct AsmOp {
enum Type { a_none, a_xchg, a_push, a_pop, a_const, a_custom, a_magic }; 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 indent{0};
int a, b, c; int a, b;
bool gconst{false}; bool gconst{false};
std::string op; std::string op;
td::RefInt256 origin; td::RefInt256 origin;
@ -1005,26 +1035,22 @@ struct AsmOp {
} }
}; };
AsmOp() = default; 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(); 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(); 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(std::ostream& os) const;
void out_indent_nl(std::ostream& os, bool no_nl = false) const; void out_indent_nl(std::ostream& os, bool no_nl = false) const;
std::string to_string() const; std::string to_string() const;
@ -1680,8 +1706,8 @@ inline simple_compile_func_t make_simple_compile(AsmOp op) {
return [op](std::vector<VarDescr>& out, std::vector<VarDescr>& in, const SrcLocation&) -> AsmOp { return op; }; return [op](std::vector<VarDescr>& out, std::vector<VarDescr>& in, const SrcLocation&) -> AsmOp { return op; };
} }
inline compile_func_t make_ext_compile(std::vector<AsmOp> ops) { inline compile_func_t make_ext_compile(std::vector<AsmOp>&& ops) {
return [ops = std::move(ops)](AsmOpList & dest, std::vector<VarDescr> & out, std::vector<VarDescr> & in)->bool { return [ops = std::move(ops)](AsmOpList& dest, std::vector<VarDescr>& out, std::vector<VarDescr>& in)->bool {
return dest.append(ops); return dest.append(ops);
}; };
} }
@ -1696,25 +1722,22 @@ struct SymValAsmFunc : SymValFunc {
compile_func_t ext_compile; compile_func_t ext_compile;
td::uint64 crc; td::uint64 crc;
~SymValAsmFunc() override = default; ~SymValAsmFunc() override = default;
SymValAsmFunc(TypeExpr* ft, const AsmOp& _macro, bool impure = false) SymValAsmFunc(TypeExpr* ft, std::vector<AsmOp>&& _macro, bool marked_as_pure)
: SymValFunc(-1, ft, impure), simple_compile(make_simple_compile(_macro)) { : SymValFunc(-1, ft, marked_as_pure), ext_compile(make_ext_compile(std::move(_macro))) {
} }
SymValAsmFunc(TypeExpr* ft, std::vector<AsmOp> _macro, bool impure = false) SymValAsmFunc(TypeExpr* ft, simple_compile_func_t _compile, bool marked_as_pure)
: SymValFunc(-1, ft, impure), ext_compile(make_ext_compile(std::move(_macro))) { : SymValFunc(-1, ft, marked_as_pure), simple_compile(std::move(_compile)) {
} }
SymValAsmFunc(TypeExpr* ft, simple_compile_func_t _compile, bool impure = false) SymValAsmFunc(TypeExpr* ft, compile_func_t _compile, bool marked_as_pure)
: SymValFunc(-1, ft, impure), simple_compile(std::move(_compile)) { : SymValFunc(-1, ft, marked_as_pure), ext_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, simple_compile_func_t _compile, std::initializer_list<int> arg_order, SymValAsmFunc(TypeExpr* ft, simple_compile_func_t _compile, std::initializer_list<int> arg_order,
std::initializer_list<int> ret_order = {}, bool impure = false) std::initializer_list<int> ret_order = {}, bool marked_as_pure = false)
: SymValFunc(-1, ft, arg_order, ret_order, impure), simple_compile(std::move(_compile)) { : 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<int> arg_order, SymValAsmFunc(TypeExpr* ft, compile_func_t _compile, std::initializer_list<int> arg_order,
std::initializer_list<int> ret_order = {}, bool impure = false) std::initializer_list<int> ret_order = {}, bool marked_as_pure = false)
: SymValFunc(-1, ft, arg_order, ret_order, impure), ext_compile(std::move(_compile)) { : SymValFunc(-1, ft, arg_order, ret_order, marked_as_pure), ext_compile(std::move(_compile)) {
} }
bool compile(AsmOpList& dest, std::vector<VarDescr>& out, std::vector<VarDescr>& in, const SrcLocation& where) const; bool compile(AsmOpList& dest, std::vector<VarDescr>& out, std::vector<VarDescr>& in, const SrcLocation& where) const;
}; };
@ -1747,30 +1770,17 @@ class GlobalPragma {
bool enabled() const { bool enabled() const {
return enabled_; return enabled_;
} }
void enable(SrcLocation loc) { void enable(SrcLocation loc);
enabled_ = true; void check_enable_in_libs();
locs_.push_back(std::move(loc)); void always_on_and_deprecated(const char *deprecated_from_v);
}
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.");
}
private: private:
std::string name_; std::string name_;
bool enabled_ = false; bool enabled_ = false;
const char *deprecated_from_v_ = nullptr;
std::vector<SrcLocation> locs_; std::vector<SrcLocation> 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;
/* /*
* *

View file

@ -113,6 +113,39 @@ int TypeExpr::extract_components(std::vector<TypeExpr*>& comp_list) {
return res; 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<int>(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) { TypeExpr* TypeExpr::new_map(TypeExpr* from, TypeExpr* to) {
return new TypeExpr{te_Map, std::vector<TypeExpr*>{from, to}}; return new TypeExpr{te_Map, std::vector<TypeExpr*>{from, to}};
} }
@ -207,7 +240,7 @@ std::ostream& operator<<(std::ostream& os, TypeExpr* type_expr) {
return type_expr->print(os); 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) { switch (constr) {
case te_Unknown: case te_Unknown:
return os << "??" << value; return os << "??" << value;

View file

@ -386,7 +386,7 @@ class TonlibCli : public td::actor::Actor {
td::TerminalIO::out() << "sendfile <filename>\tLoad a serialized message from <filename> and send it to server\n"; td::TerminalIO::out() << "sendfile <filename>\tLoad a serialized message from <filename> and send it to server\n";
td::TerminalIO::out() << "setconfig|validateconfig <path> [<name>] [<use_callback>] [<force>] - set or validate " td::TerminalIO::out() << "setconfig|validateconfig <path> [<name>] [<use_callback>] [<force>] - set or validate "
"lite server config\n"; "lite server config\n";
td::TerminalIO::out() << "runmethod <addr> <method-id> <params>...\tRuns GET method <method-id> of account " td::TerminalIO::out() << "runmethod <addr> <name> <params>...\tRuns GET method <name> of account "
"<addr> with specified parameters\n"; "<addr> with specified parameters\n";
td::TerminalIO::out() << "getstate <key_id>\tget state of wallet with requested key\n"; td::TerminalIO::out() << "getstate <key_id>\tget state of wallet with requested key\n";
td::TerminalIO::out() << "getstatebytransaction <key_id> <lt> <hash>\tget state of wallet with requested key after transaction with local time <lt> and hash <hash> (base64url)\n"; td::TerminalIO::out() << "getstatebytransaction <key_id> <lt> <hash>\tget state of wallet with requested key after transaction with local time <lt> and hash <hash> (base64url)\n";