diff --git a/tests/assembly-llvm/reg-struct-return.rs b/tests/assembly-llvm/reg-struct-return.rs new file mode 100644 index 0000000000000..6b6adf3b02663 --- /dev/null +++ b/tests/assembly-llvm/reg-struct-return.rs @@ -0,0 +1,150 @@ +//! 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 small_t { + a: i8, + b: i8, + } + + unsafe extern "C" { + fn small() -> small_t; + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn small_callee() -> small_t { + // (42 << 8) | 42 = 10794 + + // WITH-LABEL: small_callee + // WITH: movw $10794, %ax + // WITH: retl + + // WITHOUT-LABEL: small_callee + // WITHOUT-DAG: movl 4(%esp), %e{{.*}} + // WITHOUT-DAG: movw $10794, (%e{{.*}}) + // WITHOUT: retl $4 + small_t { a: 42, b: 42 } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn small_caller(dst: &mut small_t) { + // WITH-LABEL: small_caller + // WITH: calll small + // WITH: movw %ax, (%e{{.*}}) + + // WITHOUT-LABEL: small_caller + // WITHOUT: calll small + // WITHOUT: subl $4, %esp + *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 pivot_t { + a: i32, + b: i32, + } + + unsafe extern "C" { + fn pivot() -> pivot_t; + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pivot_callee() -> pivot_t { + // WITH-LABEL: pivot_callee + // WITH-DAG: movl $42, %e{{.*}} + // WITH-DAG: 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 + pivot_t { a: 42, b: 42 } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pivot_caller(dst: &mut pivot_t) { + // 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: subl $4, %esp + *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, both WITH and WITHOUT should use the hidden pointer convention (i.e, they +// should be identical). +mod large { + struct large_t { + a: i32, + b: i32, + c: i32, + } + + unsafe extern "C" { + fn large() -> large_t; + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn large_callee() -> large_t { + // WITH-LABEL: large_callee + // WITH: movl 4(%esp), %e{{.*}} + // WITH-DAG: movl $42, (%e{{.*}}) + // WITH-DAG: movl $42, 4(%e{{.*}}) + // WITH-DAG: movl $42, 8(%e{{.*}}) + // WITH: retl $4 + + // WITHOUT-LABEL: large_callee + // WITHOUT: movl 4(%esp), %e{{.*}} + // WITHOUT-DAG: movl $42, (%e{{.*}}) + // WITHOUT-DAG: movl $42, 4(%e{{.*}}) + // WITHOUT-DAG: movl $42, 8(%e{{.*}}) + // WITHOUT: retl $4 + large_t { a: 42, b: 42, c: 42 } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn large_caller(dst: *mut large_t) { + // WITH-LABEL: large_caller + // WITH: calll large + // WITH: subl $4, %esp + + // WITHOUT-LABEL: large_caller + // WITHOUT: calll large + // WITHOUT: subl $4, %esp + *dst = large(); + } +}