Skip to content

Commit df75cf3

Browse files
Rollup merge of rust-lang#153697 - teor2345:fn-arg-splat-experiment, r=oli-obk
Add arg splat experiment initial tuple impl ### Description This PR is part of the argument splatting lang experiment, and FFI overloading / C++ interop project goals: - rust-lang#153629 - https://rust-lang.github.io/rust-project-goals/2026/overloading-for-ffi.html - https://rust-lang.github.io/rust-project-goals/2025h2/interop-problem-map.html Example code using existing unstable features: - https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=f42a3754a63a3d9365670e57257053d5 Discussion of implementation strategy: - [#t-lang > On overloading @ 💬](https://rust-lang.zulipchat.com/#narrow/channel/213817-t-lang/topic/On.20overloading/near/579590336) The PR is the initial implementation of the feature: - `splat` incomplete feature gate - `#[splat]` attribute on function arguments - Splatted function argument TypeInfo - `#[splat]` function parameter check at THIR level - splatted MIR lowering (as tupled arguments) - feature gate and UI tests for item type filtering, non-splattable arguments, splattable tuples, generics, and the "overloading at home" example - about half the diff (1100 lines) is tests and test output Once this PR merges, we can add further functionality, then test it out in interop tools. ### Perf Impact We expect a 0.1% regression on 5 primary and 0.2% regression on 4 secondary benchmarks in this PR, based on [this perf run](rust-lang#158251 (comment)). We tried a number of different ways to improve perf. Limiting splat to the 255th or lower argument is a simple hack that gives good perf, and is good enough for an experiment. This PR series already has significant perf wins in rust-lang#155223 - [0.3% perf improvement across 45 primary benchmarks](rust-lang#155223 (comment)). We're spending a small amount of that perf for the new feature in this PR. ### Out of Scope for this PR - Change codegen to de-tuple caller and callee - Better diagnostics - Full support for splatted function pointer arguments
2 parents 8e25b41 + 9b44c23 commit df75cf3

89 files changed

Lines changed: 2789 additions & 181 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

compiler/rustc_ast/src/ast.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3059,21 +3059,23 @@ impl FnDecl {
30593059
}
30603060

30613061
/// The marker index for "no splatted arguments".
3062+
/// Higher values are also not supported, for performance reasons.
3063+
///
30623064
/// Must have the same value as `FnSigKind::NO_SPLATTED_ARG_INDEX` and `FnDeclFlags::NO_SPLATTED_ARG_INDEX`.
3063-
pub const NO_SPLATTED_ARG_INDEX: u16 = u16::MAX;
3065+
pub const NO_SPLATTED_ARG_INDEX: u8 = u8::MAX;
30643066

30653067
/// Returns a splatted argument index, if any are present.
3066-
pub fn splatted(&self) -> Option<u16> {
3068+
pub fn splatted(&self) -> Option<u8> {
30673069
self.inputs.iter().enumerate().find_map(|(index, arg)| {
3068-
if index == Self::NO_SPLATTED_ARG_INDEX as usize {
3070+
if index >= usize::from(Self::NO_SPLATTED_ARG_INDEX) {
30693071
// AST validation has already checked the splatted argument index is valid, so just
30703072
// ignore invalid indexes here.
30713073
None
30723074
} else {
30733075
arg.attrs
30743076
.iter()
30753077
.any(|attr| attr.has_name(sym::splat))
3076-
.then_some(u16::try_from(index).unwrap())
3078+
.then_some(u8::try_from(index).unwrap())
30773079
}
30783080
})
30793081
}

compiler/rustc_ast_lowering/src/delegation.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ struct ParamInfo {
9393
pub c_variadic: bool,
9494

9595
/// The index of the splatted parameter, if any.
96-
pub splatted: Option<u16>,
96+
pub splatted: Option<u8>,
9797
}
9898

9999
const PARENT_ID: hir::ItemLocalId = hir::ItemLocalId::ZERO;
@@ -364,11 +364,10 @@ impl<'hir> LoweringContext<'_, 'hir> {
364364
fn param_info(&self, def_id: DefId) -> ParamInfo {
365365
let sig = self.tcx.fn_sig(def_id).skip_binder().skip_binder();
366366

367-
// FIXME(splat): use `sig.splatted()` once FnSig has it
368367
ParamInfo {
369368
param_count: sig.inputs().len() + usize::from(sig.c_variadic()),
370369
c_variadic: sig.c_variadic(),
371-
splatted: None,
370+
splatted: sig.splatted(),
372371
}
373372
}
374373

compiler/rustc_ast_passes/src/ast_validation.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -412,11 +412,11 @@ impl<'a> AstValidator<'a> {
412412
})
413413
.unzip();
414414

415-
// A splatted argument at the "no splatted" marker index is not supported (this is an
416-
// unlikely edge case).
415+
// A splatted argument greater than or equal to the "no splatted" marker index is not
416+
// supported.
417417
if let (Some(&splatted_arg_index), Some(&splatted_span)) =
418418
(splatted_arg_indexes.last(), splatted_spans.last())
419-
&& splatted_arg_index == FnDecl::NO_SPLATTED_ARG_INDEX
419+
&& splatted_arg_index >= u16::from(FnDecl::NO_SPLATTED_ARG_INDEX)
420420
{
421421
self.dcx().emit_err(diagnostics::InvalidSplattedArg {
422422
splatted_arg_index,

compiler/rustc_codegen_cranelift/src/abi/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ pub(crate) fn codegen_fn_prelude<'tcx>(fx: &mut FunctionCx<'_, '_, 'tcx>, start_
271271
.map(|local| {
272272
let arg_ty = fx.monomorphize(fx.mir.local_decls[local].ty);
273273

274+
// FIXME(splat): un-tuple splatted arguments in codegen, for performance
274275
// Adapted from https://github.com/rust-lang/rust/blob/145155dc96757002c7b2e9de8489416e2fdbbd57/src/librustc_codegen_llvm/mir/mod.rs#L442-L482
275276
if Some(local) == fx.mir.spread_arg {
276277
// This argument (e.g. the last argument in the "rust-call" ABI)

compiler/rustc_codegen_ssa/src/debuginfo/type_names.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ fn push_debuginfo_type_name<'tcx>(
375375
output.push_str("fn(");
376376
}
377377

378+
// FIXME(splat): should debuginfo be de-tupled in the callee (and caller)?
378379
if !sig.inputs().is_empty() {
379380
for &parameter_type in sig.inputs() {
380381
push_debuginfo_type_name(tcx, parameter_type, true, output, visited);

compiler/rustc_codegen_ssa/src/mir/block.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,6 +1185,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
11851185
};
11861186

11871187
// Split the rust-call tupled arguments off.
1188+
// FIXME(splat): un-tuple splatted arguments in codegen, for performance
11881189
let (first_args, untuple) = if sig.abi() == ExternAbi::RustCall
11891190
&& let Some((tup, args)) = args.split_last()
11901191
{

compiler/rustc_codegen_ssa/src/mir/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
433433
let arg_decl = &mir.local_decls[local];
434434
let arg_ty = fx.monomorphize(arg_decl.ty);
435435

436+
// FIXME(splat): re-tuple splatted arguments that were un-tupled in the ABI
436437
if Some(local) == mir.spread_arg {
437438
// This argument (e.g., the last argument in the "rust-call" ABI)
438439
// is a tuple that was spread at the ABI level and now we have

compiler/rustc_const_eval/src/const_eval/type_info.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use rustc_ast::Mutability;
77
use rustc_hir::LangItem;
88
use rustc_middle::span_bug;
99
use rustc_middle::ty::layout::TyAndLayout;
10-
use rustc_middle::ty::{self, Const, FnHeader, FnSigTys, ScalarInt, Ty, TyCtxt};
10+
use rustc_middle::ty::{self, Const, FnHeader, FnSigKind, FnSigTys, ScalarInt, Ty, TyCtxt};
1111
use rustc_span::{Symbol, sym};
1212

1313
use crate::const_eval::CompileTimeMachine;
@@ -436,6 +436,22 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> {
436436
sym::variadic => {
437437
self.write_scalar(Scalar::from_bool(fn_sig_kind.c_variadic()), &field_place)?;
438438
}
439+
sym::is_splatted => {
440+
self.write_scalar(
441+
Scalar::from_bool(fn_sig_kind.splatted().is_some()),
442+
&field_place,
443+
)?;
444+
}
445+
sym::splatted_index => {
446+
self.write_scalar(
447+
Scalar::from_u8(
448+
// Currently the same encoding as FnSigKind.splatted
449+
// FIXME(splat): make these two fields into a single Option<u8/u16>, or choose a stable encoding
450+
fn_sig_kind.splatted().unwrap_or(FnSigKind::NO_SPLATTED_ARG_INDEX),
451+
),
452+
&field_place,
453+
)?;
454+
}
439455
other => span_bug!(self.tcx.def_span(field.did), "unimplemented field {other}"),
440456
}
441457
}

compiler/rustc_const_eval/src/interpret/call.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
681681
};
682682

683683
// Special handling for the closure ABI: untuple the last argument.
684+
// FIXME(splat): un-tuple splatted arguments that were tupled in typecheck
684685
let args: Cow<'_, [FnArg<'tcx, M::Provenance>]> =
685686
if caller_abi == ExternAbi::RustCall && !args.is_empty() {
686687
// Untuple

compiler/rustc_hir/src/hir.rs

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4025,12 +4025,12 @@ pub struct Param<'hir> {
40254025
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40264026
pub enum SplattedArgIndexError {
40274027
/// The splatted argument index is invalid.
4028-
/// Currently the only unsupported index is `u16::MAX`, which is used to indicate that no argument
4029-
/// is splatted.
4030-
InvalidIndex { splatted_arg_index: u16 },
4028+
/// A `u8::MAX` argument index used to indicate that no argument is splatted.
4029+
/// Higher values are also not supported, for performance reasons.
4030+
InvalidIndex { splatted_arg_index: u8 },
40314031

40324032
/// The splatted argument index is outside the bounds of the function arguments.
4033-
OutOfBounds { splatted_arg_index: u16, args_len: u16 },
4033+
OutOfBounds { splatted_arg_index: u8, args_len: u16 },
40344034
}
40354035

40364036
/// Contains the packed non-type fields of a function declaration.
@@ -4041,9 +4041,9 @@ pub struct FnDeclFlags {
40414041
flags: u8,
40424042

40434043
/// Which function argument is splatted into multiple arguments in callers, if any?
4044-
/// Splatting functions with `u16::MAX` arguments is not supported, see `FnSigKind` for
4044+
/// Splatting functions with `>= u8::MAX` arguments is not supported, see `FnSigKind` for
40454045
/// details.
4046-
splatted: u16,
4046+
splatted: u8,
40474047
}
40484048

40494049
impl fmt::Debug for FnDeclFlags {
@@ -4081,13 +4081,13 @@ impl FnDeclFlags {
40814081

40824082
/// Marker index for "no splatted argument".
40834083
/// Must have the same value as `FnSigKind::NO_SPLATTED_ARG_INDEX` and `rustc_ast::FnDecl::NO_SPLATTED_ARG_INDEX`.
4084-
const NO_SPLATTED_ARG_INDEX: u16 = u16::MAX;
4084+
const NO_SPLATTED_ARG_INDEX: u8 = u8::MAX;
40854085

40864086
/// Create a new FnDeclKind with no implicit self, no lifetime elision, no C-style variadic
40874087
/// argument, and no splatting.
40884088
/// To modify these flags, use the `set_*` methods, for readability.
40894089
// FIXME: use Default instead when that trait is const stable.
4090-
pub const fn default() -> Self {
4090+
pub fn default() -> Self {
40914091
Self { flags: 0, splatted: 0 }
40924092
.set_implicit_self(ImplicitSelfKind::None)
40934093
.set_lifetime_elision_allowed(false)
@@ -4097,7 +4097,7 @@ impl FnDeclFlags {
40974097

40984098
/// Set the implicit self kind.
40994099
#[must_use = "this method does not modify the receiver"]
4100-
pub const fn set_implicit_self(mut self, implicit_self: ImplicitSelfKind) -> Self {
4100+
pub fn set_implicit_self(mut self, implicit_self: ImplicitSelfKind) -> Self {
41014101
self.flags &= !Self::IMPLICIT_SELF_MASK;
41024102

41034103
match implicit_self {
@@ -4113,7 +4113,7 @@ impl FnDeclFlags {
41134113

41144114
/// Set the C-style variadic argument flag.
41154115
#[must_use = "this method does not modify the receiver"]
4116-
pub const fn set_c_variadic(mut self, c_variadic: bool) -> Self {
4116+
pub fn set_c_variadic(mut self, c_variadic: bool) -> Self {
41174117
if c_variadic {
41184118
self.flags |= Self::C_VARIADIC_FLAG;
41194119
} else {
@@ -4125,7 +4125,7 @@ impl FnDeclFlags {
41254125

41264126
/// Set the lifetime elision allowed flag.
41274127
#[must_use = "this method does not modify the receiver"]
4128-
pub const fn set_lifetime_elision_allowed(mut self, allowed: bool) -> Self {
4128+
pub fn set_lifetime_elision_allowed(mut self, allowed: bool) -> Self {
41294129
if allowed {
41304130
self.flags |= Self::LIFETIME_ELISION_ALLOWED_FLAG;
41314131
} else {
@@ -4138,16 +4138,17 @@ impl FnDeclFlags {
41384138
/// Set the splatted argument index.
41394139
/// The number of function arguments is used for error checking.
41404140
#[must_use = "this method does not modify the receiver"]
4141-
pub const fn set_splatted(
4141+
pub fn set_splatted(
41424142
mut self,
4143-
splatted: Option<u16>,
4143+
splatted: Option<u8>,
41444144
args_len: usize,
41454145
) -> Result<Self, SplattedArgIndexError> {
41464146
if let Some(splatted_arg_index) = splatted {
41474147
if splatted_arg_index == Self::NO_SPLATTED_ARG_INDEX {
41484148
// This index value is used as a marker for "no splatting", so it is unsupported.
4149+
// Higher values are also not supported, for performance reasons.
41494150
return Err(SplattedArgIndexError::InvalidIndex { splatted_arg_index });
4150-
} else if splatted_arg_index as usize >= args_len {
4151+
} else if usize::from(splatted_arg_index) >= args_len {
41514152
return Err(SplattedArgIndexError::OutOfBounds {
41524153
splatted_arg_index,
41534154
args_len: args_len as u16,
@@ -4164,14 +4165,14 @@ impl FnDeclFlags {
41644165

41654166
/// Set "no splatted arguments" for the function declaration.
41664167
#[must_use = "this method does not modify the receiver"]
4167-
pub const fn set_no_splatted_args(mut self) -> Self {
4168+
pub fn set_no_splatted_args(mut self) -> Self {
41684169
self.splatted = Self::NO_SPLATTED_ARG_INDEX;
41694170

41704171
self
41714172
}
41724173

41734174
/// Get the implicit self kind.
4174-
pub const fn implicit_self(self) -> ImplicitSelfKind {
4175+
pub fn implicit_self(self) -> ImplicitSelfKind {
41754176
match self.flags & Self::IMPLICIT_SELF_MASK {
41764177
0 => ImplicitSelfKind::None,
41774178
1 => ImplicitSelfKind::Imm,
@@ -4183,17 +4184,17 @@ impl FnDeclFlags {
41834184
}
41844185

41854186
/// Do the function arguments end with a C-style variadic argument?
4186-
pub const fn c_variadic(self) -> bool {
4187+
pub fn c_variadic(self) -> bool {
41874188
self.flags & Self::C_VARIADIC_FLAG != 0
41884189
}
41894190

41904191
/// Is lifetime elision allowed?
4191-
pub const fn lifetime_elision_allowed(self) -> bool {
4192+
pub fn lifetime_elision_allowed(self) -> bool {
41924193
self.flags & Self::LIFETIME_ELISION_ALLOWED_FLAG != 0
41934194
}
41944195

41954196
/// Get the splatted argument index, if any.
4196-
pub const fn splatted(self) -> Option<u16> {
4197+
pub fn splatted(self) -> Option<u8> {
41974198
if self.splatted == Self::NO_SPLATTED_ARG_INDEX { None } else { Some(self.splatted) }
41984199
}
41994200
}
@@ -4243,7 +4244,7 @@ impl<'hir> FnDecl<'hir> {
42434244
self.fn_decl_kind.lifetime_elision_allowed()
42444245
}
42454246

4246-
pub fn splatted(&self) -> Option<u16> {
4247+
pub fn splatted(&self) -> Option<u8> {
42474248
self.fn_decl_kind.splatted()
42484249
}
42494250

0 commit comments

Comments
 (0)