@@ -1140,19 +1140,70 @@ 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+ let dst = match & self . locals [ local] {
1192+ LocalRef :: Place ( p) => * p,
1193+ _ => todo ! ( ) ,
1194+ } ;
1195+ caller_indirect_arg_places[ i] = Some ( dst) ;
1196+
1197+ bx. typed_place_copy ( dst. val , tmp. val , fn_abi. args [ i] . layout ) ;
1198+ bx. lifetime_end ( tmp. val . llval , tmp. layout . size ) ;
1199+ }
1200+ }
1201+
11431202 // When generating arguments we sometimes introduce temporary allocations with lifetime
11441203 // that extend for the duration of a call. Keep track of those allocations and their sizes
11451204 // to generate `lifetime_end` when the call returns.
11461205 let mut lifetime_ends_after_call: Vec < ( Bx :: Value , Size ) > = Vec :: new ( ) ;
11471206 ' 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-
11561207 let mut op = self . codegen_operand ( bx, & arg. node ) ;
11571208
11581209 if let ( 0 , Some ( ty:: InstanceKind :: Virtual ( _, idx) ) ) = ( i, instance. map ( |i| i. def ) ) {
@@ -1203,18 +1254,47 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
12031254 }
12041255 }
12051256
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 ) ) ;
1257+ match kind {
1258+ CallKind :: Tail => {
1259+ match fn_abi. args [ i] . mode {
1260+ PassMode :: Indirect { on_stack : false , .. } => {
1261+ let Some ( dst) = caller_indirect_arg_places[ i] else {
1262+ span_bug ! ( fn_span, "missing caller place for tail call arg {i}" ) ;
1263+ } ;
1264+
1265+ // The argument has been stored in the caller's argument place above.
1266+ // Now forward that place to the callee.
1267+ op. val = Ref ( dst. val ) ;
1268+ }
1269+ PassMode :: Indirect { on_stack : true , .. } => {
1270+ // FIXME: some LLVM backends (notably x86) do not correctly pass byval
1271+ // arguments to tail calls (as of LLVM 21). See also:
1272+ //
1273+ // - https://github.com/rust-lang/rust/pull/144232#discussion_r2218543841
1274+ // - https://github.com/rust-lang/rust/issues/144855
1275+ span_bug ! (
1276+ fn_span,
1277+ "arguments using PassMode::Indirect {{ on_stack: true, .. }} are currently not supported for tail calls"
1278+ )
1279+ }
1280+ _ => ( ) ,
1281+ }
1282+ }
1283+ CallKind :: Normal => {
1284+ // The callee needs to own the argument memory if we pass it
1285+ // by-ref, so make a local copy of non-immediate constants.
1286+ match ( & arg. node , op. val ) {
1287+ ( & mir:: Operand :: Copy ( _) , Ref ( PlaceValue { llextra : None , .. } ) )
1288+ | ( & mir:: Operand :: Constant ( _) , Ref ( PlaceValue { llextra : None , .. } ) ) => {
1289+ let tmp = PlaceRef :: alloca ( bx, op. layout ) ;
1290+ bx. lifetime_start ( tmp. val . llval , tmp. layout . size ) ;
1291+ op. store_with_annotation ( bx, tmp) ;
1292+ op. val = Ref ( tmp. val ) ;
1293+ lifetime_ends_after_call. push ( ( tmp. val . llval , tmp. layout . size ) ) ;
1294+ }
1295+ _ => { }
1296+ }
12161297 }
1217- _ => { }
12181298 }
12191299
12201300 self . codegen_argument (
0 commit comments