Skip to content

Commit 58fe8e0

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 73100ee + 9b44c23 commit 58fe8e0

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
@@ -4045,12 +4045,12 @@ pub struct Param<'hir> {
40454045
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40464046
pub enum SplattedArgIndexError {
40474047
/// The splatted argument index is invalid.
4048-
/// Currently the only unsupported index is `u16::MAX`, which is used to indicate that no argument
4049-
/// is splatted.
4050-
InvalidIndex { splatted_arg_index: u16 },
4048+
/// A `u8::MAX` argument index used to indicate that no argument is splatted.
4049+
/// Higher values are also not supported, for performance reasons.
4050+
InvalidIndex { splatted_arg_index: u8 },
40514051

40524052
/// The splatted argument index is outside the bounds of the function arguments.
4053-
OutOfBounds { splatted_arg_index: u16, args_len: u16 },
4053+
OutOfBounds { splatted_arg_index: u8, args_len: u16 },
40544054
}
40554055

40564056
/// Contains the packed non-type fields of a function declaration.
@@ -4061,9 +4061,9 @@ pub struct FnDeclFlags {
40614061
flags: u8,
40624062

40634063
/// Which function argument is splatted into multiple arguments in callers, if any?
4064-
/// Splatting functions with `u16::MAX` arguments is not supported, see `FnSigKind` for
4064+
/// Splatting functions with `>= u8::MAX` arguments is not supported, see `FnSigKind` for
40654065
/// details.
4066-
splatted: u16,
4066+
splatted: u8,
40674067
}
40684068

40694069
impl fmt::Debug for FnDeclFlags {
@@ -4101,13 +4101,13 @@ impl FnDeclFlags {
41014101

41024102
/// Marker index for "no splatted argument".
41034103
/// Must have the same value as `FnSigKind::NO_SPLATTED_ARG_INDEX` and `rustc_ast::FnDecl::NO_SPLATTED_ARG_INDEX`.
4104-
const NO_SPLATTED_ARG_INDEX: u16 = u16::MAX;
4104+
const NO_SPLATTED_ARG_INDEX: u8 = u8::MAX;
41054105

41064106
/// Create a new FnDeclKind with no implicit self, no lifetime elision, no C-style variadic
41074107
/// argument, and no splatting.
41084108
/// To modify these flags, use the `set_*` methods, for readability.
41094109
// FIXME: use Default instead when that trait is const stable.
4110-
pub const fn default() -> Self {
4110+
pub fn default() -> Self {
41114111
Self { flags: 0, splatted: 0 }
41124112
.set_implicit_self(ImplicitSelfKind::None)
41134113
.set_lifetime_elision_allowed(false)
@@ -4117,7 +4117,7 @@ impl FnDeclFlags {
41174117

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

41234123
match implicit_self {
@@ -4133,7 +4133,7 @@ impl FnDeclFlags {
41334133

41344134
/// Set the C-style variadic argument flag.
41354135
#[must_use = "this method does not modify the receiver"]
4136-
pub const fn set_c_variadic(mut self, c_variadic: bool) -> Self {
4136+
pub fn set_c_variadic(mut self, c_variadic: bool) -> Self {
41374137
if c_variadic {
41384138
self.flags |= Self::C_VARIADIC_FLAG;
41394139
} else {
@@ -4145,7 +4145,7 @@ impl FnDeclFlags {
41454145

41464146
/// Set the lifetime elision allowed flag.
41474147
#[must_use = "this method does not modify the receiver"]
4148-
pub const fn set_lifetime_elision_allowed(mut self, allowed: bool) -> Self {
4148+
pub fn set_lifetime_elision_allowed(mut self, allowed: bool) -> Self {
41494149
if allowed {
41504150
self.flags |= Self::LIFETIME_ELISION_ALLOWED_FLAG;
41514151
} else {
@@ -4158,16 +4158,17 @@ impl FnDeclFlags {
41584158
/// Set the splatted argument index.
41594159
/// The number of function arguments is used for error checking.
41604160
#[must_use = "this method does not modify the receiver"]
4161-
pub const fn set_splatted(
4161+
pub fn set_splatted(
41624162
mut self,
4163-
splatted: Option<u16>,
4163+
splatted: Option<u8>,
41644164
args_len: usize,
41654165
) -> Result<Self, SplattedArgIndexError> {
41664166
if let Some(splatted_arg_index) = splatted {
41674167
if splatted_arg_index == Self::NO_SPLATTED_ARG_INDEX {
41684168
// This index value is used as a marker for "no splatting", so it is unsupported.
4169+
// Higher values are also not supported, for performance reasons.
41694170
return Err(SplattedArgIndexError::InvalidIndex { splatted_arg_index });
4170-
} else if splatted_arg_index as usize >= args_len {
4171+
} else if usize::from(splatted_arg_index) >= args_len {
41714172
return Err(SplattedArgIndexError::OutOfBounds {
41724173
splatted_arg_index,
41734174
args_len: args_len as u16,
@@ -4184,14 +4185,14 @@ impl FnDeclFlags {
41844185

41854186
/// Set "no splatted arguments" for the function declaration.
41864187
#[must_use = "this method does not modify the receiver"]
4187-
pub const fn set_no_splatted_args(mut self) -> Self {
4188+
pub fn set_no_splatted_args(mut self) -> Self {
41884189
self.splatted = Self::NO_SPLATTED_ARG_INDEX;
41894190

41904191
self
41914192
}
41924193

41934194
/// Get the implicit self kind.
4194-
pub const fn implicit_self(self) -> ImplicitSelfKind {
4195+
pub fn implicit_self(self) -> ImplicitSelfKind {
41954196
match self.flags & Self::IMPLICIT_SELF_MASK {
41964197
0 => ImplicitSelfKind::None,
41974198
1 => ImplicitSelfKind::Imm,
@@ -4203,17 +4204,17 @@ impl FnDeclFlags {
42034204
}
42044205

42054206
/// Do the function arguments end with a C-style variadic argument?
4206-
pub const fn c_variadic(self) -> bool {
4207+
pub fn c_variadic(self) -> bool {
42074208
self.flags & Self::C_VARIADIC_FLAG != 0
42084209
}
42094210

42104211
/// Is lifetime elision allowed?
4211-
pub const fn lifetime_elision_allowed(self) -> bool {
4212+
pub fn lifetime_elision_allowed(self) -> bool {
42124213
self.flags & Self::LIFETIME_ELISION_ALLOWED_FLAG != 0
42134214
}
42144215

42154216
/// Get the splatted argument index, if any.
4216-
pub const fn splatted(self) -> Option<u16> {
4217+
pub fn splatted(self) -> Option<u8> {
42174218
if self.splatted == Self::NO_SPLATTED_ARG_INDEX { None } else { Some(self.splatted) }
42184219
}
42194220
}
@@ -4263,7 +4264,7 @@ impl<'hir> FnDecl<'hir> {
42634264
self.fn_decl_kind.lifetime_elision_allowed()
42644265
}
42654266

4266-
pub fn splatted(&self) -> Option<u16> {
4267+
pub fn splatted(&self) -> Option<u8> {
42674268
self.fn_decl_kind.splatted()
42684269
}
42694270

0 commit comments

Comments
 (0)