From 3c239b3500fc09b4da0216edb8d3fd453c6eef81 Mon Sep 17 00:00:00 2001 From: tolk-vm Date: Thu, 31 Oct 2024 11:22:18 +0400 Subject: [PATCH] [Tolk] Implement logical operators && || Unary logical NOT was already implemented earlier. Logical AND OR are expressed via conditional expression: * a && b -> a ? (b != 0) : 0 * a || b -> a ? 1 : (b != 0) They work as expected in any expressions. For instance, having `cond && f()`, f is called only if cond is true. For primitive cases, like `a > 0 && b > 0`, Fift code is not optimal, it could potentially be without IFs. These are moments of future optimizations. For now, it's more than enough. --- tolk-tester/tests/invalid-logical-1.tolk | 8 -- tolk-tester/tests/logical-operators.tolk | 158 +++++++++++++++++++++++ tolk/pipe-ast-to-legacy.cpp | 80 ++++++------ tolk/symtable.h | 4 +- 4 files changed, 204 insertions(+), 46 deletions(-) delete mode 100644 tolk-tester/tests/invalid-logical-1.tolk diff --git a/tolk-tester/tests/invalid-logical-1.tolk b/tolk-tester/tests/invalid-logical-1.tolk deleted file mode 100644 index 9aa210bbd..000000000 --- a/tolk-tester/tests/invalid-logical-1.tolk +++ /dev/null @@ -1,8 +0,0 @@ -fun main() { - return 1 && 2; -} - -/** -@compilation_should_fail -@stderr logical operators are not supported yet - */ diff --git a/tolk-tester/tests/logical-operators.tolk b/tolk-tester/tests/logical-operators.tolk index ec0e7a87c..e9774f3f4 100644 --- a/tolk-tester/tests/logical-operators.tolk +++ b/tolk-tester/tests/logical-operators.tolk @@ -57,6 +57,72 @@ fun testNotNull(x: int) { return [x == null, null == x, !(x == null), null == null, +(null != null)]; } +@method_id(106) +fun testAndConstCodegen() { + return ( + [1 && 0, 0 && 1, 0 && 0, 1 && 1], + [4 && 3 && 0, 5 && 0 && 7 && 8, (7 && 0) && -19], + [4 && 3 && -1, 5 && -100 && 7 && 8, (7 && (1 + 2)) && -19], + [true && false, true && true] + ); +} + +@method_id(107) +fun testOrConstCodegen() { + return ( + [1 || 0, 0 || 1, 0 || 0, 1 || 1], + [0 || 0 || 0, 0 || (0 || 0), ((0 || 0) || 0) || 0], + [4 || 3 || -1, 0 || -100 || 0 || 0, (0 || (1 + -1)) || -19], + [true || false, false || false] + ); +} + +global eqCallsCnt: int; + +fun eq(x: int) { return x; } +fun eqCnt(x: int) { eqCallsCnt += 1; return x; } +fun isGt0(x: int) { return x > 0; } + +fun alwaysThrows(): int { throw 444 ; return 444; } + +@method_id(108) +fun testAndSimpleCodegen(a: int, b: int) { + return a && b; +} + +@method_id(109) +fun testOrSimpleCodegen(a: int, b: int) { + return a > 0 || b > 0; +} + +@method_id(110) +fun testLogicalOps1(x: int) { + eqCallsCnt = 0; + return ( + isGt0(x) || !isGt0(x) || alwaysThrows(), + x && eqCnt(x) && eqCnt(x - 1) && eqCnt(x - 2), + (400 == eq(x)) && alwaysThrows(), + (500 == eq(x)) || eqCnt(x) || false, + (500 == eq(x)) || eqCnt(x) || true, + eqCallsCnt + ); +} + +@method_id(111) +fun testLogicalOps2(first: int) { + var s = beginCell().storeInt(1, 32).storeInt(2, 32).storeInt(3, 32).storeInt(4, 32).storeInt(5, 32).endCell().beginParse(); + var sum = 0; + if (first && s.loadUint(32)) { + (2 == s.loadUint(32)) && (sum += s.loadUint(32)); + (3 == s.loadUint(32)) && (sum += s.loadUint(32)); + (5 == s.preloadUint(32)) && (sum += s.loadUint(32)); + } else { + (10 == s.loadUint(32)) || (20 == s.loadUint(32)) || (3 == s.loadUint(32)) || (4 == s.loadUint(32)); + sum += s.loadUint(32); + } + return (s.getRemainingBitsCount(), sum); +} + fun main() { } @@ -80,6 +146,19 @@ fun main() { @testcase | 104 | 0 | 3 -1 5 @testcase | 105 | 0 | [ 0 0 -1 -1 0 ] @testcase | 105 | null | [ -1 -1 0 -1 0 ] +@testcase | 106 | | [ 0 0 0 -1 ] [ 0 0 0 ] [ -1 -1 -1 ] [ 0 -1 ] +@testcase | 107 | | [ -1 -1 0 -1 ] [ 0 0 0 ] [ -1 -1 -1 ] [ -1 0 ] +@testcase | 108 | 1 2 | -1 +@testcase | 108 | 1 0 | 0 +@testcase | 109 | -5 -4 | 0 +@testcase | 109 | -5 4 | -1 +@testcase | 109 | 1 99 | -1 +@testcase | 110 | 0 | -1 0 0 0 -1 2 +@testcase | 110 | 1 | -1 0 0 -1 -1 4 +@testcase | 110 | 2 | -1 0 0 -1 -1 5 +@testcase | 110 | 500 | -1 -1 0 -1 -1 3 +@testcase | 111 | 0 | 32 4 +@testcase | 111 | -1 | 0 8 @fif_codegen """ @@ -134,4 +213,83 @@ fun main() { }> """ +@fif_codegen +""" + testAndConstCodegen PROC:<{ + // + FALSE + 0 PUSHINT + DUP + TRUE + 4 TUPLE + FALSE + 0 PUSHINT + DUP + TRIPLE + TRUE + TRUE + TRUE + TRIPLE + FALSE + TRUE + PAIR + }> +""" + +@fif_codegen +""" + testOrConstCodegen PROC:<{ + // + -1 PUSHINT + TRUE + FALSE + s2 PUSH + 4 TUPLE + FALSE + FALSE + FALSE + TRIPLE + -1 PUSHINT + DUP + TRUE + TRIPLE + -1 PUSHINT + FALSE + PAIR + }> +""" + +Currently, && operator is implemented via ?: and is not optimal in primitive cases. +For example, `a && b` can be expressed without IFs. +These are moments of future optimizations. For now, it's more than enough. +@fif_codegen +""" + testAndSimpleCodegen PROC:<{ + // a b + SWAP // b a + IF:<{ // b + 0 NEQINT // _2 + }>ELSE<{ // b + DROP // + 0 PUSHINT // _2=0 + }> + }> +""" + +@fif_codegen +""" + testOrSimpleCodegen PROC:<{ + // a b + SWAP // b a + 0 GTINT // b _3 + IF:<{ // b + DROP // + -1 PUSHINT // _4=-1 + }>ELSE<{ // b + 0 GTINT // _7 + 0 NEQINT // _4 + }> + }> +""" + */ diff --git a/tolk/pipe-ast-to-legacy.cpp b/tolk/pipe-ast-to-legacy.cpp index 050ef49d9..7257bfb07 100644 --- a/tolk/pipe-ast-to-legacy.cpp +++ b/tolk/pipe-ast-to-legacy.cpp @@ -160,6 +160,22 @@ static bool parse_raw_address(const std::string& acc_string, int& workchain, ton return true; } +static Expr* create_expr_apply(SrcLocation loc, SymDef* sym, std::vector&& args) { + Expr* apply = new Expr(Expr::_Apply, sym, std::move(args)); + apply->here = loc; + apply->flags = Expr::_IsRvalue; + apply->deduce_type(); + return apply; +} + +static Expr* create_expr_int_const(SrcLocation loc, int int_val) { + Expr* int_const = new Expr(Expr::_Const, loc); + int_const->intval = td::make_refint(int_val); + int_const->flags = Expr::_IsRvalue; + int_const->e_type = TypeExpr::new_atomic(TypeExpr::_Int); + return int_const; +} + namespace blk_fl { enum { end = 1, ret = 2, empty = 4 }; typedef int val; @@ -238,13 +254,10 @@ static Expr* process_expr(V v, CodeBlob& code) { if (x->is_immutable()) { x->fire_error_modifying_immutable("left side of assignment"); } - sym_idx_t name = G.symbols.lookup_add("^_" + operator_name + "_"); + SymDef* sym = lookup_symbol(calc_sym_idx("^_" + operator_name + "_")); Expr* y = process_expr(v->get_rhs(), code); y->chk_rvalue(); - Expr* z = new Expr{Expr::_Apply, name, {x, y}}; - z->here = v->loc; - z->flags = Expr::_IsRvalue; - z->deduce_type(); + Expr* z = create_expr_apply(v->loc, sym, {x, y}); Expr* res = new Expr{Expr::_Letop, {x->copy(), z}}; res->here = v->loc; res->flags = x->flags | Expr::_IsRvalue; @@ -276,17 +289,27 @@ static Expr* process_expr(V v, CodeBlob& code) { t == tok_mul || t == tok_div || t == tok_mod || t == tok_divC || t == tok_divR) { Expr* res = process_expr(v->get_lhs(), code); res->chk_rvalue(); - sym_idx_t name = G.symbols.lookup_add("_" + operator_name + "_"); + SymDef* sym = lookup_symbol(calc_sym_idx("_" + operator_name + "_")); Expr* x = process_expr(v->get_rhs(), code); x->chk_rvalue(); - res = new Expr{Expr::_Apply, name, {res, x}}; - res->here = v->loc; - res->flags = Expr::_IsRvalue; - res->deduce_type(); + res = create_expr_apply(v->loc, sym, {res, x}); return res; } if (t == tok_logical_and || t == tok_logical_or) { - v->error("logical operators are not supported yet"); + // do the following transformations: + // a && b -> a ? (b != 0) : 0 + // a || b -> a ? 1 : (b != 0) + SymDef* sym_neq = lookup_symbol(calc_sym_idx("_!=_")); + Expr* lhs = process_expr(v->get_lhs(), code); + Expr* rhs = process_expr(v->get_rhs(), code); + Expr* e_neq0 = create_expr_apply(v->loc, sym_neq, {rhs, create_expr_int_const(v->loc, 0)}); + Expr* e_when_true = t == tok_logical_and ? e_neq0 : create_expr_int_const(v->loc, -1); + Expr* e_when_false = t == tok_logical_and ? create_expr_int_const(v->loc, 0) : e_neq0; + Expr* e_ternary = new Expr(Expr::_CondExpr, {lhs, e_when_true, e_when_false}); + e_ternary->here = v->loc; + e_ternary->flags = Expr::_IsRvalue; + e_ternary->deduce_type(); + return e_ternary; } v->error("unsupported binary operator"); @@ -294,7 +317,7 @@ static Expr* process_expr(V v, CodeBlob& code) { static Expr* process_expr(V v, CodeBlob& code) { TokenType t = v->tok; - sym_idx_t name = G.symbols.lookup_add(static_cast(v->operator_name) + "_"); + SymDef* sym = lookup_symbol(calc_sym_idx(static_cast(v->operator_name) + "_")); Expr* x = process_expr(v->get_rhs(), code); x->chk_rvalue(); @@ -316,11 +339,7 @@ static Expr* process_expr(V v, CodeBlob& code) { return x; } - auto res = new Expr{Expr::_Apply, name, {x}}; - res->here = v->loc; - res->flags = Expr::_IsRvalue; - res->deduce_type(); - return res; + return create_expr_apply(v->loc, sym, {x}); } static Expr* process_expr(V v, CodeBlob& code) { @@ -683,19 +702,12 @@ static Expr* process_expr(V v) { static Expr* process_expr(V v) { SymDef* builtin_sym = lookup_symbol(calc_sym_idx(v->bool_val ? "__true" : "__false")); - Expr* res = new Expr{Expr::_Apply, builtin_sym, {}}; - res->flags = Expr::_IsRvalue; - res->deduce_type(); - return res; + return create_expr_apply(v->loc, builtin_sym, {}); } static Expr* process_expr(V v) { SymDef* builtin_sym = lookup_symbol(calc_sym_idx("__null")); - Expr* res = new Expr{Expr::_Apply, builtin_sym, {}}; - res->here = v->loc; - res->flags = Expr::_IsRvalue; - res->deduce_type(); - return res; + return create_expr_apply(v->loc, builtin_sym, {}); } static Expr* process_expr(V v, CodeBlob& code) { @@ -1116,11 +1128,9 @@ static blk_fl::val process_vertex(V v, CodeBlob& code) { args.push_back(process_expr(v->get_thrown_code(), code)); } - Expr* expr = new Expr{Expr::_Apply, builtin_sym, std::move(args)}; - expr->here = v->loc; - expr->flags = Expr::_IsRvalue | Expr::_IsImpure; - expr->deduce_type(); - expr->pre_compile(code); + Expr* apply = create_expr_apply(v->loc, builtin_sym, std::move(args)); + apply->flags |= Expr::_IsImpure; + apply->pre_compile(code); return blk_fl::end; } @@ -1137,11 +1147,9 @@ static blk_fl::val process_vertex(V v, CodeBlob& code) { } SymDef* builtin_sym = lookup_symbol(calc_sym_idx("__throw_if_unless")); - Expr* expr = new Expr{Expr::_Apply, builtin_sym, std::move(args)}; - expr->here = v->loc; - expr->flags = Expr::_IsRvalue | Expr::_IsImpure; - expr->deduce_type(); - expr->pre_compile(code); + Expr* apply = create_expr_apply(v->loc, builtin_sym, std::move(args)); + apply->flags |= Expr::_IsImpure; + apply->pre_compile(code); return blk_fl::end; } diff --git a/tolk/symtable.h b/tolk/symtable.h index a52e8d333..69e2eaa8e 100644 --- a/tolk/symtable.h +++ b/tolk/symtable.h @@ -67,8 +67,8 @@ class SymTable { public: static constexpr sym_idx_t not_found = 0; - sym_idx_t lookup(std::string_view str, int mode = 0) { - return gen_lookup(str, mode); + sym_idx_t lookup(std::string_view str) { + return gen_lookup(str, 0); } sym_idx_t lookup_add(std::string_view str) { return gen_lookup(str, 1);