/* This file is part of TON Blockchain Library. TON Blockchain Library is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. TON Blockchain Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . */ #include "tolk.h" #include "src-file.h" #include "ast.h" #include "ast-visitor.h" #include "type-system.h" #include "common/refint.h" #include "constant-evaluator.h" /* * This pipe is the last one operating AST: it transforms AST to IR. * IR is described as "Op" struct. So, here AST is transformed to Ops, and then all the rest "legacy" * kernel (initially forked from FunC) comes into play. * Up to this point, all types have been inferred, all validity checks have been passed, etc. * All properties in AST nodes are assigned and can be safely used (fun_ref, etc.). * So, if execution reaches this pass, the input is correct, and code generation should succeed. */ namespace tolk { struct LValGlobs { std::vector> globs; void add_modified_glob(const GlobalVarData* g_sym, var_idx_t local_ir_idx) { globs.emplace_back(g_sym, local_ir_idx); } void gen_ops_set_globs(CodeBlob& code, SrcLocation loc) const { for (const auto& [g_sym, ir_idx] : globs) { Op& op = code.emplace_back(loc, Op::_SetGlob, std::vector{}, std::vector{ ir_idx }, g_sym); op.set_impure_flag(); } } }; std::vector pre_compile_expr(AnyExprV v, CodeBlob& code, LValGlobs* lval_globs = nullptr); void process_any_statement(AnyV v, CodeBlob& code); static std::vector> pre_compile_tensor_inner(CodeBlob& code, const std::vector& args, LValGlobs* lval_globs) { const int n = static_cast(args.size()); if (n == 0) { // just `()` return {}; } if (n == 1) { // just `(x)`: even if x is modified (e.g. `f(x=x+2)`), there are no next arguments return {pre_compile_expr(args[0], code, lval_globs)}; } // the purpose is to handle such cases: `return (x, x += y, x)` // without this, ops will be { _Call $2 = +($0_x, $1_y); _Return $0_x, $2, $0_x } - invalid // with this, ops will be { _Let $3 = $0_x; _Call $2 = ...; _Return $3, $2, $0_x } - valid, tmp var for x // how it works: for every arg, after transforming to ops, start tracking ir_idx inside it // on modification attempt, create Op::_Let to a tmp var and replace old ir_idx with tmp_idx in result struct WatchingVarList { std::vector watched_vars; std::vector> res_lists; explicit WatchingVarList(int n_args) { res_lists.reserve(n_args); } bool is_watched(var_idx_t ir_idx) const { return std::find(watched_vars.begin(), watched_vars.end(), ir_idx) != watched_vars.end(); } void add_and_watch_modifications(std::vector&& vars_of_ith_arg, CodeBlob& code) { for (var_idx_t ir_idx : vars_of_ith_arg) { if (code.vars[ir_idx].v_sym && !is_watched(ir_idx)) { watched_vars.emplace_back(ir_idx); code.vars[ir_idx].on_modification.emplace_back([this, &code, ir_idx](SrcLocation loc) { on_var_modified(ir_idx, loc, code); }); } } res_lists.emplace_back(std::move(vars_of_ith_arg)); } void on_var_modified(var_idx_t ir_idx, SrcLocation loc, CodeBlob& code) { tolk_assert(is_watched(ir_idx)); var_idx_t tmp_idx = code.create_tmp_var(code.vars[ir_idx].v_type, loc); code.emplace_back(loc, Op::_Let, std::vector{tmp_idx}, std::vector{ir_idx}); for (std::vector& prev_vars : res_lists) { std::replace(prev_vars.begin(), prev_vars.end(), ir_idx, tmp_idx); } } std::vector> clear_and_stop_watching(CodeBlob& code) { for (var_idx_t ir_idx : watched_vars) { code.vars[ir_idx].on_modification.pop_back(); } watched_vars.clear(); return std::move(res_lists); } }; WatchingVarList watched_vars(n); for (int arg_idx = 0; arg_idx < n; ++arg_idx) { std::vector vars_of_ith_arg = pre_compile_expr(args[arg_idx], code, lval_globs); watched_vars.add_and_watch_modifications(std::move(vars_of_ith_arg), code); } return watched_vars.clear_and_stop_watching(code); } static std::vector pre_compile_tensor(CodeBlob& code, const std::vector& args, LValGlobs* lval_globs = nullptr) { std::vector> res_lists = pre_compile_tensor_inner(code, args, lval_globs); std::vector res; for (const std::vector& list : res_lists) { res.insert(res.end(), list.cbegin(), list.cend()); } return res; } static std::vector pre_compile_let(CodeBlob& code, AnyExprV lhs, AnyExprV rhs, SrcLocation loc) { // [lhs] = [rhs]; since type checking is ok, it's the same as "lhs = rhs" if (lhs->type == ast_typed_tuple && rhs->type == ast_typed_tuple) { std::vector right = pre_compile_tensor(code, rhs->as()->get_items()); LValGlobs globs; std::vector left = pre_compile_tensor(code, lhs->as()->get_items(), &globs); code.on_var_modification(left, loc); code.emplace_back(loc, Op::_Let, std::move(left), right); globs.gen_ops_set_globs(code, loc); return right; } // [lhs] = rhs; it's un-tuple to N left vars if (lhs->type == ast_typed_tuple) { std::vector right = pre_compile_expr(rhs, code); const TypeDataTypedTuple* inferred_tuple = rhs->inferred_type->try_as(); std::vector types_list = inferred_tuple->items; std::vector rvect = {code.create_tmp_var(TypeDataTensor::create(std::move(types_list)), rhs->loc)}; code.emplace_back(lhs->loc, Op::_UnTuple, rvect, std::move(right)); LValGlobs globs; std::vector left = pre_compile_tensor(code, lhs->as()->get_items(), &globs); code.on_var_modification(left, loc); code.emplace_back(loc, Op::_Let, std::move(left), rvect); globs.gen_ops_set_globs(code, loc); return rvect; } // lhs = rhs std::vector right = pre_compile_expr(rhs, code); LValGlobs globs; std::vector left = pre_compile_expr(lhs, code, &globs); code.on_var_modification(left, loc); code.emplace_back(loc, Op::_Let, std::move(left), right); globs.gen_ops_set_globs(code, loc); return right; } static std::vector gen_op_call(CodeBlob& code, TypePtr ret_type, SrcLocation here, std::vector&& args_vars, const FunctionData* fun_ref) { std::vector rvect = {code.create_tmp_var(ret_type, here)}; Op& op = code.emplace_back(here, Op::_Call, rvect, std::move(args_vars), fun_ref); if (!fun_ref->is_marked_as_pure()) { op.set_impure_flag(); } return rvect; } static std::vector process_symbol(SrcLocation loc, const Symbol* sym, CodeBlob& code, LValGlobs* lval_globs) { if (const auto* glob_ref = sym->try_as()) { std::vector rvect = {code.create_tmp_var(glob_ref->declared_type, loc)}; if (lval_globs) { lval_globs->add_modified_glob(glob_ref, rvect[0]); return rvect; } else { code.emplace_back(loc, Op::_GlobVar, rvect, std::vector{}, glob_ref); return rvect; } } if (const auto* const_ref = sym->try_as()) { if (const_ref->is_int_const()) { std::vector rvect = {code.create_tmp_var(TypeDataInt::create(), loc)}; code.emplace_back(loc, Op::_IntConst, rvect, const_ref->as_int_const()); return rvect; } else { std::vector rvect = {code.create_tmp_var(TypeDataSlice::create(), loc)}; code.emplace_back(loc, Op::_SliceConst, rvect, const_ref->as_slice_const()); return rvect; } } if (const auto* fun_ref = sym->try_as()) { std::vector rvect = {code.create_tmp_var(fun_ref->inferred_full_type, loc)}; code.emplace_back(loc, Op::_GlobVar, rvect, std::vector{}, fun_ref); return rvect; } if (const auto* var_ref = sym->try_as()) { return {var_ref->idx}; } throw Fatal("process_symbol"); } static std::vector process_assign(V v, CodeBlob& code) { if (auto lhs_decl = v->get_lhs()->try_as()) { return pre_compile_let(code, lhs_decl->get_expr(), v->get_rhs(), v->loc); } else { return pre_compile_let(code, v->get_lhs(), v->get_rhs(), v->loc); } } static std::vector process_set_assign(V v, CodeBlob& code) { // for "a += b", emulate "a = a + b" // seems not beautiful, but it works; probably, this transformation should be done at AST level in advance std::string_view calc_operator = v->operator_name; // "+" for operator += auto v_apply = createV(v->loc, calc_operator, static_cast(v->tok - 1), v->get_lhs(), v->get_rhs()); v_apply->assign_inferred_type(v->inferred_type); v_apply->assign_fun_ref(v->fun_ref); return pre_compile_let(code, v->get_lhs(), v_apply, v->loc); } static std::vector process_binary_operator(V v, CodeBlob& code) { TokenType t = v->tok; if (v->fun_ref) { // almost all operators, fun_ref was assigned at type inferring std::vector args_vars = pre_compile_tensor(code, {v->get_lhs(), v->get_rhs()}); return gen_op_call(code, v->inferred_type, v->loc, std::move(args_vars), v->fun_ref); } if (t == tok_logical_and || t == tok_logical_or) { // do the following transformations: // a && b -> a ? (b != 0) : 0 // a || b -> a ? 1 : (b != 0) AnyExprV v_0 = createV(v->loc, td::make_refint(0), "0"); v_0->mutate()->assign_inferred_type(TypeDataInt::create()); AnyExprV v_1 = createV(v->loc, td::make_refint(-1), "-1"); v_1->mutate()->assign_inferred_type(TypeDataInt::create()); auto v_b_ne_0 = createV(v->loc, "!=", tok_neq, v->get_rhs(), v_0); v_b_ne_0->mutate()->assign_inferred_type(TypeDataInt::create()); v_b_ne_0->mutate()->assign_fun_ref(lookup_global_symbol("_!=_")->as()); std::vector cond = pre_compile_expr(v->get_lhs(), code); tolk_assert(cond.size() == 1); std::vector rvect = {code.create_tmp_var(v->inferred_type, v->loc)}; Op& if_op = code.emplace_back(v->loc, Op::_If, cond); code.push_set_cur(if_op.block0); code.emplace_back(v->loc, Op::_Let, rvect, pre_compile_expr(t == tok_logical_and ? v_b_ne_0 : v_1, code)); code.close_pop_cur(v->loc); code.push_set_cur(if_op.block1); code.emplace_back(v->loc, Op::_Let, rvect, pre_compile_expr(t == tok_logical_and ? v_0 : v_b_ne_0, code)); code.close_pop_cur(v->loc); return rvect; } throw UnexpectedASTNodeType(v, "process_binary_operator"); } static std::vector process_unary_operator(V v, CodeBlob& code) { std::vector args_vars = pre_compile_tensor(code, {v->get_rhs()}); return gen_op_call(code, v->inferred_type, v->loc, std::move(args_vars), v->fun_ref); } static std::vector process_ternary_operator(V v, CodeBlob& code) { std::vector cond = pre_compile_expr(v->get_cond(), code); tolk_assert(cond.size() == 1); std::vector rvect = {code.create_tmp_var(v->inferred_type, v->loc)}; Op& if_op = code.emplace_back(v->loc, Op::_If, cond); code.push_set_cur(if_op.block0); code.emplace_back(v->get_when_true()->loc, Op::_Let, rvect, pre_compile_expr(v->get_when_true(), code)); code.close_pop_cur(v->get_when_true()->loc); code.push_set_cur(if_op.block1); code.emplace_back(v->get_when_false()->loc, Op::_Let, rvect, pre_compile_expr(v->get_when_false(), code)); code.close_pop_cur(v->get_when_false()->loc); return rvect; } static std::vector process_dot_access(V v, CodeBlob& code, LValGlobs* lval_globs) { // it's NOT a method call `t.tupleSize()` (since such cases are handled by process_function_call) // it's `t.0`, `getUser().id`, and `t.tupleSize` (as a reference, not as a call) // currently, nothing except a global function can be a target of dot access const FunctionData* fun_ref = v->target; tolk_assert(fun_ref); return process_symbol(v->loc, fun_ref, code, lval_globs); } static std::vector process_function_call(V v, CodeBlob& code) { // v is `globalF(args)` / `globalF(args)` / `obj.method(args)` / `local_var(args)` / `getF()(args)` const FunctionData* fun_ref = v->fun_maybe; if (!fun_ref) { std::vector args; args.reserve(v->get_num_args()); for (int i = 0; i < v->get_num_args(); ++i) { args.push_back(v->get_arg(i)->get_expr()); } std::vector args_vars = pre_compile_tensor(code, args); std::vector tfunc = pre_compile_expr(v->get_callee(), code); tolk_assert(tfunc.size() == 1); args_vars.push_back(tfunc[0]); std::vector rvect = {code.create_tmp_var(v->inferred_type, v->loc)}; Op& op = code.emplace_back(v->loc, Op::_CallInd, rvect, std::move(args_vars)); op.set_impure_flag(); return rvect; } int delta_self = v->is_dot_call(); AnyExprV obj_leftmost = nullptr; std::vector args; args.reserve(delta_self + v->get_num_args()); if (delta_self) { args.push_back(v->get_dot_obj()); obj_leftmost = v->get_dot_obj(); while (obj_leftmost->type == ast_function_call && obj_leftmost->as()->is_dot_call() && obj_leftmost->as()->fun_maybe && obj_leftmost->as()->fun_maybe->does_return_self()) { obj_leftmost = obj_leftmost->as()->get_dot_obj(); } } for (int i = 0; i < v->get_num_args(); ++i) { args.push_back(v->get_arg(i)->get_expr()); } std::vector> vars_per_arg = pre_compile_tensor_inner(code, args, nullptr); TypePtr op_call_type = v->inferred_type; TypePtr real_ret_type = v->inferred_type; if (delta_self && fun_ref->does_return_self()) { real_ret_type = TypeDataVoid::create(); if (!fun_ref->parameters[0].is_mutate_parameter()) { op_call_type = TypeDataVoid::create(); } } if (fun_ref->has_mutate_params()) { std::vector types_list; for (int i = 0; i < delta_self + v->get_num_args(); ++i) { if (fun_ref->parameters[i].is_mutate_parameter()) { types_list.push_back(args[i]->inferred_type); } } types_list.push_back(real_ret_type); op_call_type = TypeDataTensor::create(std::move(types_list)); } std::vector args_vars; for (const std::vector& list : vars_per_arg) { args_vars.insert(args_vars.end(), list.cbegin(), list.cend()); } std::vector rvect_apply = gen_op_call(code, op_call_type, v->loc, std::move(args_vars), fun_ref); if (fun_ref->has_mutate_params()) { LValGlobs local_globs; std::vector left; for (int i = 0; i < delta_self + v->get_num_args(); ++i) { if (fun_ref->parameters[i].is_mutate_parameter()) { AnyExprV arg_i = obj_leftmost && i == 0 ? obj_leftmost : args[i]; tolk_assert(arg_i->is_lvalue || i == 0); if (arg_i->is_lvalue) { std::vector ith_var_idx = pre_compile_expr(arg_i, code, &local_globs); left.insert(left.end(), ith_var_idx.begin(), ith_var_idx.end()); } else { left.insert(left.end(), vars_per_arg[0].begin(), vars_per_arg[0].end()); } } } std::vector rvect = {code.create_tmp_var(real_ret_type, v->loc)}; left.push_back(rvect[0]); code.on_var_modification(left, v->loc); code.emplace_back(v->loc, Op::_Let, std::move(left), rvect_apply); local_globs.gen_ops_set_globs(code, v->loc); rvect_apply = rvect; } if (obj_leftmost && fun_ref->does_return_self()) { if (obj_leftmost->is_lvalue) { // to handle if obj is global var, potentially re-assigned inside a chain rvect_apply = pre_compile_expr(obj_leftmost, code); } else { // temporary object, not lvalue, pre_compile_expr rvect_apply = vars_per_arg[0]; } } return rvect_apply; } static std::vector process_tensor(V v, CodeBlob& code, LValGlobs* lval_globs) { return pre_compile_tensor(code, v->get_items(), lval_globs); } static std::vector process_typed_tuple(V v, CodeBlob& code, LValGlobs* lval_globs) { if (lval_globs) { // todo some time, make "var (a, [b,c]) = (1, [2,3])" work v->error("[...] can not be used as lvalue here"); } std::vector left = std::vector{code.create_tmp_var(v->inferred_type, v->loc)}; std::vector right = pre_compile_tensor(code, v->get_items()); code.emplace_back(v->loc, Op::_Tuple, left, std::move(right)); return left; } static std::vector process_int_const(V v, CodeBlob& code) { std::vector rvect = {code.create_tmp_var(v->inferred_type, v->loc)}; code.emplace_back(v->loc, Op::_IntConst, rvect, v->intval); return rvect; } static std::vector process_string_const(V v, CodeBlob& code) { ConstantValue value = eval_const_init_value(v); std::vector rvect = {code.create_tmp_var(v->inferred_type, v->loc)}; if (value.is_int()) { code.emplace_back(v->loc, Op::_IntConst, rvect, value.as_int()); } else { code.emplace_back(v->loc, Op::_SliceConst, rvect, value.as_slice()); } return rvect; } static std::vector process_bool_const(V v, CodeBlob& code) { const FunctionData* builtin_sym = lookup_global_symbol(v->bool_val ? "__true" : "__false")->as(); return gen_op_call(code, v->inferred_type, v->loc, {}, builtin_sym); } static std::vector process_null_keyword(V v, CodeBlob& code) { const FunctionData* builtin_sym = lookup_global_symbol("__null")->as(); return gen_op_call(code, v->inferred_type, v->loc, {}, builtin_sym); } static std::vector process_local_var(V v, CodeBlob& code) { if (v->marked_as_redef) { return process_symbol(v->loc, v->var_ref, code, nullptr); } tolk_assert(v->var_ref->idx == -1); v->var_ref->mutate()->assign_idx(code.create_var(v->inferred_type, v->var_ref, v->loc)); return {v->var_ref->idx}; } static std::vector process_local_vars_declaration(V, CodeBlob&) { // it can not appear as a standalone expression // `var ... = rhs` is handled by ast_assign tolk_assert(false); } static std::vector process_underscore(V v, CodeBlob& code) { // when _ is used as left side of assignment, like `(cs, _) = cs.loadAndReturn()` return {code.create_tmp_var(v->inferred_type, v->loc)}; } std::vector pre_compile_expr(AnyExprV v, CodeBlob& code, LValGlobs* lval_globs) { switch (v->type) { case ast_reference: return process_symbol(v->loc, v->as()->sym, code, lval_globs); case ast_assign: return process_assign(v->as(), code); case ast_set_assign: return process_set_assign(v->as(), code); case ast_binary_operator: return process_binary_operator(v->as(), code); case ast_unary_operator: return process_unary_operator(v->as(), code); case ast_ternary_operator: return process_ternary_operator(v->as(), code); case ast_cast_as_operator: return pre_compile_expr(v->as()->get_expr(), code, lval_globs); case ast_dot_access: return process_dot_access(v->as(), code, lval_globs); case ast_function_call: return process_function_call(v->as(), code); case ast_parenthesized_expression: return pre_compile_expr(v->as()->get_expr(), code, lval_globs); case ast_tensor: return process_tensor(v->as(), code, lval_globs); case ast_typed_tuple: return process_typed_tuple(v->as(), code, lval_globs); case ast_int_const: return process_int_const(v->as(), code); case ast_string_const: return process_string_const(v->as(), code); case ast_bool_const: return process_bool_const(v->as(), code); case ast_null_keyword: return process_null_keyword(v->as(), code); case ast_local_var_lhs: return process_local_var(v->as(), code); case ast_local_vars_declaration: return process_local_vars_declaration(v->as(), code); case ast_underscore: return process_underscore(v->as(), code); default: throw UnexpectedASTNodeType(v, "pre_compile_expr"); } } static void process_sequence(V v, CodeBlob& code) { for (AnyV item : v->get_items()) { process_any_statement(item, code); } } static void process_assert_statement(V v, CodeBlob& code) { std::vector args(3); if (auto v_not = v->get_cond()->try_as(); v_not && v_not->tok == tok_logical_not) { args[0] = v->get_thrown_code(); args[1] = v->get_cond()->as()->get_rhs(); args[2] = createV(v->loc, true); args[2]->mutate()->assign_inferred_type(TypeDataInt::create()); } else { args[0] = v->get_thrown_code(); args[1] = v->get_cond(); args[2] = createV(v->loc, false); args[2]->mutate()->assign_inferred_type(TypeDataInt::create()); } const FunctionData* builtin_sym = lookup_global_symbol("__throw_if_unless")->as(); std::vector args_vars = pre_compile_tensor(code, args); gen_op_call(code, TypeDataVoid::create(), v->loc, std::move(args_vars), builtin_sym); } static void process_catch_variable(AnyExprV v_catch_var, CodeBlob& code) { if (auto v_ref = v_catch_var->try_as(); v_ref && v_ref->sym) { // not underscore const LocalVarData* var_ref = v_ref->sym->as(); tolk_assert(var_ref->idx == -1); var_ref->mutate()->assign_idx(code.create_var(v_catch_var->inferred_type, var_ref, v_catch_var->loc)); } } static void process_try_catch_statement(V v, CodeBlob& code) { code.require_callxargs = true; Op& try_catch_op = code.emplace_back(v->loc, Op::_TryCatch); code.push_set_cur(try_catch_op.block0); process_any_statement(v->get_try_body(), code); code.close_pop_cur(v->get_try_body()->loc_end); code.push_set_cur(try_catch_op.block1); // transform catch (excNo, arg) into TVM-catch (arg, excNo), where arg is untyped and thus almost useless now const std::vector& catch_vars = v->get_catch_expr()->get_items(); tolk_assert(catch_vars.size() == 2); process_catch_variable(catch_vars[0], code); process_catch_variable(catch_vars[1], code); try_catch_op.left = pre_compile_tensor(code, {catch_vars[1], catch_vars[0]}); process_any_statement(v->get_catch_body(), code); code.close_pop_cur(v->get_catch_body()->loc_end); } static void process_repeat_statement(V v, CodeBlob& code) { std::vector tmp_vars = pre_compile_expr(v->get_cond(), code); Op& repeat_op = code.emplace_back(v->loc, Op::_Repeat, tmp_vars); code.push_set_cur(repeat_op.block0); process_any_statement(v->get_body(), code); code.close_pop_cur(v->get_body()->loc_end); } static void process_if_statement(V v, CodeBlob& code) { std::vector tmp_vars = pre_compile_expr(v->get_cond(), code); Op& if_op = code.emplace_back(v->loc, Op::_If, std::move(tmp_vars)); code.push_set_cur(if_op.block0); process_any_statement(v->get_if_body(), code); code.close_pop_cur(v->get_if_body()->loc_end); code.push_set_cur(if_op.block1); process_any_statement(v->get_else_body(), code); code.close_pop_cur(v->get_else_body()->loc_end); if (v->is_ifnot) { std::swap(if_op.block0, if_op.block1); } } static void process_do_while_statement(V v, CodeBlob& code) { Op& until_op = code.emplace_back(v->loc, Op::_Until); code.push_set_cur(until_op.block0); process_any_statement(v->get_body(), code); // in TVM, there is only "do until", but in Tolk, we want "do while" // here we negate condition to pass it forward to legacy to Op::_Until // also, handle common situations as a hardcoded "optimization": replace (a<0) with (a>=0) and so on // todo these hardcoded conditions should be removed from this place in the future AnyExprV cond = v->get_cond(); AnyExprV until_cond; if (auto v_not = cond->try_as(); v_not && v_not->tok == tok_logical_not) { until_cond = v_not->get_rhs(); } else if (auto v_eq = cond->try_as(); v_eq && v_eq->tok == tok_eq) { until_cond = createV(cond->loc, "!=", tok_neq, v_eq->get_lhs(), v_eq->get_rhs()); } else if (auto v_neq = cond->try_as(); v_neq && v_neq->tok == tok_neq) { until_cond = createV(cond->loc, "==", tok_eq, v_neq->get_lhs(), v_neq->get_rhs()); } else if (auto v_leq = cond->try_as(); v_leq && v_leq->tok == tok_leq) { until_cond = createV(cond->loc, ">", tok_gt, v_leq->get_lhs(), v_leq->get_rhs()); } else if (auto v_lt = cond->try_as(); v_lt && v_lt->tok == tok_lt) { until_cond = createV(cond->loc, ">=", tok_geq, v_lt->get_lhs(), v_lt->get_rhs()); } else if (auto v_geq = cond->try_as(); v_geq && v_geq->tok == tok_geq) { until_cond = createV(cond->loc, "<", tok_lt, v_geq->get_lhs(), v_geq->get_rhs()); } else if (auto v_gt = cond->try_as(); v_gt && v_gt->tok == tok_gt) { until_cond = createV(cond->loc, "<=", tok_geq, v_gt->get_lhs(), v_gt->get_rhs()); } else if (cond->inferred_type == TypeDataBool::create()) { until_cond = createV(cond->loc, "!b", tok_logical_not, cond); } else { until_cond = createV(cond->loc, "!", tok_logical_not, cond); } until_cond->mutate()->assign_inferred_type(TypeDataInt::create()); if (auto v_bin = until_cond->try_as(); v_bin && !v_bin->fun_ref) { v_bin->mutate()->assign_fun_ref(lookup_global_symbol("_" + static_cast(v_bin->operator_name) + "_")->as()); } else if (auto v_un = until_cond->try_as(); v_un && !v_un->fun_ref) { v_un->mutate()->assign_fun_ref(lookup_global_symbol(static_cast(v_un->operator_name) + "_")->as()); } until_op.left = pre_compile_expr(until_cond, code); code.close_pop_cur(v->get_body()->loc_end); } static void process_while_statement(V v, CodeBlob& code) { Op& while_op = code.emplace_back(v->loc, Op::_While); code.push_set_cur(while_op.block0); while_op.left = pre_compile_expr(v->get_cond(), code); code.close_pop_cur(v->get_body()->loc); code.push_set_cur(while_op.block1); process_any_statement(v->get_body(), code); code.close_pop_cur(v->get_body()->loc_end); } static void process_throw_statement(V v, CodeBlob& code) { if (v->has_thrown_arg()) { const FunctionData* builtin_sym = lookup_global_symbol("__throw_arg")->as(); std::vector args_vars = pre_compile_tensor(code, {v->get_thrown_arg(), v->get_thrown_code()}); gen_op_call(code, TypeDataVoid::create(), v->loc, std::move(args_vars), builtin_sym); } else { const FunctionData* builtin_sym = lookup_global_symbol("__throw")->as(); std::vector args_vars = pre_compile_tensor(code, {v->get_thrown_code()}); gen_op_call(code, TypeDataVoid::create(), v->loc, std::move(args_vars), builtin_sym); } } static void process_return_statement(V v, CodeBlob& code) { std::vector return_vars = v->has_return_value() ? pre_compile_expr(v->get_return_value(), code) : std::vector{}; if (code.fun_ref->does_return_self()) { tolk_assert(return_vars.size() == 1); return_vars = {}; } if (code.fun_ref->has_mutate_params()) { std::vector mutated_vars; for (const LocalVarData& p_sym: code.fun_ref->parameters) { if (p_sym.is_mutate_parameter()) { mutated_vars.push_back(p_sym.idx); } } return_vars.insert(return_vars.begin(), mutated_vars.begin(), mutated_vars.end()); } code.emplace_back(v->loc, Op::_Return, std::move(return_vars)); } static void append_implicit_return_statement(SrcLocation loc_end, CodeBlob& code) { std::vector mutated_vars; if (code.fun_ref->has_mutate_params()) { for (const LocalVarData& p_sym: code.fun_ref->parameters) { if (p_sym.is_mutate_parameter()) { mutated_vars.push_back(p_sym.idx); } } } code.emplace_back(loc_end, Op::_Return, std::move(mutated_vars)); } void process_any_statement(AnyV v, CodeBlob& code) { switch (v->type) { case ast_sequence: return process_sequence(v->as(), code); case ast_return_statement: return process_return_statement(v->as(), code); case ast_repeat_statement: return process_repeat_statement(v->as(), code); case ast_if_statement: return process_if_statement(v->as(), code); case ast_do_while_statement: return process_do_while_statement(v->as(), code); case ast_while_statement: return process_while_statement(v->as(), code); case ast_throw_statement: return process_throw_statement(v->as(), code); case ast_assert_statement: return process_assert_statement(v->as(), code); case ast_try_catch_statement: return process_try_catch_statement(v->as(), code); case ast_empty_statement: return; default: pre_compile_expr(reinterpret_cast(v), code); } } static void convert_function_body_to_CodeBlob(const FunctionData* fun_ref, FunctionBodyCode* code_body) { auto v_body = fun_ref->ast_root->as()->get_body()->as(); CodeBlob* blob = new CodeBlob{fun_ref->name, fun_ref->loc, fun_ref}; FormalArgList legacy_arg_list; for (const LocalVarData& param : fun_ref->parameters) { legacy_arg_list.emplace_back(param.declared_type, ¶m, param.loc); } blob->import_params(std::move(legacy_arg_list)); for (AnyV item : v_body->get_items()) { process_any_statement(item, *blob); } if (fun_ref->is_implicit_return()) { append_implicit_return_statement(v_body->loc_end, *blob); } blob->close_blk(v_body->loc_end); code_body->set_code(blob); } static void convert_asm_body_to_AsmOp(const FunctionData* fun_ref, FunctionBodyAsm* asm_body) { int cnt = fun_ref->get_num_params(); int width = fun_ref->inferred_return_type->calc_width_on_stack(); std::vector asm_ops; for (AnyV v_child : fun_ref->ast_root->as()->get_body()->as()->get_asm_commands()) { std::string_view ops = v_child->as()->str_val; // \n\n... std::string op; for (char c : ops) { if (c == '\n' || c == '\r') { if (!op.empty()) { asm_ops.push_back(AsmOp::Parse(op, cnt, width)); if (asm_ops.back().is_custom()) { cnt = width; } op.clear(); } } else { op.push_back(c); } } if (!op.empty()) { asm_ops.push_back(AsmOp::Parse(op, cnt, width)); if (asm_ops.back().is_custom()) { cnt = width; } } } asm_body->set_code(std::move(asm_ops)); } class UpdateArgRetOrderConsideringStackWidth final { public: static bool should_visit_function(const FunctionData* fun_ref) { return !fun_ref->is_generic_function() && (!fun_ref->ret_order.empty() || !fun_ref->arg_order.empty()); } static void start_visiting_function(const FunctionData* fun_ref, V v_function) { int total_arg_mutate_width = 0; bool has_arg_width_not_1 = false; for (const LocalVarData& param : fun_ref->parameters) { int arg_width = param.declared_type->calc_width_on_stack(); has_arg_width_not_1 |= arg_width != 1; total_arg_mutate_width += param.is_mutate_parameter() * arg_width; } // example: `fun f(a: int, b: (int, (int, int)), c: int)` with `asm (b a c)` // current arg_order is [1 0 2] // needs to be converted to [1 2 3 0 4] because b width is 3 if (has_arg_width_not_1) { int total_arg_width = 0; std::vector cum_arg_width; cum_arg_width.reserve(1 + fun_ref->get_num_params()); cum_arg_width.push_back(0); for (const LocalVarData& param : fun_ref->parameters) { cum_arg_width.push_back(total_arg_width += param.declared_type->calc_width_on_stack()); } std::vector arg_order; for (int i = 0; i < fun_ref->get_num_params(); ++i) { int j = fun_ref->arg_order[i]; int c1 = cum_arg_width[j], c2 = cum_arg_width[j + 1]; while (c1 < c2) { arg_order.push_back(c1++); } } fun_ref->mutate()->assign_arg_order(std::move(arg_order)); } // example: `fun f(mutate self: slice): slice` with `asm(-> 1 0)` // ret_order is a shuffled range 0...N // validate N: a function should return value and mutated arguments onto a stack if (!fun_ref->ret_order.empty()) { size_t expected_width = fun_ref->inferred_return_type->calc_width_on_stack() + total_arg_mutate_width; if (expected_width != fun_ref->ret_order.size()) { v_function->get_body()->error("ret_order (after ->) expected to contain " + std::to_string(expected_width) + " numbers"); } } } }; class ConvertASTToLegacyOpVisitor final { public: static bool should_visit_function(const FunctionData* fun_ref) { return !fun_ref->is_generic_function(); } static void start_visiting_function(const FunctionData* fun_ref, V) { tolk_assert(fun_ref->is_type_inferring_done()); if (fun_ref->is_code_function()) { convert_function_body_to_CodeBlob(fun_ref, std::get(fun_ref->body)); } else if (fun_ref->is_asm_function()) { convert_asm_body_to_AsmOp(fun_ref, std::get(fun_ref->body)); } } }; void pipeline_convert_ast_to_legacy_Expr_Op() { visit_ast_of_all_functions(); visit_ast_of_all_functions(); } } // namespace tolk