mirror of
https://github.com/ton-blockchain/ton
synced 2025-03-09 15:40:10 +00:00
[Tolk] throw
interrupts control flow; never
type
In FunC (and in Tolk before) throwing an exception is just calling a built-in function: > throw 123; // actually, __throw(123) Since it's a regular function, the compiler was not aware that execution will stop, and all following code is unreachable. For instance, `throw` in the end on function needed to be followed by `return` statement. Now, `throw` interrupts control flow, all statements after it are considered unreachable. At IR level, code Ops are also not produced. This works because a built-in __throw() now has `never` type. It can also be applied to custom functions: > fun alwaysThrow(): never { throw 123; } The code after alwaysThrow() call will also be unreachable.
This commit is contained in:
parent
7bcb8b895f
commit
ef0328837f
10 changed files with 227 additions and 25 deletions
|
@ -86,6 +86,17 @@ fun test7() {
|
||||||
// __expect_type(eq<(int, slice)>, "(int, slice) -> (int, slice)");
|
// __expect_type(eq<(int, slice)>, "(int, slice) -> (int, slice)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun alwaysThrows(): never { throw 123; }
|
||||||
|
fun alwaysThrowsNotAnnotated() { throw 123; }
|
||||||
|
fun alwaysThrowsNotAnnotated2() { alwaysThrows(); }
|
||||||
|
|
||||||
|
fun test9() {
|
||||||
|
__expect_type(alwaysThrows(), "never");
|
||||||
|
__expect_type(alwaysThrows, "() -> never");
|
||||||
|
__expect_type(alwaysThrowsNotAnnotated(), "void");
|
||||||
|
__expect_type(alwaysThrowsNotAnnotated2(), "void");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
8
tolk-tester/tests/invalid-never-1.tolk
Normal file
8
tolk-tester/tests/invalid-never-1.tolk
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
fun invalidNever(): never {
|
||||||
|
if (random()) { throw 123; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@compilation_should_fail
|
||||||
|
@stderr a function returning `never` can not have a reachable endpoint
|
||||||
|
*/
|
|
@ -164,6 +164,78 @@ fun test109(): (int, int) {
|
||||||
return (g_reg, l_reg);
|
return (g_reg, l_reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun alwaysThrow123(): never {
|
||||||
|
throw 123;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun alwaysThrowX(x: int): never {
|
||||||
|
if (x > 10) { throw (x, beginCell()); }
|
||||||
|
else { throw (x, null); }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun anotherNever(throw123: bool): never {
|
||||||
|
if (throw123) { alwaysThrow123(); }
|
||||||
|
alwaysThrowX(456);
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testCodegen1(x: int) {
|
||||||
|
if (x > 10) {
|
||||||
|
throw 123;
|
||||||
|
anotherNever(true); // unreachable, will be dropped
|
||||||
|
}
|
||||||
|
else if (x < 10) {
|
||||||
|
throw x;
|
||||||
|
return -123; // unreachable, will be dropped
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testCodegen2(x: int) {
|
||||||
|
if (x > 10) {
|
||||||
|
alwaysThrow123();
|
||||||
|
anotherNever(true); // unreachable, will be dropped
|
||||||
|
}
|
||||||
|
else if (x < 10) {
|
||||||
|
anotherNever(false);
|
||||||
|
return -123; // unreachable, will be dropped
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@method_id(110)
|
||||||
|
fun test110(b: bool) {
|
||||||
|
try {
|
||||||
|
if (b == true) { testCodegen1(100); }
|
||||||
|
testCodegen1(5);
|
||||||
|
return -1;
|
||||||
|
} catch (ex) {
|
||||||
|
return ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@method_id(111)
|
||||||
|
fun test111(b: bool) {
|
||||||
|
try {
|
||||||
|
if (b == true) { testCodegen2(100); }
|
||||||
|
testCodegen2(5);
|
||||||
|
return -1;
|
||||||
|
} catch (ex) {
|
||||||
|
return ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mySetCode(newCode: slice): void
|
||||||
|
asm "SETCODE";
|
||||||
|
|
||||||
|
fun testCodegen3(numberId: int, paramVal: cell) {
|
||||||
|
if (numberId == -1000) {
|
||||||
|
var cs = paramVal.beginParse();
|
||||||
|
mySetCode(cs);
|
||||||
|
throw 0;
|
||||||
|
}
|
||||||
|
paramVal.beginParse();
|
||||||
|
}
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,6 +259,65 @@ fun main() {
|
||||||
@testcase | 107 | 5 | 5
|
@testcase | 107 | 5 | 5
|
||||||
@testcase | 107 | 20 | 20
|
@testcase | 107 | 20 | 20
|
||||||
@testcase | 108 | | 0
|
@testcase | 108 | | 0
|
||||||
|
@testcase | 109 | | 10 10
|
||||||
|
@testcase | 110 | -1 | 123
|
||||||
|
@testcase | 110 | 0 | 5
|
||||||
|
@testcase | 111 | -1 | 123
|
||||||
|
@testcase | 111 | 0 | 456
|
||||||
|
|
||||||
@code_hash 39307974281105539319288356721945232226028429128341177951717392648324358675585
|
@code_hash 57361460846265694653029920796509802052573595128418810728101968091567195330515
|
||||||
|
|
||||||
|
@fif_codegen
|
||||||
|
"""
|
||||||
|
testCodegen1 PROC:<{
|
||||||
|
// x
|
||||||
|
DUP // x x
|
||||||
|
10 GTINT // x '2
|
||||||
|
IFJMP:<{ // x
|
||||||
|
123 THROW
|
||||||
|
}> // x
|
||||||
|
DUP // x x
|
||||||
|
10 LESSINT // x '6
|
||||||
|
IFJMP:<{ // x
|
||||||
|
THROWANY
|
||||||
|
}> // x
|
||||||
|
DROP //
|
||||||
|
0 PUSHINT // '8=0
|
||||||
|
}>
|
||||||
|
"""
|
||||||
|
|
||||||
|
@fif_codegen
|
||||||
|
"""
|
||||||
|
testCodegen2 PROC:<{
|
||||||
|
// x
|
||||||
|
DUP // x x
|
||||||
|
10 GTINT // x '2
|
||||||
|
IFJMP:<{ // x
|
||||||
|
DROP //
|
||||||
|
alwaysThrow123 CALLDICT
|
||||||
|
}> // x
|
||||||
|
10 LESSINT // '5
|
||||||
|
IFJMP:<{ //
|
||||||
|
FALSE // '6
|
||||||
|
anotherNever CALLDICT
|
||||||
|
}> //
|
||||||
|
0 PUSHINT // '8=0
|
||||||
|
}>
|
||||||
|
"""
|
||||||
|
|
||||||
|
@fif_codegen
|
||||||
|
"""
|
||||||
|
testCodegen3 PROC:<{
|
||||||
|
// numberId paramVal
|
||||||
|
SWAP
|
||||||
|
-1000 PUSHINT // paramVal numberId '2=-1000
|
||||||
|
EQUAL // paramVal '3
|
||||||
|
IFJMP:<{ // paramVal
|
||||||
|
CTOS // cs
|
||||||
|
SETCODE
|
||||||
|
0 THROW
|
||||||
|
}> // paramVal
|
||||||
|
DROP //
|
||||||
|
}>
|
||||||
|
"""
|
||||||
*/
|
*/
|
||||||
|
|
24
tolk-tester/tests/unreachable-4.tolk
Normal file
24
tolk-tester/tests/unreachable-4.tolk
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
fun alwaysThrows(): never {
|
||||||
|
throw 456;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testUnreachable(x: int) {
|
||||||
|
if (x) { throw 123; }
|
||||||
|
else { alwaysThrows(); }
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
try {
|
||||||
|
testUnreachable(100);
|
||||||
|
throw 80;
|
||||||
|
} catch (excNo) {
|
||||||
|
return excNo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@testcase | 0 | | 123
|
||||||
|
@stderr warning: unreachable code
|
||||||
|
@stderr return 1;
|
||||||
|
*/
|
|
@ -20,6 +20,13 @@
|
||||||
|
|
||||||
namespace tolk {
|
namespace tolk {
|
||||||
|
|
||||||
|
// functions returning "never" are assumed to interrupt flow
|
||||||
|
// for instance, variables after their call aren't considered used
|
||||||
|
// its main purpose is `throw` statement, it's a call to a built-in `__throw` function
|
||||||
|
static bool does_function_always_throw(FunctionPtr fun_ref) {
|
||||||
|
return fun_ref->declared_return_type == TypeDataNever::create();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* ANALYZE AND PREPROCESS ABSTRACT CODE
|
* ANALYZE AND PREPROCESS ABSTRACT CODE
|
||||||
|
@ -262,17 +269,6 @@ VarDescrList& VarDescrList::operator|=(const VarDescrList& y) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VarDescrList& VarDescrList::operator&=(const VarDescrList& values) {
|
|
||||||
for (const VarDescr& vd : values.list) {
|
|
||||||
VarDescr* item = operator[](vd.idx);
|
|
||||||
if (item) {
|
|
||||||
*item &= vd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unreachable |= values.unreachable;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
VarDescrList& VarDescrList::import_values(const VarDescrList& values) {
|
VarDescrList& VarDescrList::import_values(const VarDescrList& values) {
|
||||||
if (values.unreachable) {
|
if (values.unreachable) {
|
||||||
set_unreachable();
|
set_unreachable();
|
||||||
|
@ -326,6 +322,17 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) {
|
||||||
}
|
}
|
||||||
return std_compute_used_vars(true);
|
return std_compute_used_vars(true);
|
||||||
}
|
}
|
||||||
|
if (cl == _Call && does_function_always_throw(f_sym)) {
|
||||||
|
VarDescrList new_var_info; // empty, not next->var_info
|
||||||
|
if (args.size() == right.size()) {
|
||||||
|
for (const VarDescr& arg : args) {
|
||||||
|
new_var_info.add_var(arg.idx, arg.is_unused());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
new_var_info.add_vars(right, false);
|
||||||
|
}
|
||||||
|
return set_var_info(std::move(new_var_info));
|
||||||
|
}
|
||||||
return std_compute_used_vars();
|
return std_compute_used_vars();
|
||||||
}
|
}
|
||||||
case _SetGlob: {
|
case _SetGlob: {
|
||||||
|
@ -516,20 +523,19 @@ bool prune_unreachable(std::unique_ptr<Op>& ops) {
|
||||||
case Op::_SliceConst:
|
case Op::_SliceConst:
|
||||||
case Op::_GlobVar:
|
case Op::_GlobVar:
|
||||||
case Op::_SetGlob:
|
case Op::_SetGlob:
|
||||||
case Op::_Call:
|
|
||||||
case Op::_CallInd:
|
case Op::_CallInd:
|
||||||
case Op::_Tuple:
|
case Op::_Tuple:
|
||||||
case Op::_UnTuple:
|
case Op::_UnTuple:
|
||||||
case Op::_Import:
|
case Op::_Import:
|
||||||
|
case Op::_Let:
|
||||||
reach = true;
|
reach = true;
|
||||||
break;
|
break;
|
||||||
case Op::_Let: {
|
|
||||||
reach = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Op::_Return:
|
case Op::_Return:
|
||||||
reach = false;
|
reach = false;
|
||||||
break;
|
break;
|
||||||
|
case Op::_Call:
|
||||||
|
reach = !does_function_always_throw(op.f_sym);
|
||||||
|
break;
|
||||||
case Op::_If: {
|
case Op::_If: {
|
||||||
// if left then block0 else block1; ...
|
// if left then block0 else block1; ...
|
||||||
VarDescr* c_var = op.var_info[op.left[0]];
|
VarDescr* c_var = op.var_info[op.left[0]];
|
||||||
|
@ -712,6 +718,9 @@ VarDescrList Op::fwd_analyze(VarDescrList values) {
|
||||||
values.add_newval(i);
|
values.add_newval(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (does_function_always_throw(f_sym)) {
|
||||||
|
values.set_unreachable();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case _Tuple:
|
case _Tuple:
|
||||||
|
@ -860,10 +869,11 @@ bool Op::mark_noreturn() {
|
||||||
case _SetGlob:
|
case _SetGlob:
|
||||||
case _GlobVar:
|
case _GlobVar:
|
||||||
case _CallInd:
|
case _CallInd:
|
||||||
case _Call:
|
|
||||||
return set_noreturn(next->mark_noreturn());
|
return set_noreturn(next->mark_noreturn());
|
||||||
case _Return:
|
case _Return:
|
||||||
return set_noreturn();
|
return set_noreturn();
|
||||||
|
case _Call:
|
||||||
|
return set_noreturn(next->mark_noreturn() || does_function_always_throw(f_sym));
|
||||||
case _If:
|
case _If:
|
||||||
case _TryCatch:
|
case _TryCatch:
|
||||||
// note, that & | (not && ||) here and below is mandatory to invoke both left and right calls
|
// note, that & | (not && ||) here and below is mandatory to invoke both left and right calls
|
||||||
|
|
|
@ -1088,6 +1088,7 @@ void define_builtins() {
|
||||||
TypePtr Slice = TypeDataSlice::create();
|
TypePtr Slice = TypeDataSlice::create();
|
||||||
TypePtr Builder = TypeDataBuilder::create();
|
TypePtr Builder = TypeDataBuilder::create();
|
||||||
TypePtr Tuple = TypeDataTuple::create();
|
TypePtr Tuple = TypeDataTuple::create();
|
||||||
|
TypePtr Never = TypeDataNever::create();
|
||||||
|
|
||||||
std::vector<GenericsDeclaration::GenericsItem> itemsT;
|
std::vector<GenericsDeclaration::GenericsItem> itemsT;
|
||||||
itemsT.emplace_back("T");
|
itemsT.emplace_back("T");
|
||||||
|
@ -1201,10 +1202,10 @@ void define_builtins() {
|
||||||
define_builtin_func("__isNull", {typeT}, Bool, declGenericT,
|
define_builtin_func("__isNull", {typeT}, Bool, declGenericT,
|
||||||
compile_is_null,
|
compile_is_null,
|
||||||
FunctionData::flagMarkedAsPure);
|
FunctionData::flagMarkedAsPure);
|
||||||
define_builtin_func("__throw", ParamsInt1, Unit, nullptr,
|
define_builtin_func("__throw", ParamsInt1, Never, nullptr,
|
||||||
compile_throw,
|
compile_throw,
|
||||||
0);
|
0);
|
||||||
define_builtin_func("__throw_arg", {typeT, Int}, Unit, declGenericT,
|
define_builtin_func("__throw_arg", {typeT, Int}, Never, declGenericT,
|
||||||
compile_throw_arg,
|
compile_throw_arg,
|
||||||
0);
|
0);
|
||||||
define_builtin_func("__throw_if_unless", ParamsInt3, Unit, nullptr,
|
define_builtin_func("__throw_if_unless", ParamsInt3, Unit, nullptr,
|
||||||
|
|
|
@ -274,8 +274,16 @@ void Stack::rearrange_top(var_idx_t top, bool last) {
|
||||||
|
|
||||||
bool Op::generate_code_step(Stack& stack) {
|
bool Op::generate_code_step(Stack& stack) {
|
||||||
stack.opt_show();
|
stack.opt_show();
|
||||||
|
|
||||||
|
// detect `throw 123` (actually _IntConst 123 + _Call __throw)
|
||||||
|
// don't clear the stack, since dropping unused elements make no sense, an exception is thrown anyway
|
||||||
|
bool will_now_immediate_throw = (cl == _Call && f_sym->is_builtin_function() && f_sym->name == "__throw")
|
||||||
|
|| (cl == _IntConst && next->cl == _Call && next->f_sym->is_builtin_function() && next->f_sym->name == "__throw");
|
||||||
|
if (!will_now_immediate_throw) {
|
||||||
stack.drop_vars_except(var_info);
|
stack.drop_vars_except(var_info);
|
||||||
stack.opt_show();
|
stack.opt_show();
|
||||||
|
}
|
||||||
|
|
||||||
bool inline_func = stack.mode & Stack::_InlineFunc;
|
bool inline_func = stack.mode & Stack::_InlineFunc;
|
||||||
switch (cl) {
|
switch (cl) {
|
||||||
case _Nop:
|
case _Nop:
|
||||||
|
@ -285,6 +293,7 @@ bool Op::generate_code_step(Stack& stack) {
|
||||||
stack.enforce_state(left);
|
stack.enforce_state(left);
|
||||||
if (stack.o.retalt_ && (stack.mode & Stack::_NeedRetAlt)) {
|
if (stack.o.retalt_ && (stack.mode & Stack::_NeedRetAlt)) {
|
||||||
stack.o << "RETALT";
|
stack.o << "RETALT";
|
||||||
|
stack.o.retalt_inserted_ = true;
|
||||||
}
|
}
|
||||||
stack.opt_show();
|
stack.opt_show();
|
||||||
return false;
|
return false;
|
||||||
|
@ -514,7 +523,7 @@ bool Op::generate_code_step(Stack& stack) {
|
||||||
int j = ret_order ? ret_order->at(i) : i;
|
int j = ret_order ? ret_order->at(i) : i;
|
||||||
stack.push_new_var(left.at(j));
|
stack.push_new_var(left.at(j));
|
||||||
}
|
}
|
||||||
return true;
|
return !f_sym || f_sym->declared_return_type != TypeDataNever::create();
|
||||||
}
|
}
|
||||||
case _SetGlob: {
|
case _SetGlob: {
|
||||||
tolk_assert(g_sym);
|
tolk_assert(g_sym);
|
||||||
|
|
|
@ -1013,6 +1013,9 @@ class InferTypesAndCallsAndFieldsVisitor final {
|
||||||
TypePtr inferred_type = dot_obj && fun_ref->does_return_self() ? dot_obj->inferred_type : fun_ref->inferred_return_type;
|
TypePtr inferred_type = dot_obj && fun_ref->does_return_self() ? dot_obj->inferred_type : fun_ref->inferred_return_type;
|
||||||
assign_inferred_type(v, inferred_type);
|
assign_inferred_type(v, inferred_type);
|
||||||
assign_inferred_type(callee, fun_ref->inferred_full_type);
|
assign_inferred_type(callee, fun_ref->inferred_full_type);
|
||||||
|
if (inferred_type == TypeDataNever::create()) {
|
||||||
|
flow.mark_unreachable(UnreachableKind::CallNeverReturnFunction);
|
||||||
|
}
|
||||||
// note, that mutate params don't affect typing, they are handled when converting to IR
|
// note, that mutate params don't affect typing, they are handled when converting to IR
|
||||||
return ExprFlow(std::move(flow), used_as_condition);
|
return ExprFlow(std::move(flow), used_as_condition);
|
||||||
}
|
}
|
||||||
|
@ -1139,6 +1142,7 @@ class InferTypesAndCallsAndFieldsVisitor final {
|
||||||
FlowContext process_throw_statement(V<ast_throw_statement> v, FlowContext&& flow) {
|
FlowContext process_throw_statement(V<ast_throw_statement> v, FlowContext&& flow) {
|
||||||
flow = infer_any_expr(v->get_thrown_code(), std::move(flow), false).out_flow;
|
flow = infer_any_expr(v->get_thrown_code(), std::move(flow), false).out_flow;
|
||||||
flow = infer_any_expr(v->get_thrown_arg(), std::move(flow), false).out_flow;
|
flow = infer_any_expr(v->get_thrown_arg(), std::move(flow), false).out_flow;
|
||||||
|
flow.mark_unreachable(UnreachableKind::ThrowStatement);
|
||||||
return flow;
|
return flow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1209,6 +1213,9 @@ public:
|
||||||
|
|
||||||
if (!body_end.is_unreachable()) {
|
if (!body_end.is_unreachable()) {
|
||||||
fun_ref->mutate()->assign_is_implicit_return();
|
fun_ref->mutate()->assign_is_implicit_return();
|
||||||
|
if (fun_ref->declared_return_type == TypeDataNever::create()) { // `never` can only be declared, it can't be inferred
|
||||||
|
fire(fun_ref, v_function->get_body()->as<ast_sequence>()->loc_end, "a function returning `never` can not have a reachable endpoint");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fun_ref->declared_return_type) {
|
if (!fun_ref->declared_return_type) {
|
||||||
|
|
|
@ -77,6 +77,7 @@ struct SinkExpression {
|
||||||
enum class UnreachableKind {
|
enum class UnreachableKind {
|
||||||
Unknown, // no definite info or not unreachable
|
Unknown, // no definite info or not unreachable
|
||||||
CantHappen,
|
CantHappen,
|
||||||
|
ThrowStatement,
|
||||||
ReturnStatement,
|
ReturnStatement,
|
||||||
CallNeverReturnFunction,
|
CallNeverReturnFunction,
|
||||||
};
|
};
|
||||||
|
|
|
@ -205,7 +205,6 @@ struct VarDescrList {
|
||||||
std::size_t count_used(const std::vector<var_idx_t> idx_list) const;
|
std::size_t count_used(const std::vector<var_idx_t> idx_list) const;
|
||||||
VarDescr& add(var_idx_t idx);
|
VarDescr& add(var_idx_t idx);
|
||||||
VarDescr& add_newval(var_idx_t idx);
|
VarDescr& add_newval(var_idx_t idx);
|
||||||
VarDescrList& operator&=(const VarDescrList& values);
|
|
||||||
VarDescrList& import_values(const VarDescrList& values);
|
VarDescrList& import_values(const VarDescrList& values);
|
||||||
VarDescrList operator|(const VarDescrList& y) const;
|
VarDescrList operator|(const VarDescrList& y) const;
|
||||||
VarDescrList& operator|=(const VarDescrList& values);
|
VarDescrList& operator|=(const VarDescrList& values);
|
||||||
|
@ -575,6 +574,7 @@ struct AsmOpList {
|
||||||
const std::vector<TmpVar>* var_names_{nullptr};
|
const std::vector<TmpVar>* var_names_{nullptr};
|
||||||
std::vector<Const> constants_;
|
std::vector<Const> constants_;
|
||||||
bool retalt_{false};
|
bool retalt_{false};
|
||||||
|
bool retalt_inserted_{false};
|
||||||
void out(std::ostream& os, int mode = 0) const;
|
void out(std::ostream& os, int mode = 0) const;
|
||||||
AsmOpList(int indent = 0, const std::vector<TmpVar>* var_names = nullptr) : indent_(indent), var_names_(var_names) {
|
AsmOpList(int indent = 0, const std::vector<TmpVar>* var_names = nullptr) : indent_(indent), var_names_(var_names) {
|
||||||
}
|
}
|
||||||
|
@ -1030,7 +1030,7 @@ struct Stack {
|
||||||
}
|
}
|
||||||
void apply_wrappers(int callxargs_count) {
|
void apply_wrappers(int callxargs_count) {
|
||||||
bool is_inline = mode & _InlineFunc;
|
bool is_inline = mode & _InlineFunc;
|
||||||
if (o.retalt_) {
|
if (o.retalt_inserted_) {
|
||||||
o.insert(0, "SAMEALTSAVE");
|
o.insert(0, "SAMEALTSAVE");
|
||||||
o.insert(0, "c2 SAVE");
|
o.insert(0, "c2 SAVE");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue