about summary refs log tree commit diff
path: root/tests/assembly-llvm
diff options
context:
space:
mode:
authorJacob Pratt <jacob@jhpratt.dev>2025-08-27 21:51:52 -0400
committerGitHub <noreply@github.com>2025-08-27 21:51:52 -0400
commita1c543e165a5fd7bd769a07707bb770aadb07786 (patch)
treed205f91ebc29e0d0476addc0a27b7fbde70fe1d9 /tests/assembly-llvm
parentcdb45c87e2cd43495379f7e867e3cc15dcee9f93 (diff)
parent5f39612e7af612f41cf2e78c254e06ad7e4c32e0 (diff)
downloadrust-a1c543e165a5fd7bd769a07707bb770aadb07786.tar.gz
rust-a1c543e165a5fd7bd769a07707bb770aadb07786.zip
Rollup merge of #145382 - winstonallo:reg-struct-return-asm-test, r=tgross35
Add assembly test for `-Zreg-struct-return` option

r? `@tgross35`

As discussed in rust-lang/rust#145309 with `@tgross35` and `@ojeda,` I added assembly tests for the `-Zreg-struct-return` option verifying that it changes the ABI from hidden pointer to register-return on x86_32.

The test covers:
- Direct struct construction, showing register return vs hidden pointer
- External function calls returning structs, showing ABI mismatch handling

Different memory layouts affect ABI mismatch handling, but register returns use the same register allocation regardless of struct field layout (apart from the fact that they use smaller registers for smaller structs, of course).

[Here](https://godbolt.org/z/dcW6rnMG3) is a compiler explorer with 2 examples. Let me know if there is anything more I could add. Since register returns only happen for structs up to the size of 2 registers, I figured testing the pivot value (8 bytes) would be most critical.
Diffstat (limited to 'tests/assembly-llvm')
-rw-r--r--tests/assembly-llvm/reg-struct-return.rs143
1 files changed, 143 insertions, 0 deletions
diff --git a/tests/assembly-llvm/reg-struct-return.rs b/tests/assembly-llvm/reg-struct-return.rs
new file mode 100644
index 00000000000..b251d791d51
--- /dev/null
+++ b/tests/assembly-llvm/reg-struct-return.rs
@@ -0,0 +1,143 @@
+//! Tests that -Zreg-struct-return changes ABI for small struct returns
+//! from hidden-pointer convention to register-return on x86_32.
+//! This test covers:
+//! * Callee side, verifying that the structs are correctly loaded into registers when
+//!   `-Zreg-struct-return` is activated
+//! * Caller side, verifying callers do receive returned structs in registers when
+//!   `-Zreg-struct-return` is activated
+//@ add-core-stubs
+//@ assembly-output: emit-asm
+//@ compile-flags: -O --target=i686-unknown-linux-gnu -Crelocation-model=static
+//@ revisions: WITH WITHOUT
+//@[WITH] compile-flags: -Zreg-struct-return
+//@ needs-llvm-components: x86
+
+#![feature(no_core)]
+#![no_std]
+#![no_core]
+#![crate_type = "lib"]
+
+extern crate minicore;
+use minicore::*;
+
+// Verifies ABI changes for small structs, where both fields fit into one register.
+// WITH is expected to use register return, WITHOUT should use hidden pointer.
+mod Small {
+    struct SmallStruct {
+        a: i8,
+        b: i8,
+    }
+
+    unsafe extern "C" {
+        fn small() -> SmallStruct;
+    }
+
+    #[unsafe(no_mangle)]
+    pub unsafe extern "C" fn small_callee() -> SmallStruct {
+        // (42 << 8) | 42 = 10794
+
+        // WITH-LABEL: small_callee
+        // WITH: movw $10794, %ax
+        // WITH: retl
+
+        // WITHOUT-LABEL: small_callee
+        // WITHOUT: movl 4(%esp), %e{{.*}}
+        // WITHOUT: movw $10794, (%e{{.*}})
+        // WITHOUT: retl $4
+        SmallStruct { a: 42, b: 42 }
+    }
+
+    #[unsafe(no_mangle)]
+    pub unsafe extern "C" fn small_caller(dst: &mut SmallStruct) {
+        // WITH-LABEL: small_caller
+        // WITH: calll small
+        // WITH: movw %ax, (%e{{.*}})
+
+        // WITHOUT-LABEL: small_caller
+        // WITHOUT: calll small
+        // WITHOUT: movzwl {{.*}}(%esp), %e[[TMP:..]]
+        // WITHOUT: movw %[[TMP]], (%e{{..}})
+        *dst = small();
+    }
+}
+
+// Verifies ABI changes for a struct of size 8, which is the maximum size
+// for reg-struct-return.
+// WITH is expected to still use register return, WITHOUT should use hidden
+// pointer.
+mod Pivot {
+    struct PivotStruct {
+        a: i32,
+        b: i32,
+    }
+
+    unsafe extern "C" {
+        fn pivot() -> PivotStruct;
+    }
+
+    #[unsafe(no_mangle)]
+    pub unsafe extern "C" fn pivot_callee() -> PivotStruct {
+        // WITH-LABEL: pivot_callee
+        // WITH: movl $42, %e{{.*}}
+        // WITH: movl $42, %e{{.*}}
+        // WITH: retl
+
+        // WITHOUT-LABEL: pivot_callee
+        // WITHOUT: movl 4(%esp), %e{{.*}}
+        // WITHOUT-DAG: movl $42, (%e{{.*}})
+        // WITHOUT-DAG: movl $42, 4(%e{{.*}})
+        // WITHOUT: retl $4
+        PivotStruct { a: 42, b: 42 }
+    }
+
+    #[unsafe(no_mangle)]
+    pub unsafe extern "C" fn pivot_caller(dst: &mut PivotStruct) {
+        // WITH-LABEL: pivot_caller
+        // WITH: calll pivot
+        // WITH-DAG: movl %e{{.*}}, 4(%e{{.*}})
+        // WITH-DAG: movl %e{{.*}}, (%e{{.*}})
+
+        // WITHOUT-LABEL: pivot_caller
+        // WITHOUT: calll pivot
+        // WITHOUT: movsd {{.*}}(%esp), %[[TMP:xmm.]]
+        // WITHOUT: movsd %[[TMP]], (%e{{..}})
+        *dst = pivot();
+    }
+}
+
+// Verifies ABI changes for a struct of size 12, which is larger than the
+// maximum size for reg-struct-return (8 bytes).
+// Here, the hidden pointer convention should be used even when `-Zreg-struct-return` is set.
+mod Large {
+    struct LargeStruct {
+        a: i32,
+        b: i32,
+        c: i32,
+    }
+
+    unsafe extern "C" {
+        fn large() -> LargeStruct;
+    }
+
+    #[unsafe(no_mangle)]
+    pub unsafe extern "C" fn large_callee() -> LargeStruct {
+        // CHECK-LABEL: large_callee
+        // CHECK: movl 4(%esp), %e{{.*}}
+        // CHECK-DAG: movl $42, (%e{{.*}})
+        // CHECK-DAG: movl $42, 4(%e{{.*}})
+        // CHECK-DAG: movl $42, 8(%e{{.*}})
+        // CHECK: retl $4
+        LargeStruct { a: 42, b: 42, c: 42 }
+    }
+
+    #[unsafe(no_mangle)]
+    pub unsafe extern "C" fn large_caller(dst: &mut LargeStruct) {
+        // CHECK-LABEL: large_caller
+        // CHECK: calll large
+        // CHECK-DAG: movl   {{.*}}(%esp), %[[TMP1:e..]]
+        // CHECK-DAG: movl  %[[TMP1]], {{.*}}(%e{{..}})
+        // CHECK-DAG: movsd  {{.*}}(%esp), %[[TMP2:xmm.]]
+        // CHECK-DAG: movsd  %[[TMP2]], {{.*}}(%e{{..}})
+        *dst = large();
+    }
+}