-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Moving #[rustc_box] to move_val_init intrinsic #110715
Comments
I implemented a basic #![feature(builtin_syntax)]
#![feature(new_uninit)]
type Type = [u8; 128];
pub fn foo2(f: fn() -> Type) -> Box<Type> {
let mut b = Box::<Type>::new_uninit();
unsafe {
builtin#ptr_write(f(), b.as_mut_ptr());
b.assume_init()
}
} _ZN3poc4foo217hf17d37ea238339f3E:
push r14
push rbx
push rax
mov rbx, rdi
mov edi, 128
mov esi, 1
call qword ptr [rip + __rust_alloc@GOTPCREL]
test rax, rax
je .LBB0_1
mov r14, rax
mov rdi, rax
call rbx
mov rax, r14
add rsp, 8
pop rbx
pop r14
ret
.LBB0_1:
mov edi, 128
mov esi, 1
call qword ptr [rip + _ZN5alloc5alloc18handle_alloc_error17h9f155cb3ff8eda02E@GOTPCREL]
ud2 If there's interest I can push my work (based on top of the |
@clubby789 that looks very promising! I think this gives good support for |
Although we could shift library code to do box creation, followed by pointer write and then type conversion, I still think the best thing to do is to make My patch in #87781 didn't work due to interaction with two-phase borrow, but that should be fixable. |
So if we were to implement pub fn new(x: T) -> Self {
let ptr = Box::into_raw(Box::<T>::new_uninit()).cast::<T>();
let ret = builtin#shallow_init_box(ptr);
*ret = ptr;
ret
} |
What's the semantics of |
let src = &this.thir[src];
let dst = &this.thir[dst];
let dst = unpack!(block = this.as_place(block, dst));
unpack!(block = this.expr_into_dest(this.tcx.mk_place_deref(dst), block, src)); is the MIR building for this, so probably the same thing |
Yeah that would be it, more or less. Linux is building a huge macro to support something like I think |
@nbdd0121 what do you mean by that? This is a general property, right? I can't see |
I happen to be very familiar with this ;) Asahi's macro doesn't (and won't) get into the mainline. Instead, we have a more general version of in-place/pinned initialization macros designed by y86-dev and me.
I am familiar with this removed intrinsic precisely because I was exploring it as a potential solution of the in-place initialization problem in Linux kernel, and it didn't work out eventually :(
Sorry, by "a construct" I mean the fact the box is uninitialized. I meant that we need to support box being uninitialized regardless whether |
The cases described here very much fit into function calls, or other expressions (possibly with attributes) - all the arguments are expressions, you don't need to specify something like a field name as an argument like with I wouldn't want everything that is currently served by e.g. lang items to spread to syntactic level, there's no need for that, it's all about semantics. |
Overall box syntax might make sense as |
Ohhh that's very interesting. Do you have a link to the macros or maybe any public discussions? I still don't fully understand why I've tried using
@petrochenkov thanks for explaining why you are against using The other thing to consider is the |
Does that mean we should close this issue? I'm not sure why this should be an intrinsic - |
Btw with this, I meant
I would like to keep it open to discuss the bringing back |
Okay @jyn514 I have updated the issue description with my new proposal. The next step would be maybe to get input from @RalfJung if they think that bringing back |
I didn't mean There is probably just 20 lines of code needed to support shallow initialized box. |
I personally think we should keep shallow initialization of boxes for a good reason: it (in theory), would allow us to initialize a box piece by piece, if we can gain the ability to the same thing for local variables. E.g. this can be helpful: struct Foo {
x: String,
y: String,
}
fn init_foo() -> Box<Foo> {
let mut b = /* create a shallow initialized box */;
b.x = "a".to_owned();
b.y = "b".to_owned();
b
} Of course this doesn't work today (for boxes and local variables), but I hope that it eventually would. There is no way to express something like this if we get rid of shallow init box and replace everything with |
I think one can absolutely keep around
You can, by using You'd also have to make sure optimizers don't do any reordering. At this point, even if you only want to support boxes, you'd have a big feature. And also, ideally you'd not just support boxes but also Rc, Arc, and all other types really. It's an interesting topic to discuss, and one should definitely give it a chance, but it's also quite complex and probably requires a lot of design work before one can unleash it on people. I think if |
#111010 already brought back move_val_init, I think. Is there any question left then? Having any kind of guarantee that no copy happens is a totally different question though, and would need some serious t-opsem discussion. However for |
I guess no 😆 . Given that, we can switch @clubby789 you indicated interest higher in the thread, do you want to make a PR? You can also remove Also thanks for your feedback @RalfJung on the general placement new feature. Regarding guarantee vs no guarantee, I think I agree that different groups of users have different needs. Some might only see it as optimization, and they are served really well by LLVM already, they just want something that's a little bit better. Others are in the situation that if their object lands on the stack, they get a segfault, so ideally there would be a compile error for them if there is a copy happening. I'm not sure how to unify those two needs nicely, or if that should even be a goal. I wold love to be involved in efforts around the design however. There has also recently been a thread on reddit about the issue, linking to the repo that @nbdd0121 mentioned higher up in the thread. |
FWIW |
Testing it, using both let mut bx = $crate::boxed::Box::new_uninit();
::core::intrinsics::write_via_move(bx.as_mut_ptr(), [$($x),+]);
bx.assume_init() I'm also not sure how we could use the intrinsic/ptr::write without the user's argument being evaluated inside of the |
Either too late or not at all, I don't know. There are definitely some heavily negatively affected benchmarks in the perf testsuite. The last attempt to use a function can be found here, where they tried to use Testing
That's actually an interesting question: how do you remove some expression argument's ability to use unsafe... I need to make a more thorough search but initially I can't find any construct that can undo |
One possible hack: control flow tricks. if false {
let _a = $expr;
} else {
unsafe {
write_via_move(p, $expr);
}
} (Disclaimer: I didn't try it out. Would definitely work with THIR unsafeck, but it's possible that MIR unsafeck is too smart.) |
|
@scottmcm that's a nice trick! Given the issue with the codegen of the |
As well as I understand, this will not work. As well as I understand, the following is true about type ascription:
As well as I understand creating normal intristic instead of magic |
But what if macro (such as |
I have opened this issue for discussing how to move
#[rustc_box]
(introduced by #97293) to something more simpler. My original proposal (reproduced below) was to usebuiltin #
syntax for it, but through the discussion, it was brought up that there used to be amove_val_init
intrinsic which would have been a good fit. Initial tests, both with abuiltin # ptr_write
andmove_val_init
directly showed promising results: it can deliver the same codegen as#[rustc_box]
can, which means that one could switch thevec![]
macro from using#[rustc_box]
towardsBox::new_uninit_slice
, then writing into the pointer withmove_val_init
, then callingasusme_init
on it.Right now,
#[rustc_box]
desugars to THIRExprKind::Box
, which desugars to code callingexchange_malloc
, followed byShallowInitBox
, a construct specifically added to supportbox
syntax, and then followed by something equivalent tomove_val_init
: a write into the pointer without putting the value into a local first.A move to using
move_val_init
directly would simplify the code that desugars#[rustc_box]
at the cost of the code in thevec![]
macro. To me that seems like a win because it would make the code around creating boxes less special.Original proposal:
I have opened this issue for discussing how to move
#[rustc_box]
(introduced by #97293) tobuiltin#
syntax (#110680).My original proposal was here, to introduce
builtin#ptr_write
, but I made that proposal while being unaware ofShallowInitBox
. So maybe we'd need bothbuiltin#ptr_write
andbuiltin#shallow_init_box
.The options I see:
builtin#box
instead of#[rustc_box]
, replacing it 1:1.builtin#ptr_write
andbuiltin#shallow_init_box
, first doing latter and then writing into it doing former. But what would the latter return on a type level? ABox<T>
?builtin#ptr_write
to then do in the Vec macro: firstBox::new_uninit_slice
, thenbuiltin#ptr_write
, then callassume_init
on it. This mirrors the proposal by oli-obk but the issue seems to be very poor codegen (new godbolt example based on the one linked in the ShallowInitBox MCP). The issue might just be due to the.write
call though.builtin#shallow_init_box
. I'm not sure this will work though as the magic seems to hide in the pointer writing.Maybe one could first add
builtin#ptr_write
without touching#[rustc_box]
and then check if the godbolt example still has that behaviour?cc @nbdd0121 @drmeepster @clubby789 @oli-obk
Earlier discussion: #110694 (comment)
The text was updated successfully, but these errors were encountered: