@@ -1140,19 +1140,76 @@ 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 caller_indirect_arg_places = Vec :: new ( ) ;
1165+ if kind == CallKind :: Tail {
1166+ let mut temporaries = vec ! [ None ; first_args. len( ) ] ;
1167+ caller_indirect_arg_places = vec ! [ None ; first_args. len( ) ] ;
1168+
1169+ // First copy the arguments of this call to temporary stack allocations.
1170+ for ( i, arg) in first_args. iter ( ) . enumerate ( ) {
1171+ if !matches ! ( fn_abi. args[ i] . mode, PassMode :: Indirect { on_stack: false , .. } ) {
1172+ continue ;
1173+ }
1174+
1175+ let mut op = self . codegen_operand ( bx, & arg. node ) ;
1176+
1177+ let tmp = PlaceRef :: alloca ( bx, op. layout ) ;
1178+ bx. lifetime_start ( tmp. val . llval , tmp. layout . size ) ;
1179+ op. store_with_annotation ( bx, tmp) ;
1180+
1181+ op. val = Ref ( tmp. val ) ;
1182+ temporaries[ i] = Some ( tmp) ;
1183+ }
1184+
1185+ // Then actually copy them into the corresponding argument place of our caller.
1186+ for ( i, opt_tmp) in temporaries. into_iter ( ) . enumerate ( ) {
1187+ let Some ( tmp) = opt_tmp else { continue } ;
1188+
1189+ let local = self . mir . args_iter ( ) . nth ( i) . unwrap ( ) ;
1190+
1191+ match & self . locals [ local] {
1192+ LocalRef :: Place ( arg) => {
1193+ caller_indirect_arg_places[ i] = Some ( Ref ( arg. val ) ) ;
1194+ bx. typed_place_copy ( arg. val , tmp. val , fn_abi. args [ i] . layout ) ;
1195+ }
1196+ LocalRef :: Operand ( arg) => {
1197+ caller_indirect_arg_places[ i] = Some ( arg. val ) ;
1198+ arg. store_with_annotation ( bx, tmp) ;
1199+ }
1200+ LocalRef :: UnsizedPlace ( _) => bug ! ( "unsized types are not supported" ) ,
1201+ LocalRef :: PendingOperand => bug ! ( "argument local should not be pending" ) ,
1202+ } ;
1203+
1204+ bx. lifetime_end ( tmp. val . llval , tmp. layout . size ) ;
1205+ }
1206+ }
1207+
11431208 // When generating arguments we sometimes introduce temporary allocations with lifetime
11441209 // that extend for the duration of a call. Keep track of those allocations and their sizes
11451210 // to generate `lifetime_end` when the call returns.
11461211 let mut lifetime_ends_after_call: Vec < ( Bx :: Value , Size ) > = Vec :: new ( ) ;
11471212 ' 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-
11561213 let mut op = self . codegen_operand ( bx, & arg. node ) ;
11571214
11581215 if let ( 0 , Some ( ty:: InstanceKind :: Virtual ( _, idx) ) ) = ( i, instance. map ( |i| i. def ) ) {
@@ -1203,18 +1260,47 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
12031260 }
12041261 }
12051262
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 ) ) ;
1263+ match kind {
1264+ CallKind :: Tail => {
1265+ match fn_abi. args [ i] . mode {
1266+ PassMode :: Indirect { on_stack : false , .. } => {
1267+ let Some ( dst_val) = caller_indirect_arg_places[ i] else {
1268+ span_bug ! ( fn_span, "missing caller place for tail call arg {i}" ) ;
1269+ } ;
1270+
1271+ // The argument has been stored in the caller's argument place above.
1272+ // Now forward that place to the callee.
1273+ op. val = dst_val;
1274+ }
1275+ PassMode :: Indirect { on_stack : true , .. } => {
1276+ // FIXME: some LLVM backends (notably x86) do not correctly pass byval
1277+ // arguments to tail calls (as of LLVM 21). See also:
1278+ //
1279+ // - https://github.com/rust-lang/rust/pull/144232#discussion_r2218543841
1280+ // - https://github.com/rust-lang/rust/issues/144855
1281+ span_bug ! (
1282+ fn_span,
1283+ "arguments using PassMode::Indirect {{ on_stack: true, .. }} are currently not supported for tail calls"
1284+ )
1285+ }
1286+ _ => ( ) ,
1287+ }
1288+ }
1289+ CallKind :: Normal => {
1290+ // The callee needs to own the argument memory if we pass it
1291+ // by-ref, so make a local copy of non-immediate constants.
1292+ match ( & arg. node , op. val ) {
1293+ ( & mir:: Operand :: Copy ( _) , Ref ( PlaceValue { llextra : None , .. } ) )
1294+ | ( & mir:: Operand :: Constant ( _) , Ref ( PlaceValue { llextra : None , .. } ) ) => {
1295+ let tmp = PlaceRef :: alloca ( bx, op. layout ) ;
1296+ bx. lifetime_start ( tmp. val . llval , tmp. layout . size ) ;
1297+ op. store_with_annotation ( bx, tmp) ;
1298+ op. val = Ref ( tmp. val ) ;
1299+ lifetime_ends_after_call. push ( ( tmp. val . llval , tmp. layout . size ) ) ;
1300+ }
1301+ _ => { }
1302+ }
12161303 }
1217- _ => { }
12181304 }
12191305
12201306 self . codegen_argument (
0 commit comments