Add arg splat experiment initial tuple impl#153697
Add arg splat experiment initial tuple impl#153697teor2345 wants to merge 5 commits intorust-lang:mainfrom
Conversation
|
r? @JohnTitor rustbot has assigned @JohnTitor. Use Why was this reviewer chosen?The reviewer was selected based on:
|
This comment has been minimized.
This comment has been minimized.
|
It should be better for someone on https://rust-lang.zulipchat.com/#narrow/channel/213817-t-lang/topic/On.20overloading/with/573924937 to review this, @oli-obk could you take over? |
|
Let's wait for the ongoing discussion on Zulip to figure out whether we need to have a proc macro, an AST manipulating attribute (like |
89102bf to
c784a57
Compare
c784a57 to
2d9e563
Compare
This comment was marked as outdated.
This comment was marked as outdated.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…lmann Fix typos and markdown errors This PR fixes some typos and markdown errors I found while writing rust-lang#153697. I've split it out to reduce the size of that PR.
…lmann Fix typos and markdown errors This PR fixes some typos and markdown errors I found while writing rust-lang#153697. I've split it out to reduce the size of that PR.
…lmann Fix typos and markdown errors This PR fixes some typos and markdown errors I found while writing rust-lang#153697. I've split it out to reduce the size of that PR.
This comment has been minimized.
This comment has been minimized.
4072dbb to
886f7cf
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Fix typos and markdown errors This PR fixes some typos and markdown errors I found while writing rust-lang/rust#153697. I've split it out to reduce the size of that PR.
Changing the function header also modifies the legacy symbol name encoding
886f7cf to
8a0a4c6
Compare
|
Some changes occurred in compiler/rustc_attr_parsing cc @jdonszelmann, @JonathanBrouwer Some changes occurred to the CTFE / Miri interpreter cc @rust-lang/miri Some changes occurred in compiler/rustc_passes/src/check_attr.rs cc @jdonszelmann, @JonathanBrouwer Some changes occurred in compiler/rustc_codegen_gcc Some changes occurred in compiler/rustc_codegen_cranelift cc @bjorn3 Some changes occurred to the CTFE machinery The Miri subtree was changed cc @rust-lang/miri This PR changes rustc_public cc @oli-obk, @celinval, @ouz-a, @makai410 Some changes occurred to MIR optimizations cc @rust-lang/wg-mir-opt Some changes occurred in compiler/rustc_hir/src/attrs cc @jdonszelmann, @JonathanBrouwer HIR ty lowering was modified cc @fmease Some changes occurred in src/tools/clippy cc @rust-lang/clippy rust-analyzer is developed in its own repository. If possible, consider making this change to rust-lang/rust-analyzer instead. cc @rust-lang/rust-analyzer |
| /// Is the final argument of this function splatted? | ||
| /// FIXME(splat): combine this with c_variadic in an enum, they are mutually exclusive. | ||
| pub splatted: bool, |
There was a problem hiding this comment.
Some changes occurred in ...
Wow, that's a lot. Sorry for all the CCs.
I don't think adding a new field to FnSig is the way to implement this feature correctly. It ends up propagating to FnDef and a bunch of other places, so it's very intrusive.
Does anyone have any suggestions for how to implement it more locally?
(This PR passes a bunch of new #[splat] UI tests locally, so making changes to the implementation is unlikely to break anything.)
| }; | ||
|
|
||
| // Special handling for the closure ABI: untuple the last argument. | ||
| // FIXME(splat): un-tuple splatted arguments that were tupled in typecheck |
There was a problem hiding this comment.
Isn't this meant to be desugared already during MIR building? That seems preferable. There's only one place where we do MIR building, but there's 3-4 MIR consumers in-tree depending on how you count, and a bunch more out-of-tree.
There was a problem hiding this comment.
Now that I re-read the discussion, that makes the most sense:
#t-lang > On overloading @ 💬
I think I can de-tuple in MIR. It's a bit more complicated because it involves de-tupling both the caller and callee. But the code might end up nicer, because we'd just be splatting whatever's inside the tuple. So there's no messing around with different argument counts.
Did you want that change in this PR?
There was a problem hiding this comment.
Note that MIR is generic, so this will only work if splatting doesn't support "generic forwarding". Is that meant to be possible?
There was a problem hiding this comment.
I'm not sure, the kind of overloading we're aiming for is in this UI test:
https://github.com/rust-lang/rust/pull/153697/changes#diff-c3e596dfdd17cabcfcaca5dd69bd8b1e1f5a43bc19fd0ab26d23d010dae596b2R12
I don't know if we'd want generic forwarding for more complex overloading situations.
Would we need it to support custom C++ pointer types? Or any other interop features?
Crubit wants custom auto traits eventually, they're currently using a combination of generated impls and blanket impls.
(Maybe this is a broader lang + interop question.)
There was a problem hiding this comment.
assuming we want splatting to replace extern "rust-call", we'll need generic forwarding for things like: impl Fn<Args> for Box<F> https://doc.rust-lang.org/1.93.1/src/alloc/boxed.rs.html#2219
There was a problem hiding this comment.
Would we need it to support custom C++ pointer types? Or any other interop features?
I don't know. I haven't been following in detail.
I think I also misunderstood what splat does. That ui test looks more like it's... unsplatting? Like, the caller passes a bunch of separate arguments, but actually those become a tuple and then the callee just sees the tuple. Is the idea that on the ABI level these are a single big tuple or separate arguments or does it not matter?
There was a problem hiding this comment.
imo it's up to the backend, but an optimizing backend should untuple the arguments
There was a problem hiding this comment.
hmm @RalfJung @workingjubilee since this is a thing that solely exists for the Rust ABI, should this just be a field on ExternAbi::Rust ?
There was a problem hiding this comment.
I wonder if this can't just reuse the same logic as what RustCall already uses? It seems to have exactly the behavior we want -- in fact it's already more powerful than we need: with RustCall, caller and callee can disagree on whether splatting is happening. RustCall is really two features:
- A caller-side transformation where, for ABI purposes, the last argument is a tuple that has its fields passed as separate arguments. So we may have a function with signature
fn(i32, i64, f64, i32)and then we can call it viafn(i32, (i64, f64, i32))but also viafn(i32, i64, (f64, if32)). Both generate the same actual call. This happens post-mono, i.e. the tuple can even be generic. - A callee-side transformation where one MIR local can be designated as a "spread arg". That local must have tuple type, and it will then gather N arguments that are separate on the ABI into the fields of that local. (The interpreter allows more non-spread args after this, but I don't know if codegen also supports this.) This is independent of whether the caller used "untupling" since the ABI actually has separate arguments here. The type of the local can be generic (but it must then only ever be instantiated as a tuple type); all the actual handling happens post-mono.
|
The job Click to see the possible cause of the failure (guessed by this bot) |
There was a problem hiding this comment.
I think this PR is showing somewhat what the limitations of tuple splatting are, compared to having multiple different Fn impls for the same type.
Having a generic T: Tuple as the last parameter will always require MIR to treat the last argument as a tuple, and backends to handle the untupling, along with any ABI concerns around all that.
A non-generic last argument is almost never useful, as you want to allow for different logic depending on the size of the tuple that is being splatted.
I'm continuing this train of thought on zulip, maybe we can figure sth out
https://rust-lang.zulipchat.com/#narrow/channel/213817-t-lang/topic/On.20overloading/near/582319866
| self.inputs.last().is_some_and(|arg| matches!(arg.ty.kind, TyKind::CVarArgs)) | ||
| } | ||
| pub fn splatted(&self) -> bool { | ||
| self.inputs.last().is_some_and(|arg| arg.attrs.iter().any(|attr| attr.has_name(sym::splat))) |
There was a problem hiding this comment.
since it's a per-arg attribute, are there checks ensuring it's only used on the last arg?
| }; | ||
|
|
||
| // Special handling for the closure ABI: untuple the last argument. | ||
| // FIXME(splat): un-tuple splatted arguments that were tupled in typecheck |
There was a problem hiding this comment.
hmm @RalfJung @workingjubilee since this is a thing that solely exists for the Rust ABI, should this just be a field on ExternAbi::Rust ?
| liberated_sig.c_variadic, | ||
| liberated_sig.splatted, | ||
| hir::Safety::Safe, | ||
| rustc_abi::ExternAbi::Rust, |
There was a problem hiding this comment.
preexisting, but imo this PR shows that we should do this:
on main we could merge c_variadic, splatted, safety and ExternAbi into a separate struct, with some nice methods on it for creating default versions, making it easy to use structured update syntax in the cases where it's needed, and generally simplifying all the mk_fn_sig calls
| // If the callee has `#[splat]` on an argument | ||
| if let hir::ExprKind::Path(ref qpath) = callee_expr.kind | ||
| && let Res::Def(_def_kind, def_id) = | ||
| self.typeck_results.borrow().qpath_res(qpath, callee_expr.hir_id) | ||
| && self.tcx.fn_sig(def_id).skip_binder().skip_binder().splatted |
There was a problem hiding this comment.
you can get the fn sig directly from the adjusted_ty with the fn_sig method
There was a problem hiding this comment.
I think otherwise your logic won't work for function pointers and also not for let x = f; x()
| } | ||
|
|
||
| Some(CallStep::Splatted(callee_ty)) => { | ||
| self.confirm_splatted_call(call_expr, callee_ty, arg_exprs, expected) |
There was a problem hiding this comment.
why is this needed? it's basically the same function, what's the actual difference to confirm_builtin_call?
| type_dependent_defs: ItemLocalMap<Result<(DefKind, DefId), ErrorGuaranteed>>, | ||
|
|
||
| /// Resolved definitions for splatted function calls, including the splatted argument index. | ||
| splatted_defs: |
There was a problem hiding this comment.
this tuple deserves a struct 😅
| pub fn is_splatted_method_call(&self, expr: &hir::Expr<'_>) -> bool { | ||
| matches!( | ||
| self.splatted_defs().get(expr.hir_id), | ||
| Some(Ok((true /* is_method_call */, DefKind::AssocFn, _, _))) |
There was a problem hiding this comment.
is the "is a method call" part and the DefKind useful? the only caller to is_spatted_method_call already knows it's a method
|
Reminder, once the PR becomes ready for a review, use |
View all comments
This PR is part of the argument splatting lang experiment, and FFI overloading / C++ interop project goals:
Example code using existing unstable features:
Discussion of implementation strategy:
The PR is the initial implementation of the feature:
splatincomplete feature gate#[splat]attribute on function arguments#[splat]function parameter check at THIR levelOnce this PR merges, we can add further functionality, then test it out in interop tools.
TODOs
Out of Scope for this PR