about summary refs log tree commit diff
path: root/src/tools/miri/tests
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-07-08 04:35:04 +0000
committerbors <bors@rust-lang.org>2024-07-08 04:35:04 +0000
commit9af6fee87de71729efca7dbb61c0931180895424 (patch)
tree90b357fccf2a1112c764084a90cc1900ddbbea4a /src/tools/miri/tests
parentb1de36ff34a4fe4ba820f195481a13aee74e1358 (diff)
parent14e5d5fbee637af09759f924e14f97685e5c24ea (diff)
downloadrust-9af6fee87de71729efca7dbb61c0931180895424.tar.gz
rust-9af6fee87de71729efca7dbb61c0931180895424.zip
Auto merge of #113128 - WaffleLapkin:become_trully_unuwuable, r=oli-obk,RalfJung
Support tail calls in mir via `TerminatorKind::TailCall`

This is one of the interesting bits in tail call implementation — MIR support.

This adds a new `TerminatorKind` which represents a tail call:
```rust
    TailCall {
        func: Operand<'tcx>,
        args: Vec<Operand<'tcx>>,
        fn_span: Span,
    },
```

*Structurally* this is very similar to a normal `Call` but is missing a few fields:
- `destination` — tail calls don't write to destination, instead they pass caller's destination to the callee (such that eventual `return` will write to the caller of the function that used tail call)
- `target` — similarly to `destination` tail calls pass the caller's return address to the callee, so there is nothing to do
- `unwind` — I _think_ this is applicable too, although it's a bit confusing
- `call_source` — `become` forbids operators and is not created as a lowering of something else; tail calls always come from HIR (at least for now)

It might be helpful to read the interpreter implementation to understand what `TailCall` means exactly, although I've tried documenting it too.

-----

There are a few `FIXME`-questions still left, ideally we'd be able to answer them during review ':)

-----

r? `@oli-obk`
cc `@scottmcm` `@DrMeepster` `@JakobDegen`
Diffstat (limited to 'src/tools/miri/tests')
-rw-r--r--src/tools/miri/tests/fail/tail_calls/cc-mismatch.rs10
-rw-r--r--src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr25
-rw-r--r--src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs17
-rw-r--r--src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr17
-rw-r--r--src/tools/miri/tests/pass/tail_call.rs39
5 files changed, 108 insertions, 0 deletions
diff --git a/src/tools/miri/tests/fail/tail_calls/cc-mismatch.rs b/src/tools/miri/tests/fail/tail_calls/cc-mismatch.rs
new file mode 100644
index 00000000000..5f00dbf2573
--- /dev/null
+++ b/src/tools/miri/tests/fail/tail_calls/cc-mismatch.rs
@@ -0,0 +1,10 @@
+//@error-in-other-file: Undefined Behavior: calling a function with calling convention C using calling convention Rust
+#![feature(explicit_tail_calls)]
+#![allow(incomplete_features)]
+
+fn main() {
+    let f = unsafe { std::mem::transmute::<extern "C" fn(), fn()>(f) };
+    become f();
+}
+
+extern "C" fn f() {}
diff --git a/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr b/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr
new file mode 100644
index 00000000000..b157e9f0b21
--- /dev/null
+++ b/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr
@@ -0,0 +1,25 @@
+error: Undefined Behavior: calling a function with calling convention C using calling convention Rust
+  --> RUSTLIB/core/src/ops/function.rs:LL:CC
+   |
+LL |     extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ calling a function with calling convention C using calling convention Rust
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `<fn() as std::ops::FnOnce<()>>::call_once - shim(fn())` at RUSTLIB/core/src/ops/function.rs:LL:CC
+   = note: inside `std::sys::backtrace::__rust_begin_short_backtrace::<fn(), ()>` at RUSTLIB/std/src/sys/backtrace.rs:LL:CC
+   = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC
+   = note: inside `std::ops::function::impls::<impl std::ops::FnOnce<()> for &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>::call_once` at RUSTLIB/core/src/ops/function.rs:LL:CC
+   = note: inside `std::panicking::r#try::do_call::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>` at RUSTLIB/std/src/panicking.rs:LL:CC
+   = note: inside `std::panicking::r#try::<i32, &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>` at RUSTLIB/std/src/panicking.rs:LL:CC
+   = note: inside `std::panic::catch_unwind::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>` at RUSTLIB/std/src/panic.rs:LL:CC
+   = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC
+   = note: inside `std::panicking::r#try::do_call::<{closure@std::rt::lang_start_internal::{closure#2}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC
+   = note: inside `std::panicking::r#try::<isize, {closure@std::rt::lang_start_internal::{closure#2}}>` at RUSTLIB/std/src/panicking.rs:LL:CC
+   = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#2}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC
+   = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC
+   = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs
new file mode 100644
index 00000000000..6df132d3255
--- /dev/null
+++ b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs
@@ -0,0 +1,17 @@
+#![feature(explicit_tail_calls)]
+#![allow(incomplete_features)]
+
+fn main() {
+    // FIXME(explicit_tail_calls):
+    //   the error should point to `become g(x)`,
+    //   but tail calls mess up the backtrace it seems like...
+    f(0);
+    //~^ error: Undefined Behavior: calling a function with argument of type i32 passing data of type u32
+}
+
+fn f(x: u32) {
+    let g = unsafe { std::mem::transmute::<fn(i32), fn(u32)>(g) };
+    become g(x);
+}
+
+fn g(_: i32) {}
diff --git a/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr
new file mode 100644
index 00000000000..8823ab9b970
--- /dev/null
+++ b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr
@@ -0,0 +1,17 @@
+error: Undefined Behavior: calling a function with argument of type i32 passing data of type u32
+  --> $DIR/signature-mismatch-arg.rs:LL:CC
+   |
+LL |     f(0);
+   |     ^^^^ calling a function with argument of type i32 passing data of type u32
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = help: this means these two types are not *guaranteed* to be ABI-compatible across all targets
+   = help: if you think this code should be accepted anyway, please report an issue with Miri
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/signature-mismatch-arg.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/pass/tail_call.rs b/src/tools/miri/tests/pass/tail_call.rs
new file mode 100644
index 00000000000..f6200706398
--- /dev/null
+++ b/src/tools/miri/tests/pass/tail_call.rs
@@ -0,0 +1,39 @@
+#![allow(incomplete_features)]
+#![feature(explicit_tail_calls)]
+
+fn main() {
+    assert_eq!(factorial(10), 3_628_800);
+    assert_eq!(mutually_recursive_identity(1000), 1000);
+}
+
+fn factorial(n: u32) -> u32 {
+    fn factorial_acc(n: u32, acc: u32) -> u32 {
+        match n {
+            0 => acc,
+            _ => become factorial_acc(n - 1, acc * n),
+        }
+    }
+
+    factorial_acc(n, 1)
+}
+
+// this is of course very silly, but we need to demonstrate mutual recursion somehow so...
+fn mutually_recursive_identity(x: u32) -> u32 {
+    fn switch(src: u32, tgt: u32) -> u32 {
+        match src {
+            0 => tgt,
+            _ if src % 7 == 0 => become advance_with_extra_steps(src, tgt),
+            _ => become advance(src, tgt),
+        }
+    }
+
+    fn advance(src: u32, tgt: u32) -> u32 {
+        become switch(src - 1, tgt + 1)
+    }
+
+    fn advance_with_extra_steps(src: u32, tgt: u32) -> u32 {
+        become advance(src, tgt)
+    }
+
+    switch(x, 0)
+}