Skip to content

Guard HIR lowered contracts with contract_checks #144438

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 1 commit into
base: master
Choose a base branch
from

Conversation

dawidl022
Copy link

@dawidl022 dawidl022 commented Jul 25, 2025

Refactor contract HIR lowering to ensure no contract code is executed when contract-checks are disabled.

The call to contract_checks is moved to inside the lowered fn body, and contract closures are built conditionally, ensuring no side-effects present in contracts occur when those are disabled. This partially addresses #139548, i.e. the bad behavior no longer happens with contract checks disabled (-Zcontract-checks=no).

The change is made in preparation for adding contract variable declarations - variables declared before the requires assertion, and accessible from both requires and ensures, but not in the function body (PR #144444). As those declarations may also have side-effects, it's good to guard them with contract_checks - the new lowering approach allows for this to be done easily.

Contracts tracking issue: #128044

Known limiatations:

  • It is still possible to early return from the function from within a contract, e.g.

    #[ensures({if x > 0 { return 0 }; |_| true})]
    fn foo(x: u32) -> i32 {
        42
    }

    When foo is called with an argument greater than 0, instead of 42, 0 will be returned.

    As this is not a regression, it is not addressed in this PR. However, it may be worth revisiting later down the line, as users may expect a form of early return from contract specifications, and so returning from the entire function could cause confusion.

  • Contracts are still not optimised out when disabled. Currently, even when contracts are disabled, the code generated causes existing optimisations to fail, meaning even disabled contracts could impact runtime performance. This issue is blocking Add contracts for all functions in Alignment #136578, and has not been addressed in this PR, i.e. the mir-opt and codegen tests that fail in Add contracts for all functions in Alignment #136578 still fail with these new HIR lowering changes.

Refactor contract HIR lowering to ensure no contract code is
executed when contract-checks are disabled.

The call to contract_checks is moved to inside the lowered fn
body, and contract closures are built conditionally, ensuring
no side-effects present in contracts occur when those are disabled.
@rustbot
Copy link
Collaborator

rustbot commented Jul 25, 2025

r? @oli-obk

rustbot has assigned @oli-obk.
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

@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. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Jul 25, 2025
@rustbot
Copy link
Collaborator

rustbot commented Jul 25, 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

@celinval
Copy link
Contributor

celinval commented Jul 25, 2025

What if we remove the contract_checks intrinsic for now? Instead, we only lower the contracts if the user passes -Zcontract-checks=yes. I think this will be much simpler and it won't add extra optimization overhead it will likely fix the issue from #136578.

The main downside is that there will be no way to enable the std contract check without rebuilding it. But I think this is something that we can worry about later. Most analysis tools, including MIRI, already rebuilds the std library, so they should be able to enable contracts.

@dawidl022
Copy link
Author

What if we remove the contract_checks intrinsic for now? Instead, we only lower the contracts if the user passes -Zcontract-checks=yes. I think this will be much simpler and it won't add extra optimization overhead it will likely fix the issue from #136578.

I think this would indeed fix the issue. I wanted to try it out, but wasn't sure how to access compiler flags (e.g. -Zcontract-checks=yes) from the lowering code (or the parser for that matter). Any advice on how to achieve this?

@celinval
Copy link
Contributor

You should be able to access them through self.tcx.sess.opts.unstable_opts.

Comment on lines +2575 to 2576
#[lang = "contract_checks"]
#[rustc_intrinsic]
Copy link
Member

Choose a reason for hiding this comment

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

Making the same thing both a lang item and an intrinsic doesn't make a lot of sense. Why do you propose to do this?

Copy link
Author

Choose a reason for hiding this comment

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

Making the same thing both a lang item and an intrinsic doesn't make a lot of sense.

Could you please elaborate why this doesn't make sense, or point me to sources where I can read up on this? I don't really understand the relationship between the two.

Why do you propose to do this?

I added the lang item annotation so that I can generate HIR code referring to contract_checks, and did not think there was anything wrong with that, given that both contract_check_requires and contract_check_ensures are already marked both as lang items and intrinsics.

Copy link
Member

Choose a reason for hiding this comment

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

Hm, I didn't realize we already have things hat are both.

The reason I said that it doesn't make sense is that intrinsics are one way to make a function magic, and lang items are another. Having a function be magic in 2 different ways is... a bit too much magic?^^

Lang items are strictly more powerful than intrinsics, so I would have expected this to be just a lang item then, and not also an intrinsic. But if this works fine then I guess we can keep it for now. It can always be changed later.

@dawidl022
Copy link
Author

What if we remove the contract_checks intrinsic for now? Instead, we only lower the contracts if the user passes -Zcontract-checks=yes. I think this will be much simpler and it won't add extra optimization overhead it will likely fix the issue from #136578.

I'm happy to implement it that way, and if there is consensus, close this this PR in favour of that solution. I imagine that even with -Zcontract-checks=no, we'd still want to typecheck the contracts (at least in the debug build) to ensure they are well-formed. I'm wondering what would be the best way to achieve this, given typechecking occurs after HIR lowering.

Maybe the "typechecking when disabled issue" is not a priority right now, given that we're still figuring out what exactly the contract language should be (i.e. what extra rules contracts have to obey w.r.t. regular rust code). If that's the case, I'm happy to proceed with whatever makes most sense for now.

Right now, when naively guarding the call to lower_contract with self.tcx.sess.opts.unstable_opts I'm getting a regression fixed by #136837, so I need to figure out what exactly is going on there with associated items.

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. 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.

5 participants