Skip to content

Conversation

@Fulgen301
Copy link
Contributor

Currently, std::process::abort aborts the process via __fastfail on Windows, which is the preferred way of terminating processes since Windows 8 as it doesn't go through exception handlers. However, core::intrinsics::abort does not, and terminates by executing an illegal instruction, which causes STATUS_ILLEGAL_INSTRUCTION to be thrown. This PR removes that discrepancy and makes core::intrinsics::abort use __fastfail on Windows.

Implementation-wise, this is currently done by having core::intrinsics::abort call a function in core::os if on Windows and on one of the supported architectures. Unfortunately, this means there is now a function that, depending on the target, isn't an intrinsic inside the intrinsics module, which isn't great. I've chosen to implement it like that anyway to gather feedback on better approaches - my initial idea was to lower it to a inline asm block within rustc_codegen_ssa, as none of the backends has an intrinsic for fastfail and we thus have to manually spell out the assembly anyway, but there didn't seem to be a good reason for manually generating an inline assembly block in MIR intrinsic handling as opposed to just writing it as an asm block in the first place, as has been done inside std.

@rustbot
Copy link
Collaborator

rustbot commented Dec 8, 2025

Some changes occurred to the intrinsics. Make sure the CTFE / Miri interpreter
gets adapted for the changes, if necessary.

cc @rust-lang/miri, @RalfJung, @oli-obk, @lcnr

@rustbot rustbot added A-tidy Area: The tidy tool O-windows Operating system: Windows S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-bootstrap Relevant to the bootstrap subteam: Rust's build system (x.py and src/bootstrap) T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Dec 8, 2025
@rustbot
Copy link
Collaborator

rustbot commented Dec 8, 2025

r? @Mark-Simulacrum

rustbot has assigned @Mark-Simulacrum.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@RalfJung
Copy link
Member

RalfJung commented Dec 8, 2025

However, core::intrinsics::abort does not, and terminates by executing an illegal instruction,

This is what we do on all targets. It seems odd to do something special here for Windows only. If we want the intrinsic to do something OS-specific, we should make it so that all OSes benefit from that.

It is also rather unprecedented to do anything OS-specific in core. Being OS-agnostic is pretty much the definition of core.

Cc @rust-lang/libs

@ChrisDenton
Copy link
Member

Well if you ignore core::ffi. And how intrinsics are implemented for that matter. E.g. is there a practical difference between library code and an intrinsic that causes the compiler to codegen the same thing?

@rust-log-analyzer
Copy link
Collaborator

The job aarch64-gnu-llvm-20-2 failed! Check out the build log: (web) (plain enhanced) (plain)

Click to see the possible cause of the failure (guessed by this bot)
 Documenting core v0.0.0 (/checkout/library/core)
error: this URL is not a hyperlink
  --> library/core/src/os/windows/mod.rs:16:5
   |
16 | /// https://docs.microsoft.com/en-us/cpp/intrinsics/fastfail
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: bare URLs are not automatically turned into clickable links
   = note: `-D rustdoc::bare-urls` implied by `-D warnings`
   = help: to override `-D warnings` add `#[allow(rustdoc::bare_urls)]`
help: use an automatic link instead
   |
16 | /// <https://docs.microsoft.com/en-us/cpp/intrinsics/fastfail>
   |     +                                                        +

error: could not document `core`
warning: build failed, waiting for other jobs to finish...
[RUSTC-TIMING] core test:false 24.939
Command `/checkout/obj/build/aarch64-unknown-linux-gnu/stage0/bin/cargo doc --target aarch64-unknown-linux-gnu -Zbinary-dep-depinfo -j 4 -Zroot-dir=/checkout --locked --color always --release -p alloc -p compiler_builtins -p core -p panic_abort -p panic_unwind -p proc_macro -p rustc-std-workspace-core -p std -p std_detect -p sysroot -p test -p unwind --features 'backtrace panic-unwind compiler-builtins-c' --manifest-path /checkout/library/sysroot/Cargo.toml --no-deps --target-dir /checkout/obj/build/aarch64-unknown-linux-gnu/stage1-std/aarch64-unknown-linux-gnu/doc -Zskip-rustdoc-fingerprint -Zrustdoc-map [workdir=/checkout]` failed with exit code 101
Created at: src/bootstrap/src/core/build_steps/doc.rs:795:21
Executed at: src/bootstrap/src/core/build_steps/doc.rs:828:22

Command has failed. Rerun with -v to see more details.
Bootstrap failed while executing `--stage 2 test --skip tests --skip coverage-map --skip coverage-run --skip library --skip tidyselftest`
Build completed unsuccessfully in 0:30:56
  local time: Mon Dec  8 18:51:53 UTC 2025
  network time: Mon, 08 Dec 2025 18:51:53 GMT
##[error]Process completed with exit code 1.

@Fulgen301
Copy link
Contributor Author

Fulgen301 commented Dec 8, 2025

This is what we do on all targets. It seems odd to do something special here for Windows only.

__fastfail is the intrinsic for process abortion on Windows, it being implemented in core as asm! blocks as opposed to backend inline asm or LLVM intrinsic calls is a matter of convenience, cleanliness and backends not having a fastfail intrinsic - we can generate the same asm blocks inside cg_ssa, at the expense of code readability.

If we want the intrinsic to do something OS-specific, we should make it so that all OSes benefit from that.

We have an intrinsic that declares it does something OS-specific by being described as "Aborts the execution of the process.", which is an inherently OS-specific concept and not something the compiler has to or should implement. Here I'd argue that core::intrinsic::abort itself should not exist, and should be split between an abort method in core that selects the best implementation for the current target, and a new core::intrinsic::trap intrinsic that lowers to llvm.trap or the equivalents for other codegen backends, which is the current behavior of abort. That'd need a RFC however.

It is also rather unprecedented to do anything OS-specific in core.

core::os was initially added by the darwin_objc feature, and there is no std dependency on __fastfail since it is an intrinsic that invokes a kernel interrupt.

@RalfJung
Copy link
Member

RalfJung commented Dec 8, 2025 via email

@ChrisDenton
Copy link
Member

The intrinsic is currently implemented in terms of llvm's trap which simply maps to an illegal instruction (ud2 on x86).

I don't think llvm has an abort. At least I couldn't find one.

@RalfJung
Copy link
Member

RalfJung commented Dec 8, 2025 via email

@Fulgen301
Copy link
Contributor Author

Indeed.

@ChrisDenton
Copy link
Member

Yes. I would expect an abort to immediately abort the process rather than raising an exception (unless there's no reasonable alternative).

On the other hand raising an exception is a reasonable implementation of trap.

Somewhat related is #149708 but there the exception can only be caught outside of the rust program.

@RalfJung
Copy link
Member

RalfJung commented Dec 8, 2025

Okay.

But then we should use that consistently for all implementations of "abort", not just the intrinsic. We should probably rename the abort function on codegen backends to trap to make it more clear that this is different. And to ensure that the Abort MIR terminator is consistent with intrinsics::abort, we need to do one of

  • add a lang item so the compiler can invoke the __fastfail call that is in the library
  • generate that call in the codegen backend

Then I would suggest you entirely undo the changes in core/src/intrinsics from this PR. Instead we have the codegen backends do the following to implement the abort intrinsic:

  • check if the abort lang item is present; if so, invoke that
  • otherwise invoke the trap helper of the codegen backend

@nikic
Copy link
Contributor

nikic commented Dec 9, 2025

I think there may be some unfortunate choices of terminology here, but it's not super obvious to me that this is really a desirable/necessary change in terms of how the abort intrinsic is used. If we take -C panic=immediate-abort as a primary use case, trapping seems like a pretty reasonable thing to do in that context?

Also worth noting that this also sends a SIGILL signal on other OSes, so I'm not sure why Windows specifically needs divergent behavior.

@ChrisDenton
Copy link
Member

ChrisDenton commented Dec 9, 2025

core::intrinsic::abort is used throughout core the same way std::panic::abort is used in std. I don't think there's any real justification for them to be different?

The panic_abort crate already uses std::process::abort https://github.com/search?q=repo%3Arust-lang%2Frust+__rust_abort+path%3Alibrary%2F&type=code.

And personally I would expect, from a libs pov, for abort to make a best effort to abort the process immediately. The extent to which this is possible of course varies by platform.

/// this sequence of instructions will be treated as an access violation, which
/// will still terminate the process but might run some exception handlers.
///
/// The precise behavior is not guaranteed and not stable.
Copy link
Member

Choose a reason for hiding this comment

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

This entire doc comment only applies to the Windows version of this function. So doc.rust-lang.org would no longer show it.

@bjorn3
Copy link
Member

bjorn3 commented Dec 9, 2025

Also worth noting that this also sends a SIGILL signal on other OSes, so I'm not sure why Windows specifically needs divergent behavior.

SIGILL doesn't unwind the stack, while Windows exceptions do unwind the stack through SEH. As such SIGILL only runs an explicitly registered signal handler, while Windows exceptions as I understand it run all __finally and __catch SEH blocks, even when they don't explicitly intent to run on illegal instructions. And SIGILL is safe in the face of a corrupted stack when using a separate stack for the signal handler, while SEH is not safe in that case.

@nikic
Copy link
Contributor

nikic commented Dec 9, 2025

Also worth noting that this also sends a SIGILL signal on other OSes, so I'm not sure why Windows specifically needs divergent behavior.

SIGILL doesn't unwind the stack, while Windows exceptions do unwind the stack through SEH. As such SIGILL only runs an explicitly registered signal handler, while Windows exceptions as I understand it run all __finally and __catch SEH blocks, even when they don't explicitly intent to run on illegal instructions. And SIGILL is safe in the face of a corrupted stack when using a separate stack for the signal handler, while SEH is not safe in that case.

I guess in that case I'd wonder whether this difference in behavior would be a compelling reason to change what llvm.trap does on Windows?

@Fulgen301
Copy link
Contributor Author

llvm.trap is also used for e.g. ensuring that hitting unreachable code aborts, and it's quite small (on x86, it's a one byte ud2). Unconditionally replacing that with __fastfail, even if desired, would increase that size by at least five times (movzx ecx, 7; int 0x29), and would cause observable behavior differences for all languages using LLVM, including clang's __builtin_trap.

Trapping is much more generic in usage than specifically aborting the process as fast as possible, which is what we want and already do in std::process::abort.

@RalfJung
Copy link
Member

RalfJung commented Dec 9, 2025

Trapping is much more generic in usage than specifically aborting the process as fast as possible, which is what we want and already do in std::process::abort.

That's not the only thing abort usually does though -- if a debugger is attached, it also usually halts execution so one can inspect the program state and backtrace at this point.

Does __fastfail do that?

@bjorn3
Copy link
Member

bjorn3 commented Dec 9, 2025

Trapping is much more generic in usage than specifically aborting the process as fast as possible, which is what we want and already do in std::process::abort.

Most direct uses of core::intrinsics::abort() I have seen are as no_std replacement for std::process::abort() in a panic handler.

@ChrisDenton
Copy link
Member

That's not the only thing abort usually does though -- if a debugger is attached, it also usually halts execution so one can inspect the program state and backtrace at this point.

Does __fastfail do that?

Yes, see the documentation for __fastfail

If a debugger is present, it's given an opportunity to examine the state of the program before termination.

@RalfJung
Copy link
Member

RalfJung commented Dec 9, 2025

That seems to make it a reasonable call for LLVM to use it as an implementation of trap. Arguably, a trap that unwinds and executes "finish" blocks is... odd.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-tidy Area: The tidy tool O-windows Operating system: Windows S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-bootstrap Relevant to the bootstrap subteam: Rust's build system (x.py and src/bootstrap) T-libs Relevant to the library team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants