Skip to content

Add hook for overriding compiler builtin macros #145734

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

AlexanderPortland
Copy link
Contributor

It would be useful for compiler plugins to be able to override the functionality of builtin macros, so this PR attempts to implement that. This is my first real attempt at contributing to Rust, so I’m looking to improve my skills and am very open to feedback or criticism!

This feature would specifically be very useful for us in Kani, as it would allow us to stub out the std library's panic!() macro when we’re doing codegen for verification and avoid having to verify the complicated string formatting from certain calls of that macro.

r? @oli-obk

@rustbot
Copy link
Collaborator

rustbot commented Aug 22, 2025

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

@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 22, 2025
@rustbot
Copy link
Collaborator

rustbot commented Aug 22, 2025

These commits modify the Cargo.lock file. Unintentional changes to Cargo.lock can be introduced when switching branches and rebasing PRs.

If this was unintentional then you should revert the changes before this PR is merged.
Otherwise, you can ignore this comment.

Some changes occurred to the CTFE machinery

cc @RalfJung, @oli-obk, @lcnr

@@ -40,6 +40,7 @@ pub fn provide(providers: &mut Providers) {
providers.eval_to_allocation_raw = const_eval::eval_to_allocation_raw_provider;
providers.eval_static_initializer = const_eval::eval_static_initializer_provider;
providers.hooks.const_caller_location = util::caller_location::const_caller_location_provider;
providers.hooks.after_register_builtin_macros = |_, _| {};
Copy link
Member

Choose a reason for hiding this comment

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

What does this have to do with const-eval...?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, nothing lol. I was trying to figure out which crate's provide() function a default implementation should go under, and am not very familiar with the compiler's layout. Is this the right way to do that, and if so which provide() would be the right one to add it to?

Copy link
Member

@RalfJung RalfJung Aug 22, 2025

Choose a reason for hiding this comment

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

which provide() would be the right one to add it to?

I'm not sure either, I'm just a const-eval maintainer and was confused about what showed up on "my turf" here. ;)

Something that deals with macros, probably... or rustc_middle if nothing else fits, I guess.

@bjorn3
Copy link
Member

bjorn3 commented Aug 22, 2025

This feature would specifically be very useful for us in Kani, as it would allow us to stub out the std library's panic!() macro when we’re doing codegen for verification and avoid having to verify the complicated string formatting from certain calls of that macro.

Is this for the standard library verification? If so you can just define a panic macro inside compiler-builtins to override the builtin one, right? Aside from the standard library verification, I wouldn't expect Kani to do anything with compiler-builtins at all. There shouldn't be any MIR level calls to compiler-builtins except for memcpy, memset and memcmp which you did want to shim anyway.

@RalfJung
Copy link
Member

The macro is not defined i compiler-builtins so I am not sure why you are bringing that up?

@bjorn3
Copy link
Member

bjorn3 commented Aug 22, 2025

I mean that for the standard library verification I think they could just put

macro_rules! panic { ... }

at the top of compiler-builtins to shadow the builtin panic macro.

@AlexanderPortland
Copy link
Contributor Author

Hey @bjorn3, this situation actually came up in the context of verifying user programs, not the standard library itself.

AFAIK, when compiling a user crate, Kani uses --extern to link against a slightly modified version of the standard library where we've overridden panic!() to call a kani::panic() intrinsic instead. That works fine for direct calls to panic!() in user code.

However, if the user writes something like Result::unwrap(), it goes through a dependency's internal implementation (in this case the standard library), which isn't affected by our --extern override. That ends up calling the built-in panic!() macro, which still expands into a bunch of formatting logic and, even though we stub out the final MIR call to core::panic(), we still have to verify all that intermediate string formatting logic.

(Here's the link to the original Kani issue for more context.)

@RalfJung
Copy link
Member

You need to build the dependencies against the modified standard library as well, then they will use your macro. I wonder how anything can work if you are mixing different standard libraries...?

This PR sets a precedent for ad-hoc patching of the standard library through rustc itself -- today you need this for builtin macros, but who knows what you want to replace tomorrow? I don't see anything specific to panic or builtin macros that would make them harder to replace than any other part of the standard library. (Them being builtin just means the standard library macro has no body, but in terms of name resolution it should behave like any normal macro.)

@RalfJung
Copy link
Member

(Here's the link to model-checking/kani#2835 for more context.)

Is model-checking/kani#4169 the PR implementing what you mentioned above? That is something completely different from "--extern to link against a slightly modified version of the standard library where we've overridden panic!() to call a kani::panic() intrinsic instead" though so I must be looking at the wrong thing.

@RalfJung
Copy link
Member

RalfJung commented Aug 22, 2025

Okay I have found https://github.com/model-checking/kani/blob/96eb502ffce80c3408c79d18620c84e364be9310/library/std/src/lib.rs#L203. But that's not a modified standard library. That's a tiny crate called std that you layer in between the standard library and the user crate. Of course what you do there does not affect the "real" std.

If you actually modified the standard library, you could change the macro in whatever way you please and it'd apply everywhere.

@bjorn3
Copy link
Member

bjorn3 commented Aug 22, 2025

If you are not verifying compiler-builtins, can you just ignore all functions in compiler-builtins when translating from MIR to whatever Kani produces? 99% of compiler-builtins will never be called from MIR and the remaining 1% (memcpy, memmove, memset, ...) you almost certainly want to shim anyway as it is often provided by libc rather than compiler-builtins

@RalfJung
Copy link
Member

@bjorn3 why do you keep talking about compiler-builtins? This is about builtin macros, which have nothing to do with compiler-builtins...

@bjorn3
Copy link
Member

bjorn3 commented Aug 22, 2025

Add hook for overriding compiler builtin macros

I read that as compiler-builtins macros. As in macros invoked inside the compiler-builtins crate. Never mind I guess.

@zhassan-aws
Copy link

Kani's std layer was originally introduced to allow us to override the assert macro.

Our overriden assert macro serves two purposes:

  1. It allows us to reliably instrument additional checks (what we call assertion reachability checks)
  2. It allows us to remove the formatting code from the verification path

The mechanism we use for overrides is pretty hacky though, and relies on passing --extern noprelude:std:

https://github.com/model-checking/kani/blob/8b8b89724585b08980e6a363da48aec3ad7cc690/kani-driver/src/call_single_file.rs#L20

We were hoping that rustc can provide a more reliable mechanism for performing such overrides so that we can rid of this hack.

@RalfJung
Copy link
Member

It is in general impossible to "overwrite" the macros in std without building your own std -- the std that is distributed is already fully macro-expanded.

In that sense I think actually this PR is pointless? @AlexanderPortland have you actually tried this with Kani? Even with this PR, I am fairly sure that if you call Result::unwrap you get the version with the normal macro, since it'll just load the MIR from the std in the sysroot and that one already has all the formatting.

We were hoping that rustc can provide a more reliable mechanism for performing such overrides so that we can rid of this hack.

If you want the changes to affect even std itself, I don't think there is any realistic alternative to patching the std sources and then building a custom std.

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.

6 participants