about summary refs log tree commit diff
diff options
context:
space:
mode:
authorDavid Wood <david.wood2@arm.com>2024-12-10 12:06:57 +0000
committerDavid Wood <david.wood2@arm.com>2025-01-10 18:37:55 +0000
commite4bae91be11c81b8c68a7bb521dfa17ccef3fc24 (patch)
treec8d21832bfe4e92822268041e6bacd7305f728bb
parent450793923ef1941bfca291f4deebf3c902d59c2f (diff)
downloadrust-e4bae91be11c81b8c68a7bb521dfa17ccef3fc24.tar.gz
rust-e4bae91be11c81b8c68a7bb521dfa17ccef3fc24.zip
inline: re-introduce some callee body checks
-rw-r--r--compiler/rustc_mir_transform/src/inline.rs31
-rw-r--r--tests/ui/force-inlining/asm.rs68
-rw-r--r--tests/ui/force-inlining/asm.stderr34
3 files changed, 131 insertions, 2 deletions
diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs
index cdb0ed761cf..a98901a6688 100644
--- a/compiler/rustc_mir_transform/src/inline.rs
+++ b/compiler/rustc_mir_transform/src/inline.rs
@@ -195,10 +195,37 @@ impl<'tcx> Inliner<'tcx> for ForceInliner<'tcx> {
         &self,
         _: &CallSite<'tcx>,
         callee_body: &Body<'tcx>,
-        _: &CodegenFnAttrs,
+        callee_attrs: &CodegenFnAttrs,
         _: bool,
     ) -> Result<(), &'static str> {
-        if let Some(_) = callee_body.tainted_by_errors { Err("body has errors") } else { Ok(()) }
+        if callee_body.tainted_by_errors.is_some() {
+            return Err("body has errors");
+        }
+
+        let caller_attrs = self.tcx().codegen_fn_attrs(self.caller_def_id());
+        if callee_attrs.instruction_set != caller_attrs.instruction_set
+            && callee_body
+                .basic_blocks
+                .iter()
+                .any(|bb| matches!(bb.terminator().kind, TerminatorKind::InlineAsm { .. }))
+        {
+            // During the attribute checking stage we allow a callee with no
+            // instruction_set assigned to count as compatible with a function that does
+            // assign one. However, during this stage we require an exact match when any
+            // inline-asm is detected. LLVM will still possibly do an inline later on
+            // if the no-attribute function ends up with the same instruction set anyway.
+            Err("cannot move inline-asm across instruction sets")
+        } else if callee_body
+            .basic_blocks
+            .iter()
+            .any(|bb| matches!(bb.terminator().kind, TerminatorKind::TailCall { .. }))
+        {
+            // FIXME(explicit_tail_calls): figure out how exactly functions containing tail
+            // calls can be inlined (and if they even should)
+            Err("can't inline functions with tail calls")
+        } else {
+            Ok(())
+        }
     }
 
     fn inline_limit_for_block(&self) -> Option<usize> {
diff --git a/tests/ui/force-inlining/asm.rs b/tests/ui/force-inlining/asm.rs
new file mode 100644
index 00000000000..85014ffa515
--- /dev/null
+++ b/tests/ui/force-inlining/asm.rs
@@ -0,0 +1,68 @@
+//@ build-fail
+//@ compile-flags: --crate-type=lib --target thumbv4t-none-eabi
+//@ needs-llvm-components: arm
+
+// Checks that forced inlining won't mix asm with incompatible instruction sets.
+
+#![crate_type = "lib"]
+#![feature(rustc_attrs)]
+#![feature(no_core, lang_items)]
+#![no_core]
+
+#[lang = "sized"]
+pub trait Sized {}
+#[lang = "copy"]
+pub trait Copy {}
+#[lang = "freeze"]
+pub unsafe trait Freeze {}
+
+#[lang = "start"]
+fn start<T>(_main: fn() -> T, _argc: isize, _argv: *const *const u8, _sigpipe: u8) -> isize {
+    0
+}
+
+#[rustc_builtin_macro]
+#[macro_export]
+macro_rules! asm {
+    ("assembly template",
+        $(operands,)*
+        $(options($(option),*))?
+    ) => {
+        /* compiler built-in */
+    };
+}
+
+#[instruction_set(arm::a32)]
+#[rustc_force_inline]
+fn instruction_set_a32() {}
+
+#[instruction_set(arm::t32)]
+#[rustc_force_inline]
+fn instruction_set_t32() {}
+
+#[rustc_force_inline]
+fn instruction_set_default() {}
+
+#[rustc_force_inline]
+fn inline_always_and_using_inline_asm() {
+    unsafe { asm!("/* do nothing */") };
+}
+
+#[instruction_set(arm::t32)]
+pub fn t32() {
+    instruction_set_a32();
+//~^ ERROR `instruction_set_a32` could not be inlined into `t32` but is required to be inlined
+    instruction_set_t32();
+    instruction_set_default();
+    inline_always_and_using_inline_asm();
+//~^ ERROR `inline_always_and_using_inline_asm` could not be inlined into `t32` but is required to be inlined
+}
+
+pub fn default() {
+    instruction_set_a32();
+//~^ ERROR `instruction_set_a32` could not be inlined into `default` but is required to be inlined
+    instruction_set_t32();
+//~^ ERROR `instruction_set_t32` could not be inlined into `default` but is required to be inlined
+    instruction_set_default();
+    inline_always_and_using_inline_asm();
+}
diff --git a/tests/ui/force-inlining/asm.stderr b/tests/ui/force-inlining/asm.stderr
new file mode 100644
index 00000000000..ef04688965b
--- /dev/null
+++ b/tests/ui/force-inlining/asm.stderr
@@ -0,0 +1,34 @@
+error: `instruction_set_a32` could not be inlined into `t32` but is required to be inlined
+  --> $DIR/asm.rs:53:5
+   |
+LL |     instruction_set_a32();
+   |     ^^^^^^^^^^^^^^^^^^^^^ ...`instruction_set_a32` called here
+   |
+   = note: could not be inlined due to: incompatible instruction set
+
+error: `inline_always_and_using_inline_asm` could not be inlined into `t32` but is required to be inlined
+  --> $DIR/asm.rs:57:5
+   |
+LL |     inline_always_and_using_inline_asm();
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...`inline_always_and_using_inline_asm` called here
+   |
+   = note: could not be inlined due to: cannot move inline-asm across instruction sets
+
+error: `instruction_set_a32` could not be inlined into `default` but is required to be inlined
+  --> $DIR/asm.rs:62:5
+   |
+LL |     instruction_set_a32();
+   |     ^^^^^^^^^^^^^^^^^^^^^ ...`instruction_set_a32` called here
+   |
+   = note: could not be inlined due to: incompatible instruction set
+
+error: `instruction_set_t32` could not be inlined into `default` but is required to be inlined
+  --> $DIR/asm.rs:64:5
+   |
+LL |     instruction_set_t32();
+   |     ^^^^^^^^^^^^^^^^^^^^^ ...`instruction_set_t32` called here
+   |
+   = note: could not be inlined due to: incompatible instruction set
+
+error: aborting due to 4 previous errors
+