@@ -278,6 +278,53 @@ class LValContext {
278278 }
279279};
280280
281+ // the purpose of this class is having a call `f(a1,a2,...)` when f has asm arg_order, to check
282+ // whether it's safe to rearrange arguments (to evaluate them in arg_order right here for fewer stack manipulations)
283+ // or it's unsafe, and we should evaluate them left-to-right;
284+ // example: `f(1,2,3)` / `b.storeUint(2,32)` is safe
285+ // example: `f(x,x+=5,x)` / `f(impureF1(), global_var)` is unsafe
286+ class CheckReorderingForAsmArgOrderIsSafeVisitor final : public ASTVisitorFunctionBody {
287+ bool has_side_effects = false ;
288+
289+ protected:
290+ void visit (V<ast_function_call> v) override {
291+ has_side_effects |= v->fun_maybe == nullptr || !v->fun_maybe ->is_marked_as_pure ();
292+ parent::visit (v);
293+ }
294+
295+ void visit (V<ast_assign> v) override {
296+ has_side_effects = true ;
297+ parent::visit (v);
298+ }
299+
300+ void visit (V<ast_set_assign> v) override {
301+ has_side_effects = true ;
302+ parent::visit (v);
303+ }
304+
305+ public:
306+ bool should_visit_function (FunctionPtr fun_ref) override {
307+ tolk_assert (false );
308+ }
309+
310+ static bool is_safe_to_reorder (V<ast_function_call> v) {
311+ for (const LocalVarData& param : v->fun_maybe ->parameters ) {
312+ if (param.declared_type ->get_width_on_stack () != 1 ) {
313+ return false ;
314+ }
315+ }
316+
317+ CheckReorderingForAsmArgOrderIsSafeVisitor visitor;
318+ for (int i = 0 ; i < v->get_num_args (); ++i) {
319+ visitor.ASTVisitorFunctionBody ::visit (v->get_arg (i)->get_expr ());
320+ }
321+ if (v->dot_obj_is_self ) {
322+ visitor.ASTVisitorFunctionBody ::visit (v->get_self_obj ());
323+ }
324+ return !visitor.has_side_effects ;
325+ }
326+ };
327+
281328// given `{some_expr}!`, return some_expr
282329static AnyExprV unwrap_not_null_operator (AnyExprV v) {
283330 while (auto v_notnull = v->try_as <ast_not_null_operator>()) {
@@ -469,12 +516,16 @@ static std::vector<var_idx_t> pre_compile_is_type(CodeBlob& code, TypePtr expr_t
469516}
470517
471518static std::vector<var_idx_t > gen_op_call (CodeBlob& code, TypePtr ret_type, SrcLocation loc,
472- std::vector<var_idx_t >&& args_vars, FunctionPtr fun_ref, const char * debug_desc) {
519+ std::vector<var_idx_t >&& args_vars, FunctionPtr fun_ref, const char * debug_desc,
520+ bool arg_order_already_equals_asm = false ) {
473521 std::vector<var_idx_t > rvect = code.create_tmp_var (ret_type, loc, debug_desc);
474522 Op& op = code.emplace_back (loc, Op::_Call, rvect, std::move (args_vars), fun_ref);
475523 if (!fun_ref->is_marked_as_pure ()) {
476524 op.set_impure_flag ();
477525 }
526+ if (arg_order_already_equals_asm) {
527+ op.set_arg_order_already_equals_asm_flag ();
528+ }
478529 return rvect;
479530}
480531
@@ -1234,9 +1285,31 @@ static std::vector<var_idx_t> process_function_call(V<ast_function_call> v, Code
12341285 for (int i = 0 ; i < v->get_num_args (); ++i) {
12351286 args.push_back (v->get_arg (i)->get_expr ());
12361287 }
1288+
12371289 // the purpose of tensor_tt ("tensor target type") is to transition `null` to `(int, int)?` and so on
12381290 // the purpose of calling `pre_compile_tensor_inner` is to have 0-th IR vars to handle return self
12391291 std::vector<TypePtr> params_types = fun_ref->inferred_full_type ->try_as <TypeDataFunCallable>()->params_types ;
1292+
1293+ // if fun_ref has asm arg_order, maybe it's safe to swap arguments here (to put them onto a stack in the right way);
1294+ // (if it's not safe, arguments are evaluated left-to-right, involving stack transformations later)
1295+ bool arg_order_already_equals_asm = false ;
1296+ int asm_self_idx = 0 ;
1297+ if (!fun_ref->arg_order .empty () && CheckReorderingForAsmArgOrderIsSafeVisitor::is_safe_to_reorder (v)) {
1298+ std::vector<AnyExprV> new_args (args.size ());
1299+ std::vector<TypePtr> new_params_types (params_types.size ());
1300+ for (int i = 0 ; i < static_cast <int >(fun_ref->arg_order .size ()); ++i) {
1301+ int real_i = fun_ref->arg_order [i];
1302+ new_args[i] = args[real_i];
1303+ new_params_types[i] = params_types[real_i];
1304+ if (real_i == 0 ) {
1305+ asm_self_idx = i;
1306+ }
1307+ }
1308+ args = std::move (new_args);
1309+ params_types = std::move (new_params_types);
1310+ arg_order_already_equals_asm = true ;
1311+ }
1312+
12401313 const TypeDataTensor* tensor_tt = TypeDataTensor::create (std::move (params_types))->try_as <TypeDataTensor>();
12411314 std::vector<std::vector<var_idx_t >> vars_per_arg = pre_compile_tensor_inner (code, args, tensor_tt, nullptr );
12421315
@@ -1263,20 +1336,21 @@ static std::vector<var_idx_t> process_function_call(V<ast_function_call> v, Code
12631336 for (const std::vector<var_idx_t >& list : vars_per_arg) {
12641337 args_vars.insert (args_vars.end (), list.cbegin (), list.cend ());
12651338 }
1266- std::vector<var_idx_t > rvect_apply = gen_op_call (code, op_call_type, v->loc , std::move (args_vars), fun_ref, " (fun-call)" );
1339+ std::vector<var_idx_t > rvect_apply = gen_op_call (code, op_call_type, v->loc , std::move (args_vars), fun_ref, " (fun-call)" , arg_order_already_equals_asm );
12671340
12681341 if (fun_ref->has_mutate_params ()) {
12691342 LValContext local_lval;
12701343 std::vector<var_idx_t > left;
12711344 for (int i = 0 ; i < delta_self + v->get_num_args (); ++i) {
1345+ int real_i = arg_order_already_equals_asm ? i == 0 && delta_self ? asm_self_idx : fun_ref->arg_order [i - delta_self] : i;
12721346 if (fun_ref->parameters [i].is_mutate_parameter ()) {
1273- AnyExprV arg_i = obj_leftmost && i == 0 ? obj_leftmost : args[i ];
1347+ AnyExprV arg_i = obj_leftmost && i == 0 ? obj_leftmost : args[real_i ];
12741348 tolk_assert (arg_i->is_lvalue || i == 0 );
12751349 if (arg_i->is_lvalue ) {
12761350 std::vector<var_idx_t > ith_var_idx = pre_compile_expr (arg_i, code, nullptr , &local_lval);
12771351 left.insert (left.end (), ith_var_idx.begin (), ith_var_idx.end ());
12781352 } else {
1279- left.insert (left.end (), vars_per_arg[0 ].begin (), vars_per_arg[0 ].end ());
1353+ left.insert (left.end (), vars_per_arg[asm_self_idx ].begin (), vars_per_arg[asm_self_idx ].end ());
12801354 }
12811355 }
12821356 }
@@ -1292,7 +1366,7 @@ static std::vector<var_idx_t> process_function_call(V<ast_function_call> v, Code
12921366 if (obj_leftmost->is_lvalue ) { // to handle if obj is global var, potentially re-assigned inside a chain
12931367 rvect_apply = pre_compile_expr (obj_leftmost, code, nullptr );
12941368 } else { // temporary object, not lvalue, pre_compile_expr
1295- rvect_apply = vars_per_arg[0 ];
1369+ rvect_apply = vars_per_arg[asm_self_idx ];
12961370 }
12971371 }
12981372
0 commit comments