@@ -1140,19 +1140,50 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
11401140 ( args, None )
11411141 } ;
11421142
1143+ // Special logic for tail calls with `PassMode::Indirect { on_stack: false, .. }` arguments.
1144+ //
1145+ // Normally an indirect argument with `on_stack: false` would be passed as a pointer into
1146+ // the caller's stack frame. For tail calls, that would be unsound, because the caller's
1147+ // stack frame is overwritten by the callee's stack frame.
1148+ //
1149+ // Therefore we store the argument for the callee in the corresponding caller's slot.
1150+ // Because guaranteed tail calls demand that the caller's signature matches the callee's,
1151+ // the corresponding slot has the correct type.
1152+ //
1153+ // To handle cases like the one below, the tail call arguments must first be copied to a
1154+ // temporary, and only then copied to the caller's argument slots.
1155+ //
1156+ // ```
1157+ // // A struct big enough that it is not passed via registers.
1158+ // pub struct Big([u64; 4]);
1159+ //
1160+ // fn swapper(a: Big, b: Big) -> (Big, Big) {
1161+ // become swapper_helper(b, a);
1162+ // }
1163+ // ```
1164+ let mut tail_call_temporaries = vec ! [ ] ;
1165+ if kind == CallKind :: Tail {
1166+ tail_call_temporaries = vec ! [ None ; first_args. len( ) ] ;
1167+ // First copy the arguments of this call to temporary stack allocations.
1168+ for ( i, arg) in first_args. iter ( ) . enumerate ( ) {
1169+ if !matches ! ( fn_abi. args[ i] . mode, PassMode :: Indirect { on_stack: false , .. } ) {
1170+ continue ;
1171+ }
1172+
1173+ let op = self . codegen_operand ( bx, & arg. node ) ;
1174+ let tmp = PlaceRef :: alloca ( bx, op. layout ) ;
1175+ bx. lifetime_start ( tmp. val . llval , tmp. layout . size ) ;
1176+ op. store_with_annotation ( bx, tmp) ;
1177+
1178+ tail_call_temporaries[ i] = Some ( tmp) ;
1179+ }
1180+ }
1181+
11431182 // When generating arguments we sometimes introduce temporary allocations with lifetime
11441183 // that extend for the duration of a call. Keep track of those allocations and their sizes
11451184 // to generate `lifetime_end` when the call returns.
11461185 let mut lifetime_ends_after_call: Vec < ( Bx :: Value , Size ) > = Vec :: new ( ) ;
11471186 ' make_args: for ( i, arg) in first_args. iter ( ) . enumerate ( ) {
1148- if kind == CallKind :: Tail && matches ! ( fn_abi. args[ i] . mode, PassMode :: Indirect { .. } ) {
1149- // FIXME: https://github.com/rust-lang/rust/pull/144232#discussion_r2218543841
1150- span_bug ! (
1151- fn_span,
1152- "arguments using PassMode::Indirect are currently not supported for tail calls"
1153- ) ;
1154- }
1155-
11561187 let mut op = self . codegen_operand ( bx, & arg. node ) ;
11571188
11581189 if let ( 0 , Some ( ty:: InstanceKind :: Virtual ( _, idx) ) ) = ( i, instance. map ( |i| i. def ) ) {
@@ -1203,18 +1234,71 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
12031234 }
12041235 }
12051236
1206- // The callee needs to own the argument memory if we pass it
1207- // by-ref, so make a local copy of non-immediate constants.
1208- match ( & arg. node , op. val ) {
1209- ( & mir:: Operand :: Copy ( _) , Ref ( PlaceValue { llextra : None , .. } ) )
1210- | ( & mir:: Operand :: Constant ( _) , Ref ( PlaceValue { llextra : None , .. } ) ) => {
1211- let tmp = PlaceRef :: alloca ( bx, op. layout ) ;
1212- bx. lifetime_start ( tmp. val . llval , tmp. layout . size ) ;
1213- op. store_with_annotation ( bx, tmp) ;
1214- op. val = Ref ( tmp. val ) ;
1215- lifetime_ends_after_call. push ( ( tmp. val . llval , tmp. layout . size ) ) ;
1237+ match kind {
1238+ CallKind :: Tail => {
1239+ match fn_abi. args [ i] . mode {
1240+ PassMode :: Indirect { on_stack : false , .. } => {
1241+ let Some ( tmp) = tail_call_temporaries[ i] . take ( ) else {
1242+ span_bug ! (
1243+ fn_span,
1244+ "missing temporary for indirect tail call argument #{i}"
1245+ )
1246+ } ;
1247+
1248+ let local = self . mir . args_iter ( ) . nth ( i) . unwrap ( ) ;
1249+
1250+ match & self . locals [ local] {
1251+ LocalRef :: Place ( arg) => {
1252+ bx. typed_place_copy ( arg. val , tmp. val , fn_abi. args [ i] . layout ) ;
1253+ op. val = Ref ( arg. val ) ;
1254+ }
1255+ LocalRef :: Operand ( arg) => {
1256+ bx. typed_place_copy (
1257+ arg. val . deref ( fn_abi. args [ i] . layout . align . abi ) ,
1258+ tmp. val ,
1259+ fn_abi. args [ i] . layout ,
1260+ ) ;
1261+ op. val = arg. val ;
1262+ }
1263+ LocalRef :: UnsizedPlace ( _) => {
1264+ span_bug ! ( fn_span, "unsized types are not supported" )
1265+ }
1266+ LocalRef :: PendingOperand => {
1267+ span_bug ! ( fn_span, "argument local should not be pending" )
1268+ }
1269+ } ;
1270+
1271+ bx. lifetime_end ( tmp. val . llval , tmp. layout . size ) ;
1272+ }
1273+ PassMode :: Indirect { on_stack : true , .. } => {
1274+ // FIXME: some LLVM backends (notably x86) do not correctly pass byval
1275+ // arguments to tail calls (as of LLVM 21). See also:
1276+ //
1277+ // - https://github.com/rust-lang/rust/pull/144232#discussion_r2218543841
1278+ // - https://github.com/rust-lang/rust/issues/144855
1279+ span_bug ! (
1280+ fn_span,
1281+ "arguments using PassMode::Indirect {{ on_stack: true, .. }} are currently not supported for tail calls"
1282+ )
1283+ }
1284+ _ => ( ) ,
1285+ }
1286+ }
1287+ CallKind :: Normal => {
1288+ // The callee needs to own the argument memory if we pass it
1289+ // by-ref, so make a local copy of non-immediate constants.
1290+ match ( & arg. node , op. val ) {
1291+ ( & mir:: Operand :: Copy ( _) , Ref ( PlaceValue { llextra : None , .. } ) )
1292+ | ( & mir:: Operand :: Constant ( _) , Ref ( PlaceValue { llextra : None , .. } ) ) => {
1293+ let tmp = PlaceRef :: alloca ( bx, op. layout ) ;
1294+ bx. lifetime_start ( tmp. val . llval , tmp. layout . size ) ;
1295+ op. store_with_annotation ( bx, tmp) ;
1296+ op. val = Ref ( tmp. val ) ;
1297+ lifetime_ends_after_call. push ( ( tmp. val . llval , tmp. layout . size ) ) ;
1298+ }
1299+ _ => { }
1300+ }
12161301 }
1217- _ => { }
12181302 }
12191303
12201304 self . codegen_argument (
0 commit comments