@@ -1146,19 +1146,51 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
11461146 ( args, None )
11471147 } ;
11481148
1149+ // Special logic for tail calls with `PassMode::Indirect { on_stack: false, .. }` arguments.
1150+ //
1151+ // Normally an indirect argument with `on_stack: false` would be passed as a pointer into
1152+ // the caller's stack frame. For tail calls, that would be unsound, because the caller's
1153+ // stack frame is overwritten by the callee's stack frame.
1154+ //
1155+ // Therefore we store the argument for the callee in the corresponding caller's slot.
1156+ // Because guaranteed tail calls demand that the caller's signature matches the callee's,
1157+ // the corresponding slot has the correct type.
1158+ //
1159+ // To handle cases like the one below, the tail call arguments must first be copied to a
1160+ // temporary, and only then copied to the caller's argument slots.
1161+ //
1162+ // ```
1163+ // // A struct big enough that it is not passed via registers.
1164+ // pub struct Big([u64; 4]);
1165+ //
1166+ // fn swapper(a: Big, b: Big) -> (Big, Big) {
1167+ // become swapper_helper(b, a);
1168+ // }
1169+ // ```
1170+ let mut tail_call_temporaries = vec ! [ ] ;
1171+ if kind == CallKind :: Tail {
1172+ tail_call_temporaries = vec ! [ None ; first_args. len( ) ] ;
1173+ // Copy the arguments that use `PassMode::Indirect { on_stack: false , ..}`
1174+ // to temporary stack allocations. See the comment above.
1175+ for ( i, arg) in first_args. iter ( ) . enumerate ( ) {
1176+ if !matches ! ( fn_abi. args[ i] . mode, PassMode :: Indirect { on_stack: false , .. } ) {
1177+ continue ;
1178+ }
1179+
1180+ let op = self . codegen_operand ( bx, & arg. node ) ;
1181+ let tmp = PlaceRef :: alloca ( bx, op. layout ) ;
1182+ bx. lifetime_start ( tmp. val . llval , tmp. layout . size ) ;
1183+ op. store_with_annotation ( bx, tmp) ;
1184+
1185+ tail_call_temporaries[ i] = Some ( tmp) ;
1186+ }
1187+ }
1188+
11491189 // When generating arguments we sometimes introduce temporary allocations with lifetime
11501190 // that extend for the duration of a call. Keep track of those allocations and their sizes
11511191 // to generate `lifetime_end` when the call returns.
11521192 let mut lifetime_ends_after_call: Vec < ( Bx :: Value , Size ) > = Vec :: new ( ) ;
11531193 ' make_args: for ( i, arg) in first_args. iter ( ) . enumerate ( ) {
1154- if kind == CallKind :: Tail && matches ! ( fn_abi. args[ i] . mode, PassMode :: Indirect { .. } ) {
1155- // FIXME: https://github.com/rust-lang/rust/pull/144232#discussion_r2218543841
1156- span_bug ! (
1157- fn_span,
1158- "arguments using PassMode::Indirect are currently not supported for tail calls"
1159- ) ;
1160- }
1161-
11621194 let mut op = self . codegen_operand ( bx, & arg. node ) ;
11631195
11641196 if let ( 0 , Some ( ty:: InstanceKind :: Virtual ( _, idx) ) ) = ( i, instance. map ( |i| i. def ) ) {
@@ -1209,18 +1241,72 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
12091241 }
12101242 }
12111243
1212- // The callee needs to own the argument memory if we pass it
1213- // by-ref, so make a local copy of non-immediate constants.
1214- match ( & arg. node , op. val ) {
1215- ( & mir:: Operand :: Copy ( _) , Ref ( PlaceValue { llextra : None , .. } ) )
1216- | ( & mir:: Operand :: Constant ( _) , Ref ( PlaceValue { llextra : None , .. } ) ) => {
1217- let tmp = PlaceRef :: alloca ( bx, op. layout ) ;
1218- bx. lifetime_start ( tmp. val . llval , tmp. layout . size ) ;
1219- op. store_with_annotation ( bx, tmp) ;
1220- op. val = Ref ( tmp. val ) ;
1221- lifetime_ends_after_call. push ( ( tmp. val . llval , tmp. layout . size ) ) ;
1244+ match kind {
1245+ CallKind :: Normal => {
1246+ // The callee needs to own the argument memory if we pass it
1247+ // by-ref, so make a local copy of non-immediate constants.
1248+ if let & mir:: Operand :: Copy ( _) | & mir:: Operand :: Constant ( _) = & arg. node
1249+ && let Ref ( PlaceValue { llextra : None , .. } ) = op. val
1250+ {
1251+ let tmp = PlaceRef :: alloca ( bx, op. layout ) ;
1252+ bx. lifetime_start ( tmp. val . llval , tmp. layout . size ) ;
1253+ op. store_with_annotation ( bx, tmp) ;
1254+ op. val = Ref ( tmp. val ) ;
1255+ lifetime_ends_after_call. push ( ( tmp. val . llval , tmp. layout . size ) ) ;
1256+ }
1257+ }
1258+ CallKind :: Tail => {
1259+ match fn_abi. args [ i] . mode {
1260+ PassMode :: Indirect { on_stack : false , .. } => {
1261+ let Some ( tmp) = tail_call_temporaries[ i] . take ( ) else {
1262+ span_bug ! (
1263+ fn_span,
1264+ "missing temporary for indirect tail call argument #{i}"
1265+ )
1266+ } ;
1267+
1268+ let local = self . mir . args_iter ( ) . nth ( i) . unwrap ( ) ;
1269+
1270+ match & self . locals [ local] {
1271+ LocalRef :: Place ( arg) => {
1272+ bx. typed_place_copy ( arg. val , tmp. val , fn_abi. args [ i] . layout ) ;
1273+ op. val = Ref ( arg. val ) ;
1274+ }
1275+ LocalRef :: Operand ( arg) => {
1276+ let Ref ( place_value) = arg. val else {
1277+ bug ! ( "only `Ref` should use `PassMode::Indirect`" ) ;
1278+ } ;
1279+ bx. typed_place_copy (
1280+ place_value,
1281+ tmp. val ,
1282+ fn_abi. args [ i] . layout ,
1283+ ) ;
1284+ op. val = arg. val ;
1285+ }
1286+ LocalRef :: UnsizedPlace ( _) => {
1287+ span_bug ! ( fn_span, "unsized types are not supported" )
1288+ }
1289+ LocalRef :: PendingOperand => {
1290+ span_bug ! ( fn_span, "argument local should not be pending" )
1291+ }
1292+ } ;
1293+
1294+ bx. lifetime_end ( tmp. val . llval , tmp. layout . size ) ;
1295+ }
1296+ PassMode :: Indirect { on_stack : true , .. } => {
1297+ // FIXME: some LLVM backends (notably x86) do not correctly pass byval
1298+ // arguments to tail calls (as of LLVM 21). See also:
1299+ //
1300+ // - https://github.com/rust-lang/rust/pull/144232#discussion_r2218543841
1301+ // - https://github.com/rust-lang/rust/issues/144855
1302+ span_bug ! (
1303+ fn_span,
1304+ "arguments using PassMode::Indirect {{ on_stack: true, .. }} are currently not supported for tail calls"
1305+ )
1306+ }
1307+ _ => ( ) ,
1308+ }
12221309 }
1223- _ => { }
12241310 }
12251311
12261312 self . codegen_argument (
0 commit comments