Skip to content

[CodeGen] Add per-exit scratch register clearing - #13

Merged
frabert merged 1 commit into
enforced_secrecy_mainfrom
zeroize-scratch-regs
Sep 16, 2026
Merged

frabert merged 1 commit into
enforced_secrecy_mainfrom
zeroize-scratch-regs

Conversation

@claude

@claude claude Bot commented Aug 12, 2026

Copy link
Copy Markdown

Requested by Francesco Bertolaccini · Slack thread

Stack clearing can dirty registers that the function's zero-call-used-regs mode does not select. This PR adds preparatory infrastructure for a future stack-clearing producer: per-exit scratch bookkeeping and a shared consumer that validates declarations, merges them into the exit's filtered register set, and emits register clears.

Targets explicitly identify full-width registers their emitter can clear. The helper rejects unsupported, reserved, live, return-address, and finalized callee-saved registers, including custom call-saved registers. Invalid declarations produce errors in both release and assertions-enabled builds.

Current production scope

No stack-clearing producer is implemented here. planClearStack() never returns Emit, and no production step populates ScratchRegs. Consequently, scratch-only register planning and nonempty scratch propagation and printing are dormant. The shared consumer is used by ordinary register clearing with an empty scratch set; this PR does not yet provide production scratch clearing.

The production compiler has no scratch-injection flag or synthetic capability override. Unsupported stack requests retain their warning and are never reported as emitted. They can still compile successfully without clearing the frame; this PR does not change that diagnostic policy.

Coverage and follow-up

The 70 parameterized cases are helper unit tests using real X86, AArch64, and RISC-V backends. They directly supply candidate and scratch sets to test merging, instruction semantics, independent helper calls for different exits, and rejection of invalid declarations and custom call-saved registers.

The cases with absent or skip register attributes do not exercise PEI planning. Likewise, directly supplying filtered candidates or calling the helper for separate exits does not verify PEI filtering, scratch declaration, propagation between clearing steps, or printing. These cases establish the helper contract, not end-to-end PEI integration coverage.

A production CodeGen regression checks that an unsupported stack clear does not force register clearing or report successful stack emission, while an explicit register-clearing request still runs independently.

The first real stack-clearing producer belongs in a follow-up PR stacked on this infrastructure. That follow-up must exercise the production path end to end: scratch declaration by the producer, required register clearing with absent/skip attributes, merging after exit filtering, independent per-exit propagation, emitted clears, and scratch printing. The dormant integration should not be treated as a completed stack-clearing feature before that validation lands.

Validation

Results recorded for the current implementation:

  • Assertions-enabled llc and CodeGenTests build passed.
  • CodeGen unit suite: 391 passed, including the 70 helper cases; 16 skipped.
  • Selected CodeGen lit suites: 312 passed; four unsupported. The unrelated untracked zeroize-guaranteed-tailcall.ll test was excluded.
  • The finalized-CSR helper regression failed on all five target configurations before the fix and passed afterwards.
  • Formatting and whitespace checks passed. CMake and GN source lists are updated; GN was unavailable locally.

AI tool use

This PR contains AI-assisted code and text. The original patch used Claude Code; subsequent review fixes and the unit-test fixture used Codex.

@CLAassistant

CLAassistant commented Aug 12, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@github-actions

Copy link
Copy Markdown

Hello @claude[bot] 👋

Thank you for submitting a Pull Request (PR) to the LLVM Project. Since this is your first PR, here are a few useful links covering our main contribution policies and review practices.

  • All contributions to LLVM must follow our LLVM AI Tool Use Policy. In particular, if you used AI while working on this PR, remember to add a note to the PR description.
  • The LLVM Code-Review Policy and Practices document contains practical information about the PR process, including how patches are reviewed and accepted, and who can review a PR.
  • Our LLVM Developer Policy describes our expectations for code quality, commit summaries and contains notes on our CI system.

Please reply to this message to confirm that you have read these policies, especially the LLVM AI Tool Use Policy, and that any AI tool usage has been noted in the PR description.


Frequently asked questions

How do I add reviewers?

This PR will be automatically labeled, and the relevant teams will be notified. For some parts of the project, reviewers may also be added automatically.

You can also add reviewers manually using the Reviewers section on this page. If you cannot use that section, it is probably because you do not have write permissions for the repository. In that case, you can request a review by tagging reviewers in a comment using @ followed by their GitHub username.

What if there are no comments?

If you have not received any comments on your PR after a week, you can request a review by pinging the PR with a comment such as “Ping”. The common courtesy ping rate is once a week. Please remember that you are asking for volunteer time from other developers.

Are any special GitHub settings required to contribute to LLVM?

We only require contributors to have a public email address associated with their GitHub commits, see this section of LLVM Developer Policy for details.


If you have questions, feel free to leave a comment on this PR, or ask on LLVM Discord or LLVM Discourse.

Thank you,
The LLVM Community

@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown
Author

-pei-stack-clear-scratch-regs fakes a target capability and suppresses the "not supported by this target" diagnostic, so a protected function compiles clean and clears nothing.

planClearStack returns ClearingDisposition::Emit as soon as the option is non-empty, before it ever calls TFI.supportsZeroizeStack(MF) (llvm/lib/CodeGen/PrologEpilogInserter.cpp:1798). Verified against the shared build: llc -mtriple=x86_64-unknown-linux-gnu on a function carrying "zeroize-stack"="used" reports error: ... "zeroize-stack" is not supported by this target; adding -pei-stack-clear-scratch-regs=r11 exits 0 with no diagnostic at all, emitting only xorl %r11d, %r11d before retq, with the frame untouched.

That inverts the principle this code states for itself — "silence is indistinguishable from having zeroed the registers" (llvm/include/llvm/CodeGen/TargetFrameLowering.h:215-219). An unsupported request has to be reported, not absorbed. The flag is also global and target-independent, applying to every "zeroize-stack" function in the module on any target, and any register name is accepted and handed straight to emitZeroCallUsedRegs: -pei-stack-clear-scratch-regs=rsp on this optimized, no-assertions build emits xorl %esp, %esp before retq.

At an absolute minimum the flag must not bypass supportsZeroizeStack(). The better fix is to drop it and land this coverage in the same patch as the first real emitZeroizeStack, where the producer is the test vehicle; if the consumer has to land first, drive it from a unit test over a constructed MachineFunction, or a MIR test against a test-only TargetFrameLowering override. Related, and the reason the flag exists: it is the only thing in tree that can reach findRegisterByName (:1545), declareStackClearScratchRegs (:1562), anyRegNeededAtExit (:1578) or the forced-register-clear fixup at :1774, and both new tests exercise only configurations that cannot occur without it.

Detail, and the rest of the findings on this patch, in the write-up: https://claude.ai/code/artifact/accb9215-36b5-4022-ae0f-249ee54e99cb


Generated by Claude Code

@claude
claude Bot force-pushed the zeroize-fallback branch from 4116012 to 3d68722 Compare August 13, 2026 13:04
@claude
claude Bot force-pushed the zeroize-scratch-regs branch from 9c4ac60 to 3e2a3e5 Compare August 13, 2026 13:05
@claude
claude Bot force-pushed the zeroize-fallback branch from 3d68722 to abdc7d6 Compare August 13, 2026 14:31
@claude
claude Bot force-pushed the zeroize-scratch-regs branch from 3e2a3e5 to 3405cff Compare August 13, 2026 14:31
@claude
claude Bot force-pushed the zeroize-fallback branch from abdc7d6 to 656ebca Compare August 14, 2026 12:07
@claude
claude Bot force-pushed the zeroize-scratch-regs branch from 3405cff to 664634f Compare August 14, 2026 12:08
@claude
claude Bot force-pushed the zeroize-fallback branch from 656ebca to 8517050 Compare August 14, 2026 19:28
@claude
claude Bot force-pushed the zeroize-scratch-regs branch from 664634f to ed4f423 Compare August 14, 2026 19:28
@frabert
frabert force-pushed the zeroize-scratch-regs branch from ed4f423 to a97bff7 Compare August 18, 2026 10:18
@claude
claude Bot force-pushed the zeroize-fallback branch from 4dc1dc5 to 8c05331 Compare August 18, 2026 11:08
@claude
claude Bot force-pushed the zeroize-scratch-regs branch from a97bff7 to 623c3eb Compare August 18, 2026 11:09
@frabert
frabert force-pushed the zeroize-scratch-regs branch from 623c3eb to 1c37db2 Compare August 19, 2026 09:06
@frabert
frabert force-pushed the zeroize-scratch-regs branch from 1c37db2 to 7e82218 Compare August 19, 2026 13:41
@frabert
frabert force-pushed the zeroize-scratch-regs branch from 7e82218 to 15fe301 Compare August 21, 2026 09:31
@frabert
frabert force-pushed the zeroize-scratch-regs branch from 15fe301 to 73ccafc Compare August 24, 2026 14:17
@frabert
frabert force-pushed the zeroize-scratch-regs branch from 73ccafc to 105571b Compare August 24, 2026 14:24
@frabert
frabert force-pushed the zeroize-scratch-regs branch from 105571b to d3065b6 Compare August 25, 2026 14:04
@frabert
frabert force-pushed the zeroize-scratch-regs branch from d3065b6 to b7d5d0d Compare August 26, 2026 11:28
@frabert
frabert force-pushed the zeroize-scratch-regs branch from b7d5d0d to 02b631a Compare August 26, 2026 12:56
@frabert
frabert force-pushed the zeroize-scratch-regs branch from 02b631a to a2a7792 Compare September 2, 2026 08:02
@frabert
frabert force-pushed the zeroize-scratch-regs branch from a2a7792 to 50f73ff Compare September 10, 2026 14:05
@frabert
frabert force-pushed the zeroize-scratch-regs branch from 50f73ff to a062797 Compare September 11, 2026 13:16
@frabert
frabert changed the base branch from zeroize-fallback to zeroize-fallback-rebased September 11, 2026 13:16
@frabert
frabert marked this pull request as ready for review September 15, 2026 08:27
Comment thread llvm/lib/CodeGen/PrologEpilogInserter.cpp Outdated
@kumarak
kumarak force-pushed the zeroize-scratch-regs branch from a062797 to cabfa61 Compare September 15, 2026 13:29
@kumarak
kumarak changed the base branch from zeroize-fallback-rebased to enforced_secrecy_main September 15, 2026 13:37
kumarak added a commit that referenced this pull request Sep 15, 2026
Require targets to opt in to scratch clearing for full-width registers that their emitter actually clears. Reject unsupported, reserved, live, callee-saved, and return-address registers in release and assertions-enabled builds. This prevents X86 AH clears from corrupting AL returns and rejects MMX scratch registers that the emitter skips.

Restrict AArch64 declarations to the GPRs accepted by its clear emitter, including under preserve_nonecc and ghccc. Preserve unsupported-stack diagnostics when the scratch test hook is enabled, and remove private tracker references from source and test comments.

Validation: rebuilt assertions-enabled llc with X86, ARM, AArch64, and RISCV. The relevant CodeGen suites passed 318 tests with 4 unsupported. Added negative and positive scratch regressions, including an AArch64 calling-convention test confirmed to fail before the fix. Changed-line clang-format and git diff --check passed.
@kumarak kumarak changed the title [CodeGen] Clear the registers the stack clear leaves data in [CodeGen] Add per-exit scratch register clearing Sep 15, 2026
Track scratch registers per exit and merge them into register clearing after candidate filtering. Validate target support, register width, reserved and finalized callee-saved registers, return addresses, and live exit operands before emission.

Keep stack clearing capability-driven and remove the synthetic scratch-injection option. Exercise the shared consumer with 70 unit cases using real target backends, add an unsupported-stack regression, and register the sources in CMake and GN. Tighten clearing comments and remove private tracker references.

Validation: assertions-enabled llc and CodeGenTests built successfully; 391 unit tests and 312 selected lit tests passed. GN source lists were checked; GN was unavailable locally.

Co-authored-by: Claude <noreply@anthropic.com>
@kumarak
kumarak force-pushed the zeroize-scratch-regs branch 2 times, most recently from 17234b3 to 72a7fe8 Compare September 15, 2026 18:14
@kumarak kumarak changed the title [CodeGen] Add per-exit scratch register clearing [CodeGen] Test unsupported stack clearing and tighten comments Sep 15, 2026
@kumarak kumarak changed the title [CodeGen] Test unsupported stack clearing and tighten comments [CodeGen] Add per-exit scratch register clearing Sep 15, 2026
@kumarak
kumarak force-pushed the zeroize-scratch-regs branch from 72a7fe8 to 17234b3 Compare September 15, 2026 18:19
@frabert
frabert merged commit c25dfe7 into enforced_secrecy_main Sep 16, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants