-
Couldn't load subscription status.
- Fork 13.9k
replace box_new with lower-level intrinsics #148190
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
requires lowering write_via_move during MIR building to make it just like an assignment
|
Some changes occurred to MIR optimizations cc @rust-lang/wg-mir-opt The Miri subtree was changed cc @rust-lang/miri Some changes occurred in cc @BoxyUwU Some changes occurred in match checking cc @Nadrieril |
|
r? @SparrowLii rustbot has assigned @SparrowLii. Use |
| // rvalue, | ||
| // "Unexpected CastKind::Transmute {ty_from:?} -> {ty:?}, which is not permitted in Analysis MIR", | ||
| // ), | ||
| // } |
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.
This obviously needs to be resolved before landing... what should we do here? A transmute cast is always well-typed (it is UB if the sizes mismatch), so... can we just do nothing? I don't know what the type checker inside borrow checker is about.^^
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.
MIR typeck exists to collect all the lifetime constraints for borrowck to check. It also acts as a little bit of a double-check that typechecking on HIR actually checked everything it was supposed to, in some sense it's kind of the "soundness critical typeck". Having this do nothing seems fine to me, there's nothing to really typeck here
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.
Do we need to still visit the cast type to find any lifetimes in there, or so?
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.
FWIW this "is not permitted in Analysis MIR" part in the error I am removing here is odd as this is not documented in the MIR syntax where we usually list such restrictions, and also not checked by the MIR validator.
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.
Do we need to still visit the cast type to find any lifetimes in there, or so?
I don't think so, that should be handled by the super_rvalue call at the top of this visit_rvalue fn
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 guess we do need to check something here, right now nothing enforces that the lifetimes match for the various uses of T in init_box_via_move<T>(b: Box<MaybeUninit<T>>, x: T) -> Box<T>.
| // Make sure `StorageDead` gets emitted. | ||
| this.schedule_drop_storage_and_value(expr_span, this.local_scope(), ptr); |
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.
Here I am completely guessing... well really for all the changes in this file I am guessing, but the drop/storage scope stuff I know even less about than the rest of this.
| block, | ||
| source_info, | ||
| Place::from(ptr), | ||
| // Needs to be a `Copy` so that `b` still gets dropped if `val` panics. |
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 added a Miri test to ensure the drop does indeed happen. That's the easiest way to check for memory leaks...
| // Nothing below can panic so we do not have to worry about deallocating `ptr`. | ||
| // SAFETY: we just allocated the box to store `x`. | ||
| unsafe { core::intrinsics::write_via_move(ptr, x) }; | ||
| // SAFETY: we just initialized `b`. | ||
| unsafe { mem::transmute(ptr) } |
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 tried using init_box_via_move here instead and it makes things a bit slower in some secondary benchmarks. I have no idea why.
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.
This MIR is apparently so different from the previous one that it doesn't even show a diff (and the filename changed since I had to use CleanupPostBorrowck as built contains user types which contain super fragile DefIds). I have no idea what this test is testing and there are no filecheck annotations so... 😨
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.
Are these changes fine? Who knows! At least the filecheck annotations in the test still pass.
| StorageDead(_10); | ||
| StorageDead(_8); | ||
| StorageDead(_4); | ||
| drop(_3) -> [return: bb10, unwind: bb15]; |
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 think this is the relevant drop... but the before drop-elaboration MIR makes this quite hard to say. No idea why that's what the test checks. I think after drop elaboration this is a lot more obvious as the drops of moved-out variables are gone.
This comment has been minimized.
This comment has been minimized.
|
This PR modifies |
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.
This is fully a duplicate of something already tested in nll/user-annotations/patterns.rs.
| // FIXME: What is happening?!?? | ||
| let _: Vec<&'static String> = vec![&String::new()]; | ||
| //~^ ERROR temporary value dropped while borrowed [E0716] | ||
|
|
||
| let (_, a): (Vec<&'static String>, _) = (vec![&String::new()], 44); | ||
| //~^ ERROR temporary value dropped while borrowed [E0716] | ||
|
|
||
| let (_a, b): (Vec<&'static String>, _) = (vec![&String::new()], 44); | ||
| //~^ ERROR temporary value dropped while borrowed [E0716] | ||
| } |
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 have no idea what is happening here -- somehow code like let _: Vec<&'static String> = vec![&String::new()]; now compiles. I guess something is wrong with how I am lowering init_box_via_move?
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.
It's probably related to the transmutes there. Is there some way to insert those in a way that the lifetime constraints still get enforced?
This comment has been minimized.
This comment has been minimized.
4e526d4 to
cb7642b
Compare
This comment has been minimized.
This comment has been minimized.
86e5a72 to
020bc3a
Compare
This comment has been minimized.
This comment has been minimized.
|
I can't think of any way to actually preserve these lifetimes while using transmutes... so we'll have to add more method calls to So here's another redesign of the @bors try |
|
Awaiting bors try build completion. @rustbot label: +S-waiting-on-perf |
This comment has been minimized.
This comment has been minimized.
replace box_new with lower-level 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.
We're not getting a suggestion any more here. No idea why, but I don't think it's worth worrying about.
|
Actually never mind, we do have an rvalue scope test for fn one() -> i32 { 1 }
// Make sure the vec![...] macro doesn't introduce hidden rvalue
// scopes (such as blocks) around the element expressions.
pub fn main() {
assert_eq!(vec![&one(), &one(), &2], vec![&1, &1, &(one()+one())]);
assert_eq!(vec![&one(); 2], vec![&1, &one()]);
}So, we definitely need a special "initialize that box" intrinsic. It's entirely impossible to get the same behavior with more reasonable, smaller-scoped operations without introducing extra copies or extra blocks/scopes and while ensuring the uninitialized box gets dropped if the expression panics (or returns). The only option I can still think of is to have the intrinsic generate a method call to convert |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
|
Some changes occurred to the CTFE machinery Some changes occurred to constck cc @fee1-dead |
|
@bors try |
|
⌛ Trying commit 575ae70 with merge 651b5b1… To cancel the try build, run the command Workflow: https://github.com/rust-lang/rust/actions/runs/18876036154 |
replace box_new with lower-level intrinsics
This comment has been minimized.
This comment has been minimized.
This allows us to get rid of box_new entirely
|
Some changes occurred in compiler/rustc_codegen_cranelift cc @bjorn3 Some changes occurred in compiler/rustc_codegen_gcc |
|
The job Click to see the possible cause of the failure (guessed by this bot) |
The
box_newintrinsic is super special: during THIR construction it turns into anExprKind::Box(formerly known as theboxkeyword), which then during MIR building turns into a special instruction sequence that invokes the exchange_malloc lang item (which has a name from a different time) and a special MIR statement to represent a shallowly-initializedBox(which raises interesting opsem questions).This PR is the n-th attempt to get rid of
box_new. That's non-trivial because it usually causes a perf regression: replacingbox_newby naive unsafe code will incur extra copies in debug builds, making the resulting binaries a lot slower, and will generate a lot more MIR, making compilation measurably slower.To avoid those problems, this PR does its best to make the MIR almost exactly the same as what it was before.
box_newis used in two places,Box::newandvec!:Box::newthat is fairly easy: themove_by_valueintrinsic is basically all we need. However, to avoid the extra copy that would usually be generated for the argument of a function call, we need to special-case this intrinsic during MIR building. That's what the first commit does.vec!is a lot more tricky. As a macro, its details leak to stable code, so almost every variant I tried broke either type inference or the lifetimes of temporaries in some ui test. I ended up with a variant that involves a new intrinsic,init_box_via_move, that write a value into aBox<MaybeUninit<T>>and returns that box asBox<T>. The MIR building code for this is non-trivial, but in exchange we can get rid of somewhat similar code in the lowering forExprKind::Box. (We can also get rid ofRvalue::ShallowInitBox; I didn't include that in this PR -- I think @cjgillot has a commit for this somewhere.)I ran lots of perf experiments for this in #147907. The final version is this. Most of the regressions are in deep-vector which consists entirely of an invocation of
vec!, so any change to that macro affects this benchmark disproportionally.This is my first time even looking at MIR building code, so I am very low confidence in that part of the patch, in particular when it comes to scopes and drops and things like that.