Skip to content

perf(codegen): Restore noundef On PassMode::Cast Args In Rust ABI#152864

Merged
rust-bors[bot] merged 1 commit intorust-lang:mainfrom
TKanX:bugfix/123183-array-cast-abi-noundef
Mar 1, 2026
Merged

perf(codegen): Restore noundef On PassMode::Cast Args In Rust ABI#152864
rust-bors[bot] merged 1 commit intorust-lang:mainfrom
TKanX:bugfix/123183-array-cast-abi-noundef

Conversation

@TKanX
Copy link
Contributor

@TKanX TKanX commented Feb 19, 2026

View all comments

Summary:

Problem:

Small aggregate arguments passed via PassMode::Cast in the Rust ABI (e.g. [u32; 2] cast to i64) are missing noundef in the emitted LLVM IR, even when the type contains no uninit bytes:

#[no_mangle] pub fn f(v: [u32; 2]) -> u32 { v[0] }
; expected: define i32 @f(i64 noundef %0) ; actual: define i32 @f(i64 %0) ← noundef missing

This blocks LLVM from applying optimizations that require value-defined semantics on function arguments.

Root Cause:

adjust_for_rust_abi calls arg.cast_to(Reg::Integer), which internally creates a CastTarget with ArgAttributes::new() — always empty. Any validity attribute that was present before the cast is silently dropped.

This affects all PassMode::Cast arguments and return values in the Rust ABI: plain arrays, newtype wrappers, and any BackendRepr::Memory type small enough to fit in a register.

A prior attempt (#127210) used Ty/repr attributes to detect padding.

Solution:

After adjust_for_rust_abi, iterate all PassMode::Cast args and the return value. For each, call layout_is_noundef on the original layout; if it returns true, set NoUndef on the CastTarget's attrs.

layout_is_noundef uses only the computed layout — BackendRepr, FieldsShape, Variants, Scalar::is_uninit_valid() — and never touches Ty or repr attributes. Anything it cannot prove returns false.

Covered cases:

  • Scalar / ScalarPair (both halves initialized, fields contiguous)
  • FieldsShape::Array (element type recursively uninit-free)
  • FieldsShape::Arbitrary with Variants::Single (fields cover 0..size with no gaps, each recursively uninit-free) — handles newtype wrappers, multi-field structs, single-variant enums, repr(transparent), repr(C) wrappers

Conservatively excluded with FIXMEs:

  • Multi-variant enums (per-variant padding analysis needed)
  • Foreign-ABI casts (cast target may exceed layout size, needs a size guard)

Changes:

  • compiler/rustc_ty_utils/src/abi.rs: add restoration loop after adjust_for_rust_abi; add layout_is_noundef and fields_cover_layout.
  • tests/codegen-llvm/abi-noundef-cast.rs: new FileCheck test covering arrays, newtype wrappers (repr(Rust), repr(transparent), repr(C)), multi-field structs, single-variant enums, return values, and negative cases (MaybeUninit, struct with trailing padding).
  • tests/codegen-llvm/debuginfo-dse.rs: update one CHECK pattern — Aggregate_4xi8 (struct { i8, i8, i8, i8 }) now correctly gets noundef.

Fixes #123183.

r? @RalfJung

@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 Feb 19, 2026
@rustbot
Copy link
Collaborator

rustbot commented Feb 19, 2026

RalfJung is not on the review rotation at the moment.
They may take a while to respond.

@RalfJung
Copy link
Member

I'll take a look at parts of this but I can't do the full review.
@rustbot reroll

@rustbot rustbot assigned fee1-dead and unassigned RalfJung Feb 19, 2026
@rust-log-analyzer

This comment has been minimized.

@TKanX TKanX requested a review from bjorn3 February 20, 2026 13:00
@TKanX
Copy link
Contributor Author

TKanX commented Feb 20, 2026

@rustbot label +A-LLVM +A-codegen +A-ABI +C-optimization +T-compiler

@rustbot rustbot added A-ABI Area: Concerning the application binary interface (ABI) A-codegen Area: Code generation A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. C-optimization Category: An issue highlighting optimization opportunities or PRs implementing such labels Feb 20, 2026
@TKanX
Copy link
Contributor Author

TKanX commented Feb 21, 2026

@bjorn3 Ready for review. Thanks for the advice!

@TKanX TKanX requested a review from RalfJung February 22, 2026 23:54
@RalfJung
Copy link
Member

@bors try
@rust-timer queue

@rust-timer

This comment has been minimized.

@rust-bors

This comment has been minimized.

rust-bors bot pushed a commit that referenced this pull request Feb 23, 2026
…=<try> perf(codegen): Restore `noundef` On `PassMode::Cast` Args In Rust ABI
@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Feb 23, 2026
@rust-bors
Copy link
Contributor

rust-bors bot commented Feb 23, 2026

☀️ Try build successful (CI)
Build commit: 336510d (336510dc97e872320307f66d5356820a14af413e, parent: d3e8bd94cc042c65e30a6c60146a8a00531d1292)

@rust-timer

This comment has been minimized.

@rust-timer
Copy link
Collaborator

Finished benchmarking commit (336510d): comparison URL.

Overall result: no relevant changes - no action needed

Benchmarking this pull request means it may be perf-sensitive – we'll automatically label it not fit for rolling up. You can override this, but we strongly advise not to, due to possible changes in compiler perf.

@bors rollup=never
@rustbot label: -S-waiting-on-perf -perf-regression

Instruction count

This benchmark run did not return any relevant results for this metric.

Max RSS (memory usage)

Results (primary 2.0%, secondary 3.1%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
2.0% [1.8%, 2.2%] 2
Regressions ❌
(secondary)
3.1% [2.1%, 4.5%] 4
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) 2.0% [1.8%, 2.2%] 2

Cycles

Results (primary 1.6%, secondary -2.2%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
1.6% [1.6%, 1.6%] 1
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-2.2% [-2.2%, -2.2%] 1
All ❌✅ (primary) 1.6% [1.6%, 1.6%] 1

Binary size

This benchmark run did not return any relevant results for this metric.

Bootstrap: 480.206s -> 479.468s (-0.15%)
Artifact size: 397.91 MiB -> 397.92 MiB (0.00%)

@rustbot rustbot removed the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Feb 23, 2026
@TKanX TKanX requested a review from RalfJung February 26, 2026 10:59
@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Feb 27, 2026
`adjust_for_rust_abi` was casting small aggregates to an integer register without propagating `noundef`, causing a performance regression (rust-lang#123183) — LLVM could no longer assume the bits were fully defined. Add `layout_is_noundef` (conservative) + `fields_are_noundef` helper, then use `cast_to_with_attrs` to forward `NoUndef` when proven: Scalar → `!is_uninit_valid()` ScalarPair → both scalars valid + `s1.size + s2.size == layout.size` (size equality rejects layouts with inter-scalar padding) Array → recurse into element; empty arrays unconditionally noundef Arbitrary → `Variants::Single` required; walk fields in offset order — any gap, non-noundef field, or trailing pad returns false Union / Primitive / Simd → false (conservative) Bless `pass-indirectly-attr.stderr` and `debuginfo-dse.rs` for the new attribute on Cast args. Add `tests/codegen-llvm/abi-noundef-cast.rs` covering positive Cast cases (arrays, plain structs, single-variant enum) and negative Cast cases (MaybeUninit, multi-variant enum, field/pair gap, trailing padding). Fixes rust-lang#123183. Co-authored-by: Ralf Jung <post@ralfj.de>
@TKanX TKanX force-pushed the bugfix/123183-array-cast-abi-noundef branch from d104c32 to 97bd985 Compare February 27, 2026 20:17
@rustbot
Copy link
Collaborator

rustbot commented Feb 27, 2026

⚠️ Warning ⚠️

  • There are issue links (such as #123) in the commit messages of the following commits.
    Please move them to the PR description, to avoid spamming the issues with references to the commit, and so this bot can automatically canonicalize them to avoid issues with subtree.
@TKanX
Copy link
Contributor Author

TKanX commented Feb 27, 2026

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Feb 27, 2026
@RalfJung
Copy link
Member

@bors r+

@rust-bors
Copy link
Contributor

rust-bors bot commented Feb 28, 2026

📌 Commit 97bd985 has been approved by RalfJung

It is now in the queue for this repository.

@rust-bors rust-bors bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Feb 28, 2026
@rust-bors

This comment has been minimized.

rust-bors bot pushed a commit that referenced this pull request Feb 28, 2026
…=RalfJung perf(codegen): Restore `noundef` On `PassMode::Cast` Args In Rust ABI ### Summary: #### Problem: Small aggregate arguments passed via `PassMode::Cast` in the Rust ABI (e.g. `[u32; 2]` cast to `i64`) are missing `noundef` in the emitted LLVM IR, even when the type contains no uninit bytes: ```rust #[no_mangle] pub fn f(v: [u32; 2]) -> u32 { v[0] } ``` ```llvm ; expected: define i32 @f(i64 noundef %0) ; actual: define i32 @f(i64 %0) ← noundef missing ``` This blocks LLVM from applying optimizations that require value-defined semantics on function arguments. #### Root Cause: `adjust_for_rust_abi` calls `arg.cast_to(Reg::Integer)`, which internally creates a `CastTarget` with `ArgAttributes::new()` — always empty. Any validity attribute that was present before the cast is silently dropped. This affects all `PassMode::Cast` arguments and return values in the Rust ABI: plain arrays, newtype wrappers, and any `BackendRepr::Memory` type small enough to fit in a register. A prior attempt (#127210) used `Ty`/`repr` attributes to detect padding. #### Solution: After `adjust_for_rust_abi`, iterate all `PassMode::Cast` args and the return value. For each, call `layout_is_noundef` on the original layout; if it returns `true`, set `NoUndef` on the `CastTarget`'s `attrs`. `layout_is_noundef` uses only the computed layout — `BackendRepr`, `FieldsShape`, `Variants`, `Scalar::is_uninit_valid()` — and never touches `Ty` or repr attributes. **Anything it cannot prove returns `false`.** Covered cases: - `Scalar` / `ScalarPair` (both halves initialized, fields contiguous) - `FieldsShape::Array` (element type recursively uninit-free) - `FieldsShape::Arbitrary` with `Variants::Single` (fields cover `0..size` with no gaps, each recursively uninit-free) — handles newtype wrappers, multi-field structs, single-variant enums, `repr(transparent)`, `repr(C)` wrappers Conservatively excluded with FIXMEs: - Multi-variant enums (per-variant padding analysis needed) - Foreign-ABI casts (cast target may exceed layout size, needs a size guard) ### Changes: - `compiler/rustc_ty_utils/src/abi.rs`: add restoration loop after `adjust_for_rust_abi`; add `layout_is_noundef` and `fields_cover_layout`. - `tests/codegen-llvm/abi-noundef-cast.rs`: new FileCheck test covering arrays, newtype wrappers (`repr(Rust)`, `repr(transparent)`, `repr(C)`), multi-field structs, single-variant enums, return values, and negative cases (`MaybeUninit`, struct with trailing padding). - `tests/codegen-llvm/debuginfo-dse.rs`: update one CHECK pattern — `Aggregate_4xi8` (`struct { i8, i8, i8, i8 }`) now correctly gets `noundef`. Fixes #123183. r? @RalfJung
@rust-bors rust-bors bot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Feb 28, 2026
@rust-bors
Copy link
Contributor

rust-bors bot commented Feb 28, 2026

💔 Test for a3f0079 failed: CI. Failed job:

@JonathanBrouwer
Copy link
Contributor

@bors retry
Spurious...

@rust-bors rust-bors bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Feb 28, 2026
@rust-bors

This comment has been minimized.

@rust-bors rust-bors bot added merged-by-bors This PR was explicitly merged by bors. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Mar 1, 2026
@rust-bors
Copy link
Contributor

rust-bors bot commented Mar 1, 2026

☀️ Test successful - CI
Approved by: RalfJung
Duration: 3h 31m 19s
Pushing c2c6f74 to main...

@rust-bors rust-bors bot merged commit c2c6f74 into rust-lang:main Mar 1, 2026
12 checks passed
@rustbot rustbot added this to the 1.96.0 milestone Mar 1, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

What is this? This is an experimental post-merge analysis report that shows differences in test outcomes between the merged PR and its parent PR.

Comparing 8215374 (parent) -> c2c6f74 (this PR)

Test differences

Show 8 test diffs

Stage 1

  • [codegen] tests/codegen-llvm/abi-noundef-cast.rs: [missing] -> pass (J2)
  • [codegen] tests/codegen-llvm/abi-noundef-cast.rs: [missing] -> ignore (only executed when the pointer width is 64bit) (J3)

Stage 2

  • [codegen] tests/codegen-llvm/abi-noundef-cast.rs: [missing] -> pass (J0)
  • [codegen] tests/codegen-llvm/abi-noundef-cast.rs: [missing] -> ignore (only executed when the pointer width is 64bit) (J1)

Additionally, 4 doctest diffs were found. These are ignored, as they are noisy.

Job group index

Test dashboard

Run

cargo run --manifest-path src/ci/citool/Cargo.toml -- \ test-dashboard c2c6f74fd2216a63c7d180d5dda4c825d503c2fa --output-dir test-dashboard

And then open test-dashboard/index.html in your browser to see an overview of all executed tests.

Job duration changes

  1. dist-aarch64-apple: 2h 20m -> 1h 52m (-19.7%)
  2. dist-x86_64-apple: 2h 27m -> 2h 3m (-16.3%)
  3. dist-aarch64-llvm-mingw: 1h 43m -> 1h 30m (-13.0%)
  4. dist-apple-various: 1h 32m -> 1h 41m (+9.9%)
  5. x86_64-gnu-llvm-21-3: 1h 48m -> 1h 58m (+9.2%)
  6. dist-x86_64-solaris: 1h 37m -> 1h 28m (-9.0%)
  7. i686-msvc-1: 2h 46m -> 3h (+8.9%)
  8. aarch64-apple: 3h 43m -> 3h 24m (-8.8%)
  9. x86_64-gnu-miri: 1h 28m -> 1h 20m (-8.8%)
  10. x86_64-gnu-aux: 2h 8m -> 2h 19m (+8.4%)
How to interpret the job duration changes?

Job durations can vary a lot, based on the actual runner instance
that executed the job, system noise, invalidated caches, etc. The table above is provided
mostly for t-infra members, for simpler debugging of potential CI slow-downs.

@rust-timer
Copy link
Collaborator

Finished benchmarking commit (c2c6f74): comparison URL.

Overall result: ✅ improvements - no action needed

@rustbot label: -perf-regression

Instruction count

Our most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-0.5% [-0.5%, -0.5%] 1
All ❌✅ (primary) - - 0

Max RSS (memory usage)

Results (primary -3.7%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
-3.7% [-3.7%, -3.7%] 1
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) -3.7% [-3.7%, -3.7%] 1

Cycles

Results (secondary -2.5%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-2.5% [-2.5%, -2.5%] 1
All ❌✅ (primary) - - 0

Binary size

This benchmark run did not return any relevant results for this metric.

Bootstrap: 479.99s -> 479.752s (-0.05%)
Artifact size: 397.51 MiB -> 397.58 MiB (0.02%)

@TKanX TKanX deleted the bugfix/123183-array-cast-abi-noundef branch March 1, 2026 06:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-ABI Area: Concerning the application binary interface (ABI) A-codegen Area: Code generation A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. C-optimization Category: An issue highlighting optimization opportunities or PRs implementing such merged-by-bors This PR was explicitly merged by bors. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

8 participants