From: Utkarsh Bahuguna <[email protected]>
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]>
---
This change was merged into the gccrs repository and is posted here for
upstream visibility and potential drive-by review, as requested by GCC
release managers.
Each commit email contains a link to its details on github from where you can
find the Pull-Request and associated discussions.
Commit on github:
https://github.com/Rust-GCC/gccrs/commit/2e8c317e34dc5531ca3e7927e2abfdbce497c646
The commit has been mentioned in the following issue(s):
- Rust-GCC/gccrs#4956: https://github.com/Rust-GCC/gccrs/issues/4956
The commit has been mentioned in the following pull-request(s):
- https://github.com/Rust-GCC/gccrs/pull/4958
gcc/rust/rust-gcc.cc | 44 +++++++++++++
.../execute/torture/eval-order-call-args.rs | 66 +++++++++++++++++++
2 files changed, 110 insertions(+)
create mode 100644 gcc/testsuite/rust/execute/torture/eval-order-call-args.rs
diff --git a/gcc/rust/rust-gcc.cc b/gcc/rust/rust-gcc.cc
index b3295a54f..bb9aee2c5 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 000000000..506b69733
--- /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
+}
base-commit: 7cadcacdd2821e2357b0866a4c1720baeba1d9f3
--
2.55.0