https://gcc.gnu.org/g:797146f19c4aaf2b7cba5126f0ae07da6c17a3ff
commit r17-5436-g797146f19c4aaf2b7cba5126f0ae07da6c17a3ff Author: Utkarsh Bahuguna <[email protected]> Date: Mon Oct 5 13:00:56 2026 +0530 gccrs: Evaluate call arguments from left to right The gimplifier evaluates call arguments in the order the target pushes them, which is right to left on x86, but Rust wants left to right. Method calls and overloaded operators were affected too. When a call has side effects, save the callee and every argument but the last, in order, before the call. Fixes Rust-GCC/gccrs#4956 gcc/rust/ChangeLog: * rust-gcc.cc (save_call_operands): New function. (call_expression): Use it to evaluate the callee and the arguments from left to right. gcc/testsuite/ChangeLog: * rust/execute/torture/eval-order-call-args.rs: New test. Signed-off-by: Utkarsh Bahuguna <[email protected]> Diff: --- gcc/rust/rust-gcc.cc | 44 +++++++++++++++ .../rust/execute/torture/eval-order-call-args.rs | 66 ++++++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/gcc/rust/rust-gcc.cc b/gcc/rust/rust-gcc.cc index b3295a54f36e..bb9aee2c5581 100644 --- a/gcc/rust/rust-gcc.cc +++ b/gcc/rust/rust-gcc.cc @@ -1590,6 +1590,44 @@ slice_index_expression (tree slice_tree, tree index_tree, location_t location) return build1_loc (location, INDIRECT_REF, element_type, data_offset_expr); } +// Rust evaluates the callee and then the arguments of a call from left to +// right, but gimplify_call_expr evaluates the arguments in the order the +// target pushes them, which is right to left on x86. When anything in the +// call has side effects, wrap the callee and every argument but the last in +// a SAVE_EXPR, and return those in order so that the caller can evaluate +// them before the call. The call then only evaluates its last argument. + +static std::vector<tree> +save_call_operands (tree &fn, tree *args, size_t nargs) +{ + std::vector<tree> saved; + + bool has_side_effects = TREE_SIDE_EFFECTS (fn); + for (size_t i = 0; i < nargs && !has_side_effects; ++i) + has_side_effects = TREE_SIDE_EFFECTS (args[i]); + + if (!has_side_effects || nargs == 0) + return saved; + + if (TREE_SIDE_EFFECTS (fn)) + { + fn = save_expr (fn); + saved.push_back (fn); + } + + for (size_t i = 0; i + 1 < nargs; ++i) + { + if (VOID_TYPE_P (TREE_TYPE (args[i])) || really_constant_p (args[i])) + continue; + + args[i] = save_expr (args[i]); + if (TREE_CODE (args[i]) == SAVE_EXPR) + saved.push_back (args[i]); + } + + return saved; +} + // Create an expression for a call to FN_EXPR with FN_ARGS. tree call_expression (tree fn, const std::vector<tree> &fn_args, tree chain_expr, @@ -1642,6 +1680,8 @@ call_expression (tree fn, const std::vector<tree> &fn_args, tree chain_expr, } } + std::vector<tree> saved = save_call_operands (fn, args, nargs); + tree ret = build_call_array_loc (location, excess_type != NULL_TREE ? excess_type : rettype, @@ -1665,6 +1705,10 @@ call_expression (tree fn, const std::vector<tree> &fn_args, tree chain_expr, ret = build1_loc (location, NOP_EXPR, rettype, ret); } + // evaluate the saved operands in order before the call + for (auto it = saved.rbegin (); it != saved.rend (); ++it) + ret = build2_loc (location, COMPOUND_EXPR, TREE_TYPE (ret), *it, ret); + delete[] args; return ret; } diff --git a/gcc/testsuite/rust/execute/torture/eval-order-call-args.rs b/gcc/testsuite/rust/execute/torture/eval-order-call-args.rs new file mode 100644 index 000000000000..506b69733cd7 --- /dev/null +++ b/gcc/testsuite/rust/execute/torture/eval-order-call-args.rs @@ -0,0 +1,66 @@ +// Operands of a call, a method call and an overloaded operator are +// evaluated from left to right. +#![feature(no_core, lang_items)] +#![no_core] + +#[lang = "sized"] +trait Sized {} + +#[lang = "add"] +trait Add<Rhs = Self> { + type Output; + fn add(self, rhs: Rhs) -> Self::Output; +} + +static mut N: i32 = 0; + +fn tick() -> i32 { + unsafe { + N += 1; + N + } +} + +fn three(a: i32, b: i32, c: i32) -> i32 { + a * 100 + b * 10 + c +} + +struct S; + +impl S { + fn two(&self, a: i32, b: i32) -> i32 { + a * 10 + b + } +} + +struct W(i32); + +impl Add for W { + type Output = i32; + fn add(self, rhs: W) -> i32 { + self.0 * 10 + rhs.0 + } +} + +fn w() -> W { + W(tick()) +} + +fn main() -> i32 { + if three(tick(), tick(), tick()) != 123 { + return 1; + } + + unsafe { N = 0 }; + let s = S; + if s.two(tick(), tick()) != 12 { + return 2; + } + + unsafe { N = 0 }; + if w() + w() != 12 { + return 3; + } + + 0 +}
