mirror of
https://github.com/ton-blockchain/ton
synced 2025-02-12 19:22:37 +00:00
Add try/catch to FunC (#560)
* Add try-catch * Fix 'return' bugs * Update tests * Fix 'SETCONTVARARGS' bug * Fix 'SETCONTVARARGS' bug again * Check deep stack * Add throw_arg Co-authored-by: legaii <jgates.ardux@gmail.com>
This commit is contained in:
parent
dd9cdba587
commit
e1be988df5
8 changed files with 365 additions and 0 deletions
|
@ -520,6 +520,14 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) {
|
|||
} while (changes <= edit);
|
||||
return set_var_info(std::move(new_var_info));
|
||||
}
|
||||
case _TryCatch: {
|
||||
code.compute_used_code_vars(block0, next_var_info, edit);
|
||||
code.compute_used_code_vars(block1, next_var_info, edit);
|
||||
VarDescrList merge_info = block0->var_info + block1->var_info + next_var_info;
|
||||
merge_info -= left;
|
||||
merge_info.clear_last();
|
||||
return set_var_info(std::move(merge_info));
|
||||
}
|
||||
default:
|
||||
std::cerr << "fatal: unknown operation <??" << cl << "> in compute_used_vars()\n";
|
||||
throw src::ParseError{where, "unknown operation"};
|
||||
|
@ -645,6 +653,10 @@ bool prune_unreachable(std::unique_ptr<Op>& ops) {
|
|||
reach = true;
|
||||
break;
|
||||
}
|
||||
case Op::_TryCatch: {
|
||||
reach = prune_unreachable(op.block0) | prune_unreachable(op.block1);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
std::cerr << "fatal: unknown operation <??" << op.cl << ">\n";
|
||||
throw src::ParseError{op.where, "unknown operation in prune_unreachable()"};
|
||||
|
@ -825,6 +837,12 @@ VarDescrList Op::fwd_analyze(VarDescrList values) {
|
|||
values = block0->fwd_analyze(values);
|
||||
break;
|
||||
}
|
||||
case _TryCatch: {
|
||||
VarDescrList val1 = block0->fwd_analyze(values);
|
||||
VarDescrList val2 = block1->fwd_analyze(std::move(values));
|
||||
values = val1 | val2;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
std::cerr << "fatal: unknown operation <??" << cl << ">\n";
|
||||
throw src::ParseError{where, "unknown operation in fwd_analyze()"};
|
||||
|
@ -866,6 +884,7 @@ bool Op::mark_noreturn() {
|
|||
case _Return:
|
||||
return set_noreturn(true);
|
||||
case _If:
|
||||
case _TryCatch:
|
||||
return set_noreturn((block0->mark_noreturn() & (block1 && block1->mark_noreturn())) | next->mark_noreturn());
|
||||
case _Again:
|
||||
block0->mark_noreturn();
|
||||
|
|
|
@ -986,6 +986,38 @@ AsmOp compile_cond_throw(std::vector<VarDescr>& res, std::vector<VarDescr>& args
|
|||
}
|
||||
}
|
||||
|
||||
AsmOp compile_throw_arg(std::vector<VarDescr>& res, std::vector<VarDescr>& args) {
|
||||
assert(res.empty() && args.size() == 2);
|
||||
VarDescr &x = args[1];
|
||||
if (x.is_int_const() && x.int_const->unsigned_fits_bits(11)) {
|
||||
x.unused();
|
||||
return exec_arg_op("THROWARG", x.int_const, 1, 0);
|
||||
} else {
|
||||
return exec_op("THROWARGANY", 2, 0);
|
||||
}
|
||||
}
|
||||
|
||||
AsmOp compile_cond_throw_arg(std::vector<VarDescr>& res, std::vector<VarDescr>& args, bool mode) {
|
||||
assert(res.empty() && args.size() == 3);
|
||||
VarDescr &x = args[1], &y = args[2];
|
||||
std::string suff = (mode ? "IF" : "IFNOT");
|
||||
bool skip_cond = false;
|
||||
if (y.always_true() || y.always_false()) {
|
||||
y.unused();
|
||||
skip_cond = true;
|
||||
if (y.always_true() != mode) {
|
||||
x.unused();
|
||||
return AsmOp::Nop();
|
||||
}
|
||||
}
|
||||
if (x.is_int_const() && x.int_const->unsigned_fits_bits(11)) {
|
||||
x.unused();
|
||||
return skip_cond ? exec_arg_op("THROWARG", x.int_const, 1, 0) : exec_arg_op("THROWARG"s + suff, x.int_const, 2, 0);
|
||||
} else {
|
||||
return skip_cond ? exec_op("THROWARGANY", 2, 0) : exec_op("THROWARGANY"s + suff, 3, 0);
|
||||
}
|
||||
}
|
||||
|
||||
AsmOp compile_bool_const(std::vector<VarDescr>& res, std::vector<VarDescr>& args, bool val) {
|
||||
assert(res.size() == 1 && args.empty());
|
||||
VarDescr& r = res[0];
|
||||
|
@ -1111,6 +1143,8 @@ void define_builtins() {
|
|||
auto fetch_slice_op = TypeExpr::new_map(SliceInt, TypeExpr::new_tensor({Slice, Slice}));
|
||||
auto prefetch_slice_op = TypeExpr::new_map(SliceInt, Slice);
|
||||
//auto arith_null_op = TypeExpr::new_map(TypeExpr::new_unit(), Int);
|
||||
auto throw_arg_op = TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_tensor({X, Int}), Unit));
|
||||
auto cond_throw_arg_op = TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_tensor({X, Int, Int}), Unit));
|
||||
define_builtin_func("_+_", arith_bin_op, compile_add);
|
||||
define_builtin_func("_-_", arith_bin_op, compile_sub);
|
||||
define_builtin_func("-_", arith_un_op, compile_negate);
|
||||
|
@ -1170,6 +1204,9 @@ void define_builtins() {
|
|||
define_builtin_func("throw", impure_un_op, compile_throw, true);
|
||||
define_builtin_func("throw_if", impure_bin_op, std::bind(compile_cond_throw, _1, _2, true), true);
|
||||
define_builtin_func("throw_unless", impure_bin_op, std::bind(compile_cond_throw, _1, _2, false), true);
|
||||
define_builtin_func("throw_arg", throw_arg_op, compile_throw_arg, true);
|
||||
define_builtin_func("throw_arg_if", cond_throw_arg_op, std::bind(compile_cond_throw_arg, _1, _2, true), true);
|
||||
define_builtin_func("throw_arg_unless", cond_throw_arg_op, std::bind(compile_cond_throw_arg, _1, _2, false), true);
|
||||
define_builtin_func("load_int", fetch_int_op, std::bind(compile_fetch_int, _1, _2, true, true), {}, {1, 0});
|
||||
define_builtin_func("load_uint", fetch_int_op, std::bind(compile_fetch_int, _1, _2, true, false), {}, {1, 0});
|
||||
define_builtin_func("preload_int", prefetch_int_op, std::bind(compile_fetch_int, _1, _2, false, true));
|
||||
|
|
|
@ -782,6 +782,77 @@ bool Op::generate_code_step(Stack& stack) {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
case _TryCatch: {
|
||||
if (block0->is_empty() && block1->is_empty()) {
|
||||
return true;
|
||||
}
|
||||
if (block0->noreturn() || block1->noreturn()) {
|
||||
stack.o.retalt_ = true;
|
||||
}
|
||||
Stack catch_stack{stack.o};
|
||||
std::vector<var_idx_t> catch_vars;
|
||||
std::vector<bool> catch_last;
|
||||
for (const VarDescr& var : block1->var_info.list) {
|
||||
if (stack.find(var.idx) >= 0) {
|
||||
catch_vars.push_back(var.idx);
|
||||
catch_last.push_back(!block0->var_info[var.idx]);
|
||||
}
|
||||
}
|
||||
const size_t block_size = 255;
|
||||
for (size_t begin = catch_vars.size(), end = begin; end > 0; end = begin) {
|
||||
begin = end >= block_size ? end - block_size : 0;
|
||||
for (size_t i = begin; i < end; ++i) {
|
||||
catch_stack.push_new_var(catch_vars[i]);
|
||||
}
|
||||
}
|
||||
catch_stack.push_new_var(left[0]);
|
||||
catch_stack.push_new_var(left[1]);
|
||||
stack.rearrange_top(catch_vars, catch_last);
|
||||
stack.opt_show();
|
||||
stack.o << "c4 PUSH";
|
||||
stack.o << "c5 PUSH";
|
||||
stack.o << "c7 PUSH";
|
||||
stack.o << "<{";
|
||||
stack.o.indent();
|
||||
if (block1->noreturn()) {
|
||||
catch_stack.mode |= Stack::_NeedRetAlt;
|
||||
}
|
||||
block1->generate_code_all(catch_stack);
|
||||
catch_stack.drop_vars_except(next->var_info);
|
||||
catch_stack.opt_show();
|
||||
stack.o.undent();
|
||||
stack.o << "}>CONT";
|
||||
stack.o << "c7 SETCONT";
|
||||
stack.o << "c5 SETCONT";
|
||||
stack.o << "c4 SETCONT";
|
||||
for (size_t begin = catch_vars.size(), end = begin; end > 0; end = begin) {
|
||||
begin = end >= block_size ? end - block_size : 0;
|
||||
stack.o << std::to_string(end - begin) + " PUSHINT";
|
||||
stack.o << "-1 PUSHINT";
|
||||
stack.o << "SETCONTVARARGS";
|
||||
}
|
||||
stack.s.erase(stack.s.end() - catch_vars.size(), stack.s.end());
|
||||
stack.modified();
|
||||
stack.o << "<{";
|
||||
stack.o.indent();
|
||||
if (block0->noreturn()) {
|
||||
stack.mode |= Stack::_NeedRetAlt;
|
||||
}
|
||||
block0->generate_code_all(stack);
|
||||
if (block0->noreturn()) {
|
||||
stack.s = std::move(catch_stack.s);
|
||||
} else if (!block1->noreturn()) {
|
||||
stack.merge_state(catch_stack);
|
||||
}
|
||||
stack.opt_show();
|
||||
stack.o.undent();
|
||||
stack.o << "}>CONT";
|
||||
stack.o << "c1 PUSH";
|
||||
stack.o << "COMPOSALT";
|
||||
stack.o << "SWAP";
|
||||
stack.o << "TRY";
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
std::cerr << "fatal: unknown operation <??" << cl << ">\n";
|
||||
throw src::ParseError{where, "unknown operation in generate_code()"};
|
||||
|
|
|
@ -53,6 +53,8 @@ enum Keyword {
|
|||
_Do,
|
||||
_While,
|
||||
_Until,
|
||||
_Try,
|
||||
_Catch,
|
||||
_If,
|
||||
_Ifnot,
|
||||
_Then,
|
||||
|
@ -537,6 +539,7 @@ struct Op {
|
|||
_Until,
|
||||
_Repeat,
|
||||
_Again,
|
||||
_TryCatch,
|
||||
_SliceConst
|
||||
};
|
||||
int cl;
|
||||
|
@ -1559,6 +1562,9 @@ struct Stack {
|
|||
int find_outside(var_idx_t var, int from, int to) const;
|
||||
void forget_const();
|
||||
void validate(int i) const {
|
||||
if (i > 255) {
|
||||
throw src::Fatal{"Too deep stack"};
|
||||
}
|
||||
assert(i >= 0 && i < depth() && "invalid stack reference");
|
||||
}
|
||||
void modified() {
|
||||
|
@ -1593,6 +1599,7 @@ struct Stack {
|
|||
void apply_wrappers() {
|
||||
if (o.retalt_) {
|
||||
o.insert(0, "SAMEALTSAVE");
|
||||
o.insert(0, "c2 SAVE");
|
||||
if (mode & _InlineFunc) {
|
||||
o.indent_all();
|
||||
o.insert(0, "CONT:<{");
|
||||
|
|
|
@ -97,6 +97,8 @@ void define_keywords() {
|
|||
.add_keyword("do", Kw::_Do)
|
||||
.add_keyword("while", Kw::_While)
|
||||
.add_keyword("until", Kw::_Until)
|
||||
.add_keyword("try", Kw::_Try)
|
||||
.add_keyword("catch", Kw::_Catch)
|
||||
.add_keyword("if", Kw::_If)
|
||||
.add_keyword("ifnot", Kw::_Ifnot)
|
||||
.add_keyword("then", Kw::_Then)
|
||||
|
|
|
@ -1102,6 +1102,36 @@ blk_fl::val parse_do_stmt(Lexer& lex, CodeBlob& code) {
|
|||
return res & ~blk_fl::empty;
|
||||
}
|
||||
|
||||
blk_fl::val parse_try_catch_stmt(Lexer& lex, CodeBlob& code) {
|
||||
lex.expect(_Try);
|
||||
Op& try_catch_op = code.emplace_back(lex.cur().loc, Op::_TryCatch);
|
||||
code.push_set_cur(try_catch_op.block0);
|
||||
blk_fl::val res0 = parse_block_stmt(lex, code);
|
||||
code.close_pop_cur(lex.cur().loc);
|
||||
lex.expect(_Catch);
|
||||
code.push_set_cur(try_catch_op.block1);
|
||||
sym::open_scope(lex);
|
||||
Expr* expr = parse_expr(lex, code, true);
|
||||
expr->chk_lvalue(lex.cur());
|
||||
TypeExpr* tvm_error_type = TypeExpr::new_tensor(TypeExpr::new_var(), TypeExpr::new_atomic(_Int));
|
||||
try {
|
||||
unify(expr->e_type, tvm_error_type);
|
||||
} catch (UnifyError& ue) {
|
||||
std::ostringstream os;
|
||||
os << "`catch` arguments have incorrect type " << expr->e_type << ": " << ue;
|
||||
lex.cur().error(os.str());
|
||||
}
|
||||
expr->predefine_vars();
|
||||
expr->define_new_vars(code);
|
||||
try_catch_op.left = expr->pre_compile(code);
|
||||
assert(try_catch_op.left.size() == 2);
|
||||
blk_fl::val res1 = parse_block_stmt(lex, code);
|
||||
sym::close_scope(lex);
|
||||
code.close_pop_cur(lex.cur().loc);
|
||||
blk_fl::combine_parallel(res0, res1);
|
||||
return res0;
|
||||
}
|
||||
|
||||
blk_fl::val parse_if_stmt(Lexer& lex, CodeBlob& code, int first_lex = _If) {
|
||||
SrcLocation loc{lex.cur().loc};
|
||||
lex.expect(first_lex);
|
||||
|
@ -1165,6 +1195,8 @@ blk_fl::val parse_stmt(Lexer& lex, CodeBlob& code) {
|
|||
return parse_do_stmt(lex, code);
|
||||
case _While:
|
||||
return parse_while_stmt(lex, code);
|
||||
case _Try:
|
||||
return parse_try_catch_stmt(lex, code);
|
||||
default: {
|
||||
auto expr = parse_expr(lex, code);
|
||||
expr->chk_rvalue(lex.cur());
|
||||
|
|
113
crypto/func/test/tc1.fc
Normal file
113
crypto/func/test/tc1.fc
Normal file
|
@ -0,0 +1,113 @@
|
|||
() test1() impure {
|
||||
int i = 3;
|
||||
repeat (3) {
|
||||
try {
|
||||
int j = i;
|
||||
i *= 2;
|
||||
throw_unless(500, j <= 10);
|
||||
} catch (x, e) {
|
||||
i -= 2;
|
||||
}
|
||||
i += i + 1;
|
||||
}
|
||||
throw_unless(501, i == 43);
|
||||
}
|
||||
|
||||
int divide_by_ten(int num) {
|
||||
try {
|
||||
throw_unless(500, num < 10);
|
||||
} catch (x, e) {
|
||||
return divide_by_ten(num - 10) + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
() test2() impure {
|
||||
int n = divide_by_ten(37);
|
||||
throw_unless(502, n == 3);
|
||||
}
|
||||
|
||||
(int, int) swap_int(int a, int b) {
|
||||
try {
|
||||
a = a * b;
|
||||
b = a / b;
|
||||
a = a / b;
|
||||
return (a, b);
|
||||
} catch (x, e) {
|
||||
throw_unless(500, b == 0);
|
||||
}
|
||||
return (0, a);
|
||||
}
|
||||
|
||||
() test3() impure {
|
||||
int a = 0;
|
||||
int b = 57;
|
||||
try {
|
||||
(a, b) = swap_int(a, b);
|
||||
} catch (x, e) {
|
||||
throw_unless(500, a == 0);
|
||||
a = b;
|
||||
b = 0;
|
||||
}
|
||||
throw_unless(503, (a == 57) & (b == 0));
|
||||
}
|
||||
|
||||
int get_x(int x, int y) {
|
||||
try {
|
||||
} catch (x, e) {
|
||||
return -1;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
int get_y(int x, int y) {
|
||||
try {
|
||||
return -1;
|
||||
} catch (x, e) {
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
() test4() impure {
|
||||
throw_unless(504, get_x(3, 4) == 3);
|
||||
throw_unless(504, get_y(3, 4) == -1);
|
||||
}
|
||||
|
||||
(int, int, int, int, int) foo(int a, int b, int c, int d, int e) {
|
||||
try {
|
||||
throw(11);
|
||||
} catch (x, y) {
|
||||
a += 1;
|
||||
b += 2;
|
||||
c += 3;
|
||||
d += 4;
|
||||
e += 5;
|
||||
}
|
||||
return (a, b, c, d, e);
|
||||
}
|
||||
|
||||
() test5() impure {
|
||||
var (a, b, c, d, e) = foo(10, 20, 30, 40, 50);
|
||||
throw_unless(505, (a == 11) & (b == 22) & (c == 33) & (d == 44) & (e == 55));
|
||||
}
|
||||
|
||||
() test6() impure {
|
||||
int a = 0;
|
||||
int b = 0;
|
||||
int c = 0;
|
||||
try {
|
||||
b = 3;
|
||||
} catch (x, y) {
|
||||
b = 12;
|
||||
}
|
||||
throw_unless(506, (a == 0) & (b == 3) & (c == 0));
|
||||
}
|
||||
|
||||
() main() {
|
||||
test1();
|
||||
test2();
|
||||
test3();
|
||||
test4();
|
||||
test5();
|
||||
test6();
|
||||
}
|
84
crypto/func/test/tc2.fc
Normal file
84
crypto/func/test/tc2.fc
Normal file
|
@ -0,0 +1,84 @@
|
|||
forall X -> int cast_to_int(X x) asm "NOP";
|
||||
forall X -> builder cast_to_builder(X x) asm "NOP";
|
||||
|
||||
_ test1_body() {
|
||||
int a = 3;
|
||||
builder b = begin_cell();
|
||||
int c = 1;
|
||||
try {
|
||||
c = 3;
|
||||
throw_arg(b, 100);
|
||||
} catch (x, y) {
|
||||
return (a + c + y, cast_to_builder(x));
|
||||
}
|
||||
return (0, null());
|
||||
}
|
||||
|
||||
() test1() impure {
|
||||
var (x, y) = test1_body();
|
||||
throw_unless(101, x == 104);
|
||||
throw_unless(102, y.builder_refs() == y.builder_bits());
|
||||
}
|
||||
|
||||
_ test2_body(int a, int b, int c) {
|
||||
try {
|
||||
try {
|
||||
try {
|
||||
try {
|
||||
throw_arg_if(1, 201, a + b + c == 3);
|
||||
throw_arg_if(2, 201, a == 3);
|
||||
throw_arg_unless(1, 202, b == 4);
|
||||
return 1;
|
||||
} catch (y, x) {
|
||||
int y = y.cast_to_int();
|
||||
throw_arg_unless(y, x, x == 202);
|
||||
throw_arg(y + 1, 200);
|
||||
}
|
||||
} catch (y, x) {
|
||||
int y = y.cast_to_int();
|
||||
throw_arg_if(y, x, x == 200);
|
||||
throw_arg_if(y + 2, x, y < 2);
|
||||
throw_arg_if(y + 3, 203, a + b + c == 4);
|
||||
throw_arg_unless(y + 4, 204, b == 4);
|
||||
return 3;
|
||||
}
|
||||
} catch (y, x) {
|
||||
int y = y.cast_to_int();
|
||||
try {
|
||||
throw_arg_if(y, x, x == 200);
|
||||
throw_arg_if(y + 1, 200, x == 201);
|
||||
throw_arg_if(x - 203, 200, x == 202);
|
||||
throw_arg_if(y, 200, x == 203);
|
||||
throw_arg_if(a + 4, 205, a + b + c == 5);
|
||||
throw_arg(7, 200);
|
||||
} catch (v, u) {
|
||||
int v = v.cast_to_int();
|
||||
throw_arg_unless(v, u, u == 205);
|
||||
if (c == 0) {
|
||||
return b + 4;
|
||||
}
|
||||
throw_arg(v + 1, 200);
|
||||
}
|
||||
}
|
||||
} catch (y, x) {
|
||||
throw_unless(x, x == 200);
|
||||
return y.cast_to_int();
|
||||
}
|
||||
return null();
|
||||
}
|
||||
|
||||
() test2() impure {
|
||||
throw_unless(201, test2_body(0, 4, 0) == 1);
|
||||
throw_unless(202, test2_body(0, 5, 0) == 2);
|
||||
throw_unless(203, test2_body(3, 4, 0) == 3);
|
||||
throw_unless(204, test2_body(3, 0, 0) == 4);
|
||||
throw_unless(205, test2_body(3, 1, 0) == 5);
|
||||
throw_unless(206, test2_body(3, 2, 0) == 6);
|
||||
throw_unless(207, test2_body(3, 1, 2) == 7);
|
||||
throw_unless(208, test2_body(3, 1, 1) == 8);
|
||||
}
|
||||
|
||||
() main() {
|
||||
test1();
|
||||
test2();
|
||||
}
|
Loading…
Reference in a new issue