Skip to content

x86: Correctly pass large integers and structs in registers with -Zregparm (v2) - #161027

Open
sulix wants to merge 1 commit into
rust-lang:mainfrom
sulix:regparm-struct
Open

x86: Correctly pass large integers and structs in registers with -Zregparm (v2)#161027
sulix wants to merge 1 commit into
rust-lang:mainfrom
sulix:regparm-struct

Conversation

@sulix

@sulix sulix commented Aug 13, 2026

Copy link
Copy Markdown

[This is a new version of #147628.]

Fixes#145694. I think this should be the last compiler fix needed to unblock 32-bit x86 support for Rust-for-linux (Rust-for-Linux/linux#78)

Tracking issue for -Zregparm: #131749

The -Zregparm option for 32-bit x86 modifies the calling convention to pass a number of arguments in registers instead of on the stack. (This is primarily used by the Linux kernel.)

Currently, rustc will only pass integers of size <= 32 bits in registers, but gcc (and clang) will pass larger integers (e.g., 64-bit integers) across two registers if possible. This is not the case with fastcall/vectorcall.

Equally, struct arguments will be split into registers and passed where possible.

Supporting this in rustc required moving the 'inreg' determination into compute_abi_info, as this is the stage where we determine if structs are passed directly or indirectly. This involves updating the x86_win32 ABI implementation as well, as it previously shared the inreg handling, but has a different compute_abi_info implementation.

Also add some assembly-llvm and codegen-llvm tests. Note that the LLVM IR generated doesn't exactly match clang, as rustc treats struct arguments as one argument in three registers, whereas clang seems to decompose it into three registers. It seems to work regardless...

CC: @tgross35

…gparm

The -Zregparm option for 32-bit x86 modifies the calling convention to pass
a number of arguments in registers instead of on the stack. (This is primarily
used by the Linux kernel.)

Currently, rustc will only pass integers of size <= 32 bits in registers, but
gcc (and clang) will pass larger integers (e.g., 64-bit integers) across two
registers if possible. This is not the case with fastcall/vectorcall.

Equally, struct arguments will be split into registers and passed where
possible.

Supporting this in rustc required moving the 'inreg' determination into
compute_abi_info, as this is the stage where we determine if structs are
passed directly or indirectly. This involves updating the x86_win32 ABI
implementation as well, as it previously shared the inreg handling, but has
a different compute_abi_info implementation.

Also add some assembly-llvm and codegen-llvm tests. Note that the LLVM
IR generated doesn't exactly match clang, as rustc treats struct arguments
as one argument in three registers, whereas clang seems to decompose it
into three registers. It seems to work regardless...
@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Aug 13, 2026
@rustbot

rustbot commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the pull request, and welcome! The Rust Project is excited to review your changes, and you should hear from @mu001999 (or someone else) some time within the next two weeks.

Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (S-waiting-on-review and S-waiting-on-author) stays updated, invoking these commands when appropriate:

  • @rustbot author: the review is finished, PR author should check the comments and take action accordingly
  • @rustbot review: the author is ready for a review, this PR will be queued again in the reviewer's queue
Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: compiler
  • compiler expanded to 75 candidates
  • Random selection from 18 candidates

@mu001999

Copy link
Copy Markdown
Member

@rustbot reroll

@rustbot rustbot assigned oli-obk and unassigned mu001999 Aug 13, 2026
@tgross35

Copy link
Copy Markdown
Contributor

I believe @folkertdev and @beetrees have been working on this code recently

@folkertdev

Copy link
Copy Markdown
Contributor

I haven't looked into the details, what are you basing the implementation on?


I think I've found two issues with the x86 (so, not windows) implementation.

My interpretation is that we have 3 32-bit registers available. Here we use up 2 registers for the first i64, then the second i64 does not fit in the remaining 32 bits, so the value is passed via the stack. Then a further i32 argument is also passed via the stack by clang, but this PR tries to place it in the remaining 32-bit register.

https://godbolt.org/z/z7zeGG3P1

define dso_local noundef i32 @callee(i64 inreg noundef %a, i64 noundef %b, i32 noundef returned %tail) local_unnamed_addr {
entry:
  ret i32 %tail
}

versus this PR (with RUSTFLAGS="-Zregparm=3 -Cunsafe-allow-abi-mismatch=regparm" cargo +stage1 asm --lib --llvm --everything --target=i686-unknown-linux-gnu):

define noundef i32 @callee(i64 inreg noundef %_a, i64 noundef %_b, i32 inreg noundef returned %tail) unnamed_addr #0 !guid !4 {
start:
  ret i32 %tail
}

The inreg should not be applied to the final i32 argument.


Another one, less relevant for linux but this flag should work in general: structs containing a single float are coerced to integer registers: clang does not do that.

https://godbolt.org/z/56Pne7dEE

define dso_local noundef i32 @callee_f1(float %s.0, i32 inreg noundef returned %tail) local_unnamed_addr {
entry:
  ret i32 %tail
}

define dso_local noundef i32 @callee_d1(double %s.0, i32 inreg noundef returned %tail) local_unnamed_addr {
entry:
  ret i32 %tail
}

define dso_local noundef i32 @callee_f2(i32 inreg %s.coerce0, i32 inreg %s.coerce1, i32 inreg noundef returned %tail) local_unnamed_addr {
entry:
  ret i32 %tail
}

versus this PR

; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) uwtable
define noundef i32 @callee_d1([2 x i32] inreg %0, i32 inreg noundef returned %tail) unnamed_addr #0 !guid !4 {
start:
  ret i32 %tail
}

; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) uwtable
define noundef i32 @callee_f1(i32 inreg %0, i32 inreg noundef returned %tail) unnamed_addr #0 !guid !5 {
start:
  ret i32 %tail
}

; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) uwtable
define noundef i32 @callee_f2([2 x i32] inreg %0, i32 inreg noundef returned %tail) unnamed_addr #0 !guid !6 {
start:
  ret i32 %tail
}

@folkertdev folkertdev Aug 13, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure where this should go but we should add a c-variadic example too

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per the GCC regparm docs C-variadic functions have all arguments passed on the stack (this is missing from the current implementation).

@beetrees beetrees left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth test your implementation against Clang and GCC with abi-cafe if you can - it's quite good and finding places where ABI implementations don't match.

View changes since this review

}

if arg.layout.pass_indirectly_in_non_rustic_abis(cx) {
arg.make_indirect();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This indirect pointer also needs to have inreg set on it as appropriate.


#[derive(PartialEq)]
pub(crate) enum Flavor {
General,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
General { regparm: u32 },

Not added in the PR, but since only Flavor::General uses regparm, I think it would be better to store it directly in the enum variant.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per the GCC regparm docs C-variadic functions have all arguments passed on the stack (this is missing from the current implementation).

}

for arg in fn_abi.args.iter_mut() {
if arg.is_ignore() || !arg.layout.is_sized() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the body of this for loop is growing, I think it would make sense to split the loop body into a classify_arg, similar to other files in this module.

@folkertdev

Copy link
Copy Markdown
Contributor

It might be worth test your implementation against Clang and GCC with abi-cafe if you can - it's quite good and finding places where ABI implementations don't match.

That's how I found those two examples. I guess on linux it might work to cross-compile for i686, but I also have a fork (I do hope to get this upstreamed eventually) with a version that can cross-compile

https://github.com/folkertdev/abi-cafe#cross-compilation

That's been finding all sorts of ABI inconsistencies.

@tgross35

Copy link
Copy Markdown
Contributor

I think probably

r? @folkertdev

@rustbot rustbot assigned folkertdev and unassigned oli-obk Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants