-
Notifications
You must be signed in to change notification settings - Fork 13.7k
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
base: master
Are you sure you want to change the base?
Conversation
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.
What if we remove the The main downside is that there will be no way to enable the |
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. |
You should be able to access them through |
#[lang = "contract_checks"] | ||
#[rustc_intrinsic] |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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 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 |
// into: | ||
// | ||
// { | ||
// let __postcond = if contracts_checks() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The suggestion is for this code to be either:
let __postcond = if false {
// ...
}
or
let __postcond = if true {
// ...
}
depending on the value of the -Z contract-checks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion! I tried implementing this change in a branch, and the codegen test still fails (tests/codegen/cross-crate-inlining/leaf-inlining.rs) with the alignment contracts from #136578.
I have not looked into why the test is failing, but based on the name, I suspect the optimiser is not able to inline functions it was previously able to, because now they have the if false ...
contract code, and maybe the inlining optimisation runs before the if false
elimination run, (or there is no if false
optimisation in the optimisation settings the test runs under, even though it is set to opt-level 3)?
I'm happy to investigate more deeply why this test fails, so that we understand how to fix this optimisation problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want to debug this further you should first check if the branch has been eliminated in the final optimized mir. If it has, you should check if rustc runs the inline pass after dead code elimination or just before.
I believe that llvm inline pass will still run, so this may not be a problem and we may need to adjust the tests. But it's worth checking with the library team.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@celinval @tautschnig a small update on the progress on my investigation. I noticed that removing #[rustc_intrinsic]
from contract_check_requires
and contract_check_ensures
allows the optimiser to do its job. As @RalfJung pointed out in another comment here, having both lang_item
and rustc_intrinsic
on the same function seems suspicious. What is the reason why those functions were annotated with both?
Unfortunately, just removing those annotations is yet not enough to fix the issue, as contract_checks
itself remains an intrinsic (therefore not inlinable?), and doesn't seem to work properly when the rustc_intrinsic
annotation is removed, and the body replaced with cfg!(contract_checks)
(as suggested by the FIXME comment).
When using the approach @celinval suggested here with let __postcond = if false {
, I'm struggling to get the constants to propagate and get eliminated from contract_check_ensures
.
I.e. wrapping the body of contract_check_ensures
with if false {
successfully gets optimised and the codegen inlining tests pass, but doing the same with an argument passed to contract_check_ensures
that is set to false
doesn't seem to work. I will continue to investigate to see if we can resolve this somehow. I'd also appreciate any further suggestions of things to try :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason for this to be an intrinsic is for tools / custom backends to be able to override the behavior
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would you still need the lang item?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure. Do you know the reason they were marked as lang items to begin with?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I.e. wrapping the body of contract_check_ensures with if false { successfully gets optimised and the codegen inlining tests pass, but doing the same with an argument passed to contract_check_ensures that is set to false doesn't seem to work. I will continue to investigate to see if we can resolve this somehow. I'd also appreciate any further suggestions of things to try :)
It is very hard to follow what you are doing when all one can look at is a big PR. Would be much easier to have a high-level discussion first, sketching out the lowering "on paper" until it has a shape that has good chances of being optimizeable.
It seems you want to do something that involves inlining, and intrinsics don't get inlined. So you'll either need not-an-intrinsic or a custom opt pass. But I really don't know since I don't have the time to reverse engineer what you are trying to do.
Goal: fully optimise out contracts when disabled, whilst preserving type checkingAs suggested by @RalfJung, I am starting a high-level discussion on what the HIR lowering should look like "sketching out the lowering "on paper" until it has a shape that has good chances of being optimizeable.", detailing the designed I've tried out and what didn't work in each. Hopefully, we can together come up with a lowering strategy that meets the above goal. Note: the above goal was not the original purpose of this PR, but given the discussions here, it seems it is desirable. The original purpose of the PR was to ensure no part of the contract was executed when disabled, and to prepare for #144444. New lowering approach proposed in this PRWe split the lowering of contracts into 4 distinct cases, providing the lowered pseudocode for each:
Limitations of the lowering approachSince Potential solutionsEvaluate
|
Is something setting However, I also don't understand what you think that would achieve. You already generate hard-coded
Regarding the inference issue - the type of
This one seems most promising to me. Everything the MIR opts need is right there in the body without knowing anything about what the intrinsics do, and passing |
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 bothrequires
andensures
, but not in the function body (PR #144444). As those declarations may also have side-effects, it's good to guard them withcontract_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.
When
foo
is called with an argument greater than 0, instead of42
,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. themir-opt
andcodegen
tests that fail in Add contracts for all functions inAlignment
#136578 still fail with these new HIR lowering changes.