mirror of
https://github.com/ton-blockchain/ton
synced 2025-03-09 15:40:10 +00:00
[Tolk] bool
type (-1/0 int under the hood)
Comparison operators `== / >= /...` return `bool`. Logical operators `&& ||` return bool. Constants `true` and `false` have the `bool` type. Lots of stdlib functions return `bool`, not `int`. Operator `!x` supports both `int` and `bool`. Condition of `if` accepts both `int` and `bool`. Arithmetic operators are restricted to integers. Logical `&&` and `||` accept both `bool` and `int`. No arithmetic operations with bools allowed (only bitwise and logical).
This commit is contained in:
parent
799e2d1265
commit
974d76c5f6
33 changed files with 764 additions and 212 deletions
|
@ -18,6 +18,7 @@ set(TOLK_SOURCE
|
|||
pipe-check-rvalue-lvalue.cpp
|
||||
pipe-check-pure-impure.cpp
|
||||
pipe-constant-folding.cpp
|
||||
pipe-optimize-boolean-expr.cpp
|
||||
pipe-ast-to-legacy.cpp
|
||||
pipe-find-unused-symbols.cpp
|
||||
pipe-generate-fif-output.cpp
|
||||
|
|
|
@ -673,25 +673,20 @@ static AnyV parse_return_statement(Lexer& lex) {
|
|||
return createV<ast_return_statement>(loc, child);
|
||||
}
|
||||
|
||||
static AnyV parse_if_statement(Lexer& lex, bool is_ifnot) {
|
||||
static AnyV parse_if_statement(Lexer& lex) {
|
||||
SrcLocation loc = lex.cur_location();
|
||||
lex.expect(tok_if, "`if`");
|
||||
|
||||
lex.expect(tok_oppar, "`(`");
|
||||
AnyExprV cond = parse_expr(lex);
|
||||
lex.expect(tok_clpar, "`)`");
|
||||
// replace if(!expr) with ifnot(expr) (this should be done later, but for now, let this be right at parsing time)
|
||||
if (auto v_not = cond->try_as<ast_unary_operator>(); v_not && v_not->tok == tok_logical_not) {
|
||||
is_ifnot = !is_ifnot;
|
||||
cond = v_not->get_rhs();
|
||||
}
|
||||
|
||||
V<ast_sequence> if_body = parse_sequence(lex);
|
||||
V<ast_sequence> else_body = nullptr;
|
||||
if (lex.tok() == tok_else) { // else if(e) { } or else { }
|
||||
lex.next();
|
||||
if (lex.tok() == tok_if) {
|
||||
AnyV v_inner_if = parse_if_statement(lex, false);
|
||||
AnyV v_inner_if = parse_if_statement(lex);
|
||||
else_body = createV<ast_sequence>(v_inner_if->loc, lex.cur_location(), {v_inner_if});
|
||||
} else {
|
||||
else_body = parse_sequence(lex);
|
||||
|
@ -699,7 +694,7 @@ static AnyV parse_if_statement(Lexer& lex, bool is_ifnot) {
|
|||
} else { // no 'else', create empty block
|
||||
else_body = createV<ast_sequence>(lex.cur_location(), lex.cur_location(), {});
|
||||
}
|
||||
return createV<ast_if_statement>(loc, is_ifnot, cond, if_body, else_body);
|
||||
return createV<ast_if_statement>(loc, false, cond, if_body, else_body);
|
||||
}
|
||||
|
||||
static AnyV parse_repeat_statement(Lexer& lex) {
|
||||
|
@ -838,7 +833,7 @@ AnyV parse_statement(Lexer& lex) {
|
|||
case tok_return:
|
||||
return parse_return_statement(lex);
|
||||
case tok_if:
|
||||
return parse_if_statement(lex, false);
|
||||
return parse_if_statement(lex);
|
||||
case tok_repeat:
|
||||
return parse_repeat_statement(lex);
|
||||
case tok_do:
|
||||
|
|
|
@ -475,7 +475,7 @@ AsmOp compile_unary_plus(std::vector<VarDescr>& res, std::vector<VarDescr>& args
|
|||
return AsmOp::Nop();
|
||||
}
|
||||
|
||||
AsmOp compile_logical_not(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where) {
|
||||
AsmOp compile_logical_not(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where, bool for_int_arg) {
|
||||
tolk_assert(res.size() == 1 && args.size() == 1);
|
||||
VarDescr &r = res[0], &x = args[0];
|
||||
if (x.is_int_const()) {
|
||||
|
@ -484,7 +484,9 @@ AsmOp compile_logical_not(std::vector<VarDescr>& res, std::vector<VarDescr>& arg
|
|||
return push_const(r.int_const);
|
||||
}
|
||||
r.val = VarDescr::ValBool;
|
||||
return exec_op("0 EQINT", 1);
|
||||
// for integers, `!var` is `var != 0`
|
||||
// for booleans, `!var` can be shortened to `~var` (works the same for 0/-1 but consumes less)
|
||||
return for_int_arg ? exec_op("0 EQINT", 1) : exec_op("NOT", 1);
|
||||
}
|
||||
|
||||
AsmOp compile_bitwise_and(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation where) {
|
||||
|
@ -1047,7 +1049,7 @@ AsmOp compile_fetch_slice(std::vector<VarDescr>& res, std::vector<VarDescr>& arg
|
|||
return exec_op(fetch ? "LDSLICEX" : "PLDSLICEX", 2, 1 + (unsigned)fetch);
|
||||
}
|
||||
|
||||
// fun at<X>(t: tuple, index: int): X asm "INDEXVAR";
|
||||
// fun tupleAt<X>(t: tuple, index: int): X asm "INDEXVAR";
|
||||
AsmOp compile_tuple_at(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation) {
|
||||
tolk_assert(args.size() == 2 && res.size() == 1);
|
||||
auto& y = args[1];
|
||||
|
@ -1058,7 +1060,7 @@ AsmOp compile_tuple_at(std::vector<VarDescr>& res, std::vector<VarDescr>& args,
|
|||
return exec_op("INDEXVAR", 2, 1);
|
||||
}
|
||||
|
||||
// fun __isNull<X>(X arg): int
|
||||
// fun __isNull<X>(X arg): bool
|
||||
AsmOp compile_is_null(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation) {
|
||||
tolk_assert(args.size() == 1 && res.size() == 1);
|
||||
res[0].val = VarDescr::ValBool;
|
||||
|
@ -1071,6 +1073,7 @@ void define_builtins() {
|
|||
|
||||
TypePtr Unit = TypeDataVoid::create();
|
||||
TypePtr Int = TypeDataInt::create();
|
||||
TypePtr Bool = TypeDataBool::create();
|
||||
TypePtr Slice = TypeDataSlice::create();
|
||||
TypePtr Builder = TypeDataBuilder::create();
|
||||
TypePtr Tuple = TypeDataTuple::create();
|
||||
|
@ -1085,18 +1088,36 @@ void define_builtins() {
|
|||
std::vector ParamsInt3 = {Int, Int, Int};
|
||||
std::vector ParamsSliceInt = {Slice, Int};
|
||||
|
||||
define_builtin_func("_+_", ParamsInt2, Int, nullptr,
|
||||
compile_add,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_-_", ParamsInt2, Int, nullptr,
|
||||
compile_sub,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
// builtin operators
|
||||
// they are internally stored as functions, because at IR level, there is no difference
|
||||
// between calling `userAdd(a,b)` and `_+_(a,b)`
|
||||
// since they are registered in a global symtable, technically, they can even be referenced from Tolk code,
|
||||
// though it's a "hidden feature" and won't work well for overloads (`==` for int and bool, for example)
|
||||
|
||||
// unary operators
|
||||
define_builtin_func("-_", ParamsInt1, Int, nullptr,
|
||||
compile_unary_minus,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("+_", ParamsInt1, Int, nullptr,
|
||||
compile_unary_plus,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("!_", ParamsInt1, Bool, nullptr,
|
||||
std::bind(compile_logical_not, _1, _2, _3, true),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("!b_", {Bool}, Bool, nullptr, // "overloaded" separate version for bool
|
||||
std::bind(compile_logical_not, _1, _2, _3, false),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("~_", ParamsInt1, Int, nullptr,
|
||||
compile_bitwise_not,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
|
||||
// binary operators
|
||||
define_builtin_func("_+_", ParamsInt2, Int, nullptr,
|
||||
compile_add,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_-_", ParamsInt2, Int, nullptr,
|
||||
compile_sub,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_*_", ParamsInt2, Int, nullptr,
|
||||
compile_mul,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
|
@ -1124,25 +1145,19 @@ void define_builtins() {
|
|||
define_builtin_func("_^>>_", ParamsInt2, Int, nullptr,
|
||||
std::bind(compile_rshift, _1, _2, _3, 1),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("!_", ParamsInt1, Int, nullptr,
|
||||
compile_logical_not,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("~_", ParamsInt1, Int, nullptr,
|
||||
compile_bitwise_not,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_&_", ParamsInt2, Int, nullptr,
|
||||
define_builtin_func("_&_", ParamsInt2, Int, nullptr, // also works for bool
|
||||
compile_bitwise_and,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_|_", ParamsInt2, Int, nullptr,
|
||||
define_builtin_func("_|_", ParamsInt2, Int, nullptr, // also works for bool
|
||||
compile_bitwise_or,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_^_", ParamsInt2, Int, nullptr,
|
||||
define_builtin_func("_^_", ParamsInt2, Int, nullptr, // also works for bool
|
||||
compile_bitwise_xor,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_==_", ParamsInt2, Int, nullptr,
|
||||
define_builtin_func("_==_", ParamsInt2, Int, nullptr, // also works for bool
|
||||
std::bind(compile_cmp_int, _1, _2, 2),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_!=_", ParamsInt2, Int, nullptr,
|
||||
define_builtin_func("_!=_", ParamsInt2, Int, nullptr, // also works for bool
|
||||
std::bind(compile_cmp_int, _1, _2, 5),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("_<_", ParamsInt2, Int, nullptr,
|
||||
|
@ -1160,6 +1175,33 @@ void define_builtins() {
|
|||
define_builtin_func("_<=>_", ParamsInt2, Int, nullptr,
|
||||
std::bind(compile_cmp_int, _1, _2, 7),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
|
||||
// special function used for internal compilation of some lexical constructs
|
||||
// for example, `throw 123;` is actually calling `__throw(123)`
|
||||
define_builtin_func("__true", {}, Bool, nullptr, /* AsmOp::Const("TRUE") */
|
||||
std::bind(compile_bool_const, _1, _2, true),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("__false", {}, Bool, nullptr, /* AsmOp::Const("FALSE") */
|
||||
std::bind(compile_bool_const, _1, _2, false),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("__null", {}, typeT, declGenericT,
|
||||
AsmOp::Const("PUSHNULL"),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("__isNull", {typeT}, Bool, declGenericT,
|
||||
compile_is_null,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("__throw", ParamsInt1, Unit, nullptr,
|
||||
compile_throw,
|
||||
0);
|
||||
define_builtin_func("__throw_arg", {typeT, Int}, Unit, declGenericT,
|
||||
compile_throw_arg,
|
||||
0);
|
||||
define_builtin_func("__throw_if_unless", ParamsInt3, Unit, nullptr,
|
||||
compile_throw_if_unless,
|
||||
0);
|
||||
|
||||
// functions from stdlib marked as `builtin`, implemented at compiler level for optimizations
|
||||
// (for example, `loadInt(1)` is `1 LDI`, but `loadInt(n)` for non-constant requires it be on a stack and `LDIX`)
|
||||
define_builtin_func("mulDivFloor", ParamsInt3, Int, nullptr,
|
||||
std::bind(compile_muldiv, _1, _2, _3, -1),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
|
@ -1172,27 +1214,6 @@ void define_builtins() {
|
|||
define_builtin_func("mulDivMod", ParamsInt3, TypeDataTensor::create({Int, Int}), nullptr,
|
||||
AsmOp::Custom("MULDIVMOD", 3, 2),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("__true", {}, Int, nullptr, /* AsmOp::Const("TRUE") */
|
||||
std::bind(compile_bool_const, _1, _2, true),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("__false", {}, Int, nullptr, /* AsmOp::Const("FALSE") */
|
||||
std::bind(compile_bool_const, _1, _2, false),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("__null", {}, typeT, declGenericT,
|
||||
AsmOp::Const("PUSHNULL"),
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("__isNull", {typeT}, Int, declGenericT,
|
||||
compile_is_null,
|
||||
FunctionData::flagMarkedAsPure);
|
||||
define_builtin_func("__throw", ParamsInt1, Unit, nullptr,
|
||||
compile_throw,
|
||||
0);
|
||||
define_builtin_func("__throw_arg", {typeT, Int}, Unit, declGenericT,
|
||||
compile_throw_arg,
|
||||
0);
|
||||
define_builtin_func("__throw_if_unless", ParamsInt3, Unit, nullptr,
|
||||
compile_throw_if_unless,
|
||||
0);
|
||||
define_builtin_func("loadInt", ParamsSliceInt, Int, nullptr,
|
||||
std::bind(compile_fetch_int, _1, _2, true, true),
|
||||
FunctionData::flagMarkedAsPure | FunctionData::flagHasMutateParams | FunctionData::flagAcceptsSelf,
|
||||
|
|
|
@ -586,6 +586,8 @@ static void process_do_while_statement(V<ast_do_while_statement> v, CodeBlob& co
|
|||
until_cond = createV<ast_binary_operator>(cond->loc, "<", tok_lt, v_geq->get_lhs(), v_geq->get_rhs());
|
||||
} else if (auto v_gt = cond->try_as<ast_binary_operator>(); v_gt && v_gt->tok == tok_gt) {
|
||||
until_cond = createV<ast_binary_operator>(cond->loc, "<=", tok_geq, v_gt->get_lhs(), v_gt->get_rhs());
|
||||
} else if (cond->inferred_type == TypeDataBool::create()) {
|
||||
until_cond = createV<ast_unary_operator>(cond->loc, "!b", tok_logical_not, cond);
|
||||
} else {
|
||||
until_cond = createV<ast_unary_operator>(cond->loc, "!", tok_logical_not, cond);
|
||||
}
|
||||
|
|
|
@ -23,7 +23,8 @@
|
|||
* This pipe is supposed to do constant folding, like replacing `2 + 3` with `5`.
|
||||
* It happens after type inferring and validity checks, one of the last ones.
|
||||
*
|
||||
* Currently, it just replaces `-1` (ast_unary_operator ast_int_const) with a number -1.
|
||||
* Currently, it just replaces `-1` (ast_unary_operator ast_int_const) with a number -1
|
||||
* and `!true` with false.
|
||||
* More rich constant folding should be done some day, but even without this, IR optimizations
|
||||
* (operating low-level stack variables) pretty manage to do all related optimizations.
|
||||
* Constant folding in the future, done at AST level, just would slightly reduce amount of work for optimizer.
|
||||
|
@ -39,6 +40,13 @@ class ConstantFoldingReplacer final : public ASTReplacerInFunctionBody {
|
|||
return v_int;
|
||||
}
|
||||
|
||||
static V<ast_bool_const> create_bool_const(SrcLocation loc, bool bool_val) {
|
||||
auto v_bool = createV<ast_bool_const>(loc, bool_val);
|
||||
v_bool->assign_inferred_type(TypeDataBool::create());
|
||||
v_bool->assign_rvalue_true();
|
||||
return v_bool;
|
||||
}
|
||||
|
||||
AnyExprV replace(V<ast_unary_operator> v) override {
|
||||
parent::replace(v);
|
||||
|
||||
|
@ -58,6 +66,15 @@ class ConstantFoldingReplacer final : public ASTReplacerInFunctionBody {
|
|||
return v->get_rhs();
|
||||
}
|
||||
|
||||
// `!true` / `!false`
|
||||
if (t == tok_logical_not && v->get_rhs()->type == ast_bool_const) {
|
||||
return create_bool_const(v->loc, !v->get_rhs()->as<ast_bool_const>()->bool_val);
|
||||
}
|
||||
// `!0`
|
||||
if (t == tok_logical_not && v->get_rhs()->type == ast_int_const) {
|
||||
return create_bool_const(v->loc, v->get_rhs()->as<ast_int_const>()->intval == 0);
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
|
|
|
@ -110,6 +110,20 @@ static void fire_error_assign_always_null_to_variable(SrcLocation loc, const Loc
|
|||
throw ParseError(loc, "can not infer type of `" + var_name + "`, it's always null; specify its type with `" + var_name + ": <type>`" + (is_assigned_null_literal ? " or use `null as <type>`" : ""));
|
||||
}
|
||||
|
||||
// fire an error on `!cell` / `+slice`
|
||||
GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD
|
||||
static void fire_error_cannot_apply_operator(SrcLocation loc, std::string_view operator_name, AnyExprV unary_expr) {
|
||||
std::string op = static_cast<std::string>(operator_name);
|
||||
throw ParseError(loc, "can not apply operator `" + op + "` to " + to_string(unary_expr->inferred_type));
|
||||
}
|
||||
|
||||
// fire an error on `int + cell` / `slice & int`
|
||||
GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD
|
||||
static void fire_error_cannot_apply_operator(SrcLocation loc, std::string_view operator_name, AnyExprV lhs, AnyExprV rhs) {
|
||||
std::string op = static_cast<std::string>(operator_name);
|
||||
throw ParseError(loc, "can not apply operator `" + op + "` to " + to_string(lhs->inferred_type) + " and " + to_string(rhs->inferred_type));
|
||||
}
|
||||
|
||||
// check correctness of called arguments counts and their type matching
|
||||
static void check_function_arguments(const FunctionData* fun_ref, V<ast_argument_list> v, AnyExprV lhs_of_dot_call) {
|
||||
int delta_self = lhs_of_dot_call ? 1 : 0;
|
||||
|
@ -345,6 +359,10 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
return v_inferred->inferred_type == TypeDataInt::create();
|
||||
}
|
||||
|
||||
static bool expect_boolean(AnyExprV v_inferred) {
|
||||
return v_inferred->inferred_type == TypeDataBool::create();
|
||||
}
|
||||
|
||||
static void infer_int_const(V<ast_int_const> v) {
|
||||
assign_inferred_type(v, TypeDataInt::create());
|
||||
}
|
||||
|
@ -358,8 +376,7 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
}
|
||||
|
||||
static void infer_bool_const(V<ast_bool_const> v) {
|
||||
// currently, Tolk has no `bool` type; `true` and `false` are integers (-1 and 0)
|
||||
assign_inferred_type(v, TypeDataInt::create());
|
||||
assign_inferred_type(v, TypeDataBool::create());
|
||||
}
|
||||
|
||||
static void infer_local_vars_declaration(V<ast_local_vars_declaration>) {
|
||||
|
@ -544,8 +561,23 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
// almost all operators implementation is hardcoded by built-in functions `_+_` and similar
|
||||
std::string_view builtin_func = v->operator_name; // "+" for operator +=
|
||||
|
||||
if (!expect_integer(lhs) || !expect_integer(rhs)) {
|
||||
v->error("can not apply operator `" + static_cast<std::string>(v->operator_name) + "` to " + to_string(lhs) + " and " + to_string(rhs));
|
||||
switch (v->tok) {
|
||||
// &= |= ^= are "overloaded" both for integers and booleans, (int &= bool) is NOT allowed
|
||||
case tok_set_bitwise_and:
|
||||
case tok_set_bitwise_or:
|
||||
case tok_set_bitwise_xor: {
|
||||
bool both_int = expect_integer(lhs) && expect_integer(rhs);
|
||||
bool both_bool = expect_boolean(lhs) && expect_boolean(rhs);
|
||||
if (!both_int && !both_bool) {
|
||||
fire_error_cannot_apply_operator(v->loc, v->operator_name, lhs, rhs);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// others are mathematical: += *= ...
|
||||
default:
|
||||
if (!expect_integer(lhs) || !expect_integer(rhs)) {
|
||||
fire_error_cannot_apply_operator(v->loc, v->operator_name, lhs, rhs);
|
||||
}
|
||||
}
|
||||
|
||||
assign_inferred_type(v, lhs);
|
||||
|
@ -563,10 +595,26 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
// all operators implementation is hardcoded by built-in functions `~_` and similar
|
||||
std::string_view builtin_func = v->operator_name;
|
||||
|
||||
if (!expect_integer(rhs)) {
|
||||
v->error("can not apply operator `" + static_cast<std::string>(v->operator_name) + "` to " + to_string(rhs));
|
||||
switch (v->tok) {
|
||||
case tok_minus:
|
||||
case tok_plus:
|
||||
case tok_bitwise_not:
|
||||
if (!expect_integer(rhs)) {
|
||||
fire_error_cannot_apply_operator(v->loc, v->operator_name, rhs);
|
||||
}
|
||||
assign_inferred_type(v, TypeDataInt::create());
|
||||
break;
|
||||
case tok_logical_not:
|
||||
if (expect_boolean(rhs)) {
|
||||
builtin_func = "!b"; // "overloaded" for bool
|
||||
} else if (!expect_integer(rhs)) {
|
||||
fire_error_cannot_apply_operator(v->loc, v->operator_name, rhs);
|
||||
}
|
||||
assign_inferred_type(v, TypeDataBool::create());
|
||||
break;
|
||||
default:
|
||||
tolk_assert(false);
|
||||
}
|
||||
assign_inferred_type(v, TypeDataInt::create());
|
||||
|
||||
if (!builtin_func.empty()) {
|
||||
const FunctionData* builtin_sym = lookup_global_symbol(static_cast<std::string>(builtin_func) + "_")->as<FunctionData>();
|
||||
|
@ -587,26 +635,59 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
switch (v->tok) {
|
||||
// == != can compare both integers and booleans, (int == bool) is NOT allowed
|
||||
case tok_eq:
|
||||
case tok_neq:
|
||||
case tok_neq: {
|
||||
bool both_int = expect_integer(lhs) && expect_integer(rhs);
|
||||
bool both_bool = expect_boolean(lhs) && expect_boolean(rhs);
|
||||
if (!both_int && !both_bool) {
|
||||
if (lhs->inferred_type == rhs->inferred_type) { // compare slice with slice
|
||||
v->error("type " + to_string(lhs) + " can not be compared with `== !=`");
|
||||
} else {
|
||||
fire_error_cannot_apply_operator(v->loc, v->operator_name, lhs, rhs);
|
||||
}
|
||||
}
|
||||
assign_inferred_type(v, TypeDataBool::create());
|
||||
break;
|
||||
}
|
||||
// < > can compare only integers
|
||||
case tok_lt:
|
||||
case tok_gt:
|
||||
case tok_leq:
|
||||
case tok_geq:
|
||||
case tok_spaceship: {
|
||||
if (!expect_integer(lhs) || !expect_integer(rhs)) {
|
||||
v->error("comparison operators `== !=` can compare only integers, got " + to_string(lhs) + " and " + to_string(rhs));
|
||||
fire_error_cannot_apply_operator(v->loc, v->operator_name, lhs, rhs);
|
||||
}
|
||||
assign_inferred_type(v, TypeDataInt::create());
|
||||
assign_inferred_type(v, TypeDataBool::create());
|
||||
break;
|
||||
}
|
||||
// & | ^ are "overloaded" both for integers and booleans, (int & bool) is NOT allowed
|
||||
case tok_bitwise_and:
|
||||
case tok_bitwise_or:
|
||||
case tok_bitwise_xor: {
|
||||
bool both_int = expect_integer(lhs) && expect_integer(rhs);
|
||||
bool both_bool = expect_boolean(lhs) && expect_boolean(rhs);
|
||||
if (!both_int && !both_bool) {
|
||||
fire_error_cannot_apply_operator(v->loc, v->operator_name, lhs, rhs);
|
||||
}
|
||||
assign_inferred_type(v, rhs); // (int & int) is int, (bool & bool) is bool
|
||||
break;
|
||||
}
|
||||
// && || can work with integers and booleans, (int && bool) is allowed
|
||||
case tok_logical_and:
|
||||
case tok_logical_or: {
|
||||
if (!expect_integer(lhs) || !expect_integer(rhs)) {
|
||||
v->error("logical operators `&& ||` expect integer operands, got " + to_string(lhs) + " and " + to_string(rhs));
|
||||
bool lhs_ok = expect_integer(lhs) || expect_boolean(lhs);
|
||||
bool rhs_ok = expect_integer(rhs) || expect_boolean(rhs);
|
||||
if (!lhs_ok || !rhs_ok) {
|
||||
fire_error_cannot_apply_operator(v->loc, v->operator_name, lhs, rhs);
|
||||
}
|
||||
assign_inferred_type(v, TypeDataInt::create());
|
||||
builtin_func = {};
|
||||
assign_inferred_type(v, TypeDataBool::create());
|
||||
builtin_func = {}; // no built-in functions, logical operators are expressed as IFs at IR level
|
||||
break;
|
||||
}
|
||||
// others are mathematical: + * ...
|
||||
default:
|
||||
if (!expect_integer(lhs) || !expect_integer(rhs)) {
|
||||
v->error("can not apply operator `" + static_cast<std::string>(v->operator_name) + "` to " + to_string(lhs) + " and " + to_string(rhs));
|
||||
fire_error_cannot_apply_operator(v->loc, v->operator_name, lhs, rhs);
|
||||
}
|
||||
assign_inferred_type(v, TypeDataInt::create());
|
||||
}
|
||||
|
@ -619,9 +700,10 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
}
|
||||
|
||||
void infer_ternary_operator(V<ast_ternary_operator> v, TypePtr hint) {
|
||||
infer_any_expr(v->get_cond());
|
||||
if (!expect_integer(v->get_cond())) {
|
||||
v->get_cond()->error("condition of ternary operator must be an integer, got " + to_string(v->get_cond()));
|
||||
AnyExprV cond = v->get_cond();
|
||||
infer_any_expr(cond);
|
||||
if (!expect_integer(cond) && !expect_boolean(cond)) {
|
||||
cond->error("can not use " + to_string(cond) + " as a boolean condition");
|
||||
}
|
||||
infer_any_expr(v->get_when_true(), hint);
|
||||
infer_any_expr(v->get_when_false(), hint);
|
||||
|
@ -983,35 +1065,39 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
}
|
||||
|
||||
void process_if_statement(V<ast_if_statement> v) {
|
||||
infer_any_expr(v->get_cond());
|
||||
if (!expect_integer(v->get_cond())) {
|
||||
v->get_cond()->error("condition of `if` must be an integer, got " + to_string(v->get_cond()));
|
||||
AnyExprV cond = v->get_cond();
|
||||
infer_any_expr(cond);
|
||||
if (!expect_integer(cond) && !expect_boolean(cond)) {
|
||||
cond->error("can not use " + to_string(cond) + " as a boolean condition");
|
||||
}
|
||||
process_any_statement(v->get_if_body());
|
||||
process_any_statement(v->get_else_body());
|
||||
}
|
||||
|
||||
void process_repeat_statement(V<ast_repeat_statement> v) {
|
||||
infer_any_expr(v->get_cond());
|
||||
if (!expect_integer(v->get_cond())) {
|
||||
v->get_cond()->error("condition of `repeat` must be an integer, got " + to_string(v->get_cond()));
|
||||
AnyExprV cond = v->get_cond();
|
||||
infer_any_expr(cond);
|
||||
if (!expect_integer(cond)) {
|
||||
cond->error("condition of `repeat` must be an integer, got " + to_string(cond));
|
||||
}
|
||||
process_any_statement(v->get_body());
|
||||
}
|
||||
|
||||
void process_while_statement(V<ast_while_statement> v) {
|
||||
infer_any_expr(v->get_cond());
|
||||
if (!expect_integer(v->get_cond())) {
|
||||
v->get_cond()->error("condition of `while` must be an integer, got " + to_string(v->get_cond()));
|
||||
AnyExprV cond = v->get_cond();
|
||||
infer_any_expr(cond);
|
||||
if (!expect_integer(cond) && !expect_boolean(cond)) {
|
||||
cond->error("can not use " + to_string(cond) + " as a boolean condition");
|
||||
}
|
||||
process_any_statement(v->get_body());
|
||||
}
|
||||
|
||||
void process_do_while_statement(V<ast_do_while_statement> v) {
|
||||
process_any_statement(v->get_body());
|
||||
infer_any_expr(v->get_cond());
|
||||
if (!expect_integer(v->get_cond())) {
|
||||
v->get_cond()->error("condition of `while` must be an integer, got " + to_string(v->get_cond()));
|
||||
AnyExprV cond = v->get_cond();
|
||||
infer_any_expr(cond);
|
||||
if (!expect_integer(cond) && !expect_boolean(cond)) {
|
||||
cond->error("can not use " + to_string(cond) + " as a boolean condition");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1027,9 +1113,10 @@ class InferCheckTypesAndCallsAndFieldsVisitor final {
|
|||
}
|
||||
|
||||
void process_assert_statement(V<ast_assert_statement> v) {
|
||||
infer_any_expr(v->get_cond());
|
||||
if (!expect_integer(v->get_cond())) {
|
||||
v->get_cond()->error("condition of `assert` must be an integer, got " + to_string(v->get_cond()));
|
||||
AnyExprV cond = v->get_cond();
|
||||
infer_any_expr(cond);
|
||||
if (!expect_integer(cond) && !expect_boolean(cond)) {
|
||||
cond->error("can not use " + to_string(cond) + " as a boolean condition");
|
||||
}
|
||||
infer_any_expr(v->get_thrown_code());
|
||||
if (!expect_integer(v->get_thrown_code())) {
|
||||
|
|
172
tolk/pipe-optimize-boolean-expr.cpp
Normal file
172
tolk/pipe-optimize-boolean-expr.cpp
Normal file
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
This file is part of TON Blockchain source code.
|
||||
|
||||
TON Blockchain is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU 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 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with TON Blockchain. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "tolk.h"
|
||||
#include "ast.h"
|
||||
#include "ast-replacer.h"
|
||||
#include "type-system.h"
|
||||
|
||||
/*
|
||||
* This pipe does some optimizations related to booleans.
|
||||
* It happens after type inferring, when we know types of all expressions.
|
||||
*
|
||||
* Example: `boolVar == true` -> `boolVar`.
|
||||
* Example: `!!boolVar` -> `boolVar`.
|
||||
* Also in unwraps parenthesis inside if condition and similar: `assert(((x)), 404)` -> `assert(x, 404)`
|
||||
*
|
||||
* todo some day, replace && || with & | when it's safe (currently, && always produces IFs in Fift)
|
||||
* It's tricky to implement whether replacing is safe.
|
||||
* For example, safe: `a > 0 && a < 10` / `a != 3 && a != 5`
|
||||
* For example, unsafe: `cached && calc()` / `a > 0 && log(a)` / `b != 0 && a / b > 1` / `i >= 0 && arr[idx]` / `f != null && close(f)`
|
||||
*/
|
||||
|
||||
namespace tolk {
|
||||
|
||||
static AnyExprV unwrap_parenthesis(AnyExprV v) {
|
||||
while (v->type == ast_parenthesized_expression) {
|
||||
v = v->as<ast_parenthesized_expression>()->get_expr();
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
struct OptimizerBooleanExpressionsReplacer final : ASTReplacerInFunctionBody {
|
||||
static V<ast_int_const> create_int_const(SrcLocation loc, td::RefInt256&& intval) {
|
||||
auto v_int = createV<ast_int_const>(loc, std::move(intval), {});
|
||||
v_int->assign_inferred_type(TypeDataInt::create());
|
||||
v_int->assign_rvalue_true();
|
||||
return v_int;
|
||||
}
|
||||
|
||||
static V<ast_bool_const> create_bool_const(SrcLocation loc, bool bool_val) {
|
||||
auto v_bool = createV<ast_bool_const>(loc, bool_val);
|
||||
v_bool->assign_inferred_type(TypeDataInt::create());
|
||||
v_bool->assign_rvalue_true();
|
||||
return v_bool;
|
||||
}
|
||||
|
||||
static V<ast_unary_operator> create_logical_not_for_bool(SrcLocation loc, AnyExprV rhs) {
|
||||
auto v_not = createV<ast_unary_operator>(loc, "!", tok_logical_not, rhs);
|
||||
v_not->assign_inferred_type(TypeDataBool::create());
|
||||
v_not->assign_rvalue_true();
|
||||
v_not->assign_fun_ref(lookup_global_symbol("!b_")->as<FunctionData>());
|
||||
return v_not;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
AnyExprV replace(V<ast_unary_operator> v) override {
|
||||
parent::replace(v);
|
||||
|
||||
if (v->tok == tok_logical_not) {
|
||||
if (auto inner_not = v->get_rhs()->try_as<ast_unary_operator>(); inner_not && inner_not->tok == tok_logical_not) {
|
||||
AnyExprV cond_not_not = inner_not->get_rhs();
|
||||
// `!!boolVar` => `boolVar`
|
||||
if (cond_not_not->inferred_type == TypeDataBool::create()) {
|
||||
return cond_not_not;
|
||||
}
|
||||
// `!!intVar` => `intVar != 0`
|
||||
if (cond_not_not->inferred_type == TypeDataInt::create()) {
|
||||
auto v_zero = create_int_const(v->loc, td::make_refint(0));
|
||||
auto v_neq = createV<ast_binary_operator>(v->loc, "!=", tok_neq, cond_not_not, v_zero);
|
||||
v_neq->mutate()->assign_rvalue_true();
|
||||
v_neq->mutate()->assign_inferred_type(TypeDataBool::create());
|
||||
v_neq->mutate()->assign_fun_ref(lookup_global_symbol("_!=_")->as<FunctionData>());
|
||||
return v_neq;
|
||||
}
|
||||
}
|
||||
if (auto inner_bool = v->get_rhs()->try_as<ast_bool_const>()) {
|
||||
// `!true` / `!false`
|
||||
return create_bool_const(v->loc, !inner_bool->bool_val);
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
AnyExprV replace(V<ast_binary_operator> v) override {
|
||||
parent::replace(v);
|
||||
|
||||
if (v->tok == tok_eq || v->tok == tok_neq) {
|
||||
AnyExprV lhs = v->get_lhs();
|
||||
AnyExprV rhs = v->get_rhs();
|
||||
if (lhs->inferred_type == TypeDataBool::create() && rhs->type == ast_bool_const) {
|
||||
// `boolVar == true` / `boolVar != false`
|
||||
if (rhs->as<ast_bool_const>()->bool_val ^ (v->tok == tok_neq)) {
|
||||
return lhs;
|
||||
}
|
||||
// `boolVar != true` / `boolVar == false`
|
||||
return create_logical_not_for_bool(v->loc, lhs);
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
AnyV replace(V<ast_if_statement> v) override {
|
||||
parent::replace(v);
|
||||
if (v->get_cond()->type == ast_parenthesized_expression) {
|
||||
v = createV<ast_if_statement>(v->loc, v->is_ifnot, unwrap_parenthesis(v->get_cond()), v->get_if_body(), v->get_else_body());
|
||||
}
|
||||
|
||||
// `if (!x)` -> ifnot(x)
|
||||
while (auto v_cond_unary = v->get_cond()->try_as<ast_unary_operator>()) {
|
||||
if (v_cond_unary->tok != tok_logical_not) {
|
||||
break;
|
||||
}
|
||||
v = createV<ast_if_statement>(v->loc, !v->is_ifnot, v_cond_unary->get_rhs(), v->get_if_body(), v->get_else_body());
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
AnyV replace(V<ast_while_statement> v) override {
|
||||
parent::replace(v);
|
||||
|
||||
if (v->get_cond()->type == ast_parenthesized_expression) {
|
||||
v = createV<ast_while_statement>(v->loc, unwrap_parenthesis(v->get_cond()), v->get_body());
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
AnyV replace(V<ast_do_while_statement> v) override {
|
||||
parent::replace(v);
|
||||
|
||||
if (v->get_cond()->type == ast_parenthesized_expression) {
|
||||
v = createV<ast_do_while_statement>(v->loc, v->get_body(), unwrap_parenthesis(v->get_cond()));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
AnyV replace(V<ast_assert_statement> v) override {
|
||||
parent::replace(v);
|
||||
|
||||
if (v->get_cond()->type == ast_parenthesized_expression) {
|
||||
v = createV<ast_assert_statement>(v->loc, unwrap_parenthesis(v->get_cond()), v->get_thrown_code());
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
public:
|
||||
bool should_visit_function(const FunctionData* fun_ref) override {
|
||||
return fun_ref->is_code_function() && !fun_ref->is_generic_function();
|
||||
}
|
||||
};
|
||||
|
||||
void pipeline_optimize_boolean_expressions() {
|
||||
replace_ast_of_all_functions<OptimizerBooleanExpressionsReplacer>();
|
||||
}
|
||||
|
||||
} // namespace tolk
|
|
@ -150,9 +150,6 @@ struct TypeDataResolver {
|
|||
if (un->text == "self") {
|
||||
throw ParseError(un->loc, "`self` type can be used only as a return type of a function (enforcing it to be chainable)");
|
||||
}
|
||||
if (un->text == "bool") {
|
||||
throw ParseError(un->loc, "bool type is not supported yet");
|
||||
}
|
||||
fire_error_unknown_type_name(un->loc, un->text);
|
||||
}
|
||||
return child;
|
||||
|
|
|
@ -41,6 +41,7 @@ void pipeline_refine_lvalue_for_mutate_arguments();
|
|||
void pipeline_check_rvalue_lvalue();
|
||||
void pipeline_check_pure_impure_operations();
|
||||
void pipeline_constant_folding();
|
||||
void pipeline_optimize_boolean_expressions();
|
||||
void pipeline_convert_ast_to_legacy_Expr_Op();
|
||||
|
||||
void pipeline_find_unused_symbols();
|
||||
|
|
|
@ -64,6 +64,7 @@ int tolk_proceed(const std::string &entrypoint_filename) {
|
|||
pipeline_check_rvalue_lvalue();
|
||||
pipeline_check_pure_impure_operations();
|
||||
pipeline_constant_folding();
|
||||
pipeline_optimize_boolean_expressions();
|
||||
pipeline_convert_ast_to_legacy_Expr_Op();
|
||||
|
||||
pipeline_find_unused_symbols();
|
||||
|
|
|
@ -76,6 +76,7 @@ public:
|
|||
|
||||
std::unordered_map<uint64_t, TypePtr> TypeDataTypeIdCalculation::all_unique_occurred_types;
|
||||
TypePtr TypeDataInt::singleton;
|
||||
TypePtr TypeDataBool::singleton;
|
||||
TypePtr TypeDataCell::singleton;
|
||||
TypePtr TypeDataSlice::singleton;
|
||||
TypePtr TypeDataBuilder::singleton;
|
||||
|
@ -87,6 +88,7 @@ TypePtr TypeDataVoid::singleton;
|
|||
|
||||
void type_system_init() {
|
||||
TypeDataInt::singleton = new TypeDataInt;
|
||||
TypeDataBool::singleton = new TypeDataBool;
|
||||
TypeDataCell::singleton = new TypeDataCell;
|
||||
TypeDataSlice::singleton = new TypeDataSlice;
|
||||
TypeDataBuilder::singleton = new TypeDataBuilder;
|
||||
|
@ -330,6 +332,16 @@ bool TypeDataInt::can_rhs_be_assigned(TypePtr rhs) const {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool TypeDataBool::can_rhs_be_assigned(TypePtr rhs) const {
|
||||
if (rhs == this) {
|
||||
return true;
|
||||
}
|
||||
if (rhs == TypeDataNullLiteral::create()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TypeDataCell::can_rhs_be_assigned(TypePtr rhs) const {
|
||||
if (rhs == this) {
|
||||
return true;
|
||||
|
@ -446,6 +458,10 @@ bool TypeDataInt::can_be_casted_with_as_operator(TypePtr cast_to) const {
|
|||
return cast_to == this;
|
||||
}
|
||||
|
||||
bool TypeDataBool::can_be_casted_with_as_operator(TypePtr cast_to) const {
|
||||
return cast_to == this || cast_to == TypeDataInt::create();
|
||||
}
|
||||
|
||||
bool TypeDataCell::can_be_casted_with_as_operator(TypePtr cast_to) const {
|
||||
return cast_to == this;
|
||||
}
|
||||
|
@ -468,7 +484,7 @@ bool TypeDataContinuation::can_be_casted_with_as_operator(TypePtr cast_to) const
|
|||
|
||||
bool TypeDataNullLiteral::can_be_casted_with_as_operator(TypePtr cast_to) const {
|
||||
return cast_to == this
|
||||
|| cast_to == TypeDataInt::create() || cast_to == TypeDataCell::create() || cast_to == TypeDataSlice::create()
|
||||
|| cast_to == TypeDataInt::create() || cast_to == TypeDataBool::create() || cast_to == TypeDataCell::create() || cast_to == TypeDataSlice::create()
|
||||
|| cast_to == TypeDataBuilder::create() || cast_to == TypeDataContinuation::create() || cast_to == TypeDataTuple::create()
|
||||
|| cast_to->try_as<TypeDataTypedTuple>();
|
||||
}
|
||||
|
@ -593,6 +609,9 @@ static TypePtr parse_simple_type(Lexer& lex) {
|
|||
case tok_int:
|
||||
lex.next();
|
||||
return TypeDataInt::create();
|
||||
case tok_bool:
|
||||
lex.next();
|
||||
return TypeDataBool::create();
|
||||
case tok_cell:
|
||||
lex.next();
|
||||
return TypeDataCell::create();
|
||||
|
@ -614,7 +633,6 @@ static TypePtr parse_simple_type(Lexer& lex) {
|
|||
case tok_void:
|
||||
lex.next();
|
||||
return TypeDataVoid::create();
|
||||
case tok_bool:
|
||||
case tok_self:
|
||||
case tok_identifier: {
|
||||
SrcLocation loc = lex.cur_location();
|
||||
|
|
|
@ -120,6 +120,24 @@ public:
|
|||
bool can_be_casted_with_as_operator(TypePtr cast_to) const override;
|
||||
};
|
||||
|
||||
/*
|
||||
* `bool` is TypeDataBool. TVM has no bool, only integers. Under the hood, -1 is true, 0 is false.
|
||||
* From the type system point of view, int and bool are different, not-autocastable types.
|
||||
*/
|
||||
class TypeDataBool final : public TypeData {
|
||||
TypeDataBool() : TypeData(2ULL, 0) {}
|
||||
|
||||
static TypePtr singleton;
|
||||
friend void type_system_init();
|
||||
|
||||
public:
|
||||
static TypePtr create() { return singleton; }
|
||||
|
||||
std::string as_human_readable() const override { return "bool"; }
|
||||
bool can_rhs_be_assigned(TypePtr rhs) const override;
|
||||
bool can_be_casted_with_as_operator(TypePtr cast_to) const override;
|
||||
};
|
||||
|
||||
/*
|
||||
* `cell` is TypeDataCell, representation of TVM cell.
|
||||
*/
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue