Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0722a1e

Browse files
committedJun 15, 2023
Support tail calls in rustc_codegen_{ssa,llvm}
1 parent dee279d commit 0722a1e

File tree

4 files changed

+390
-6
lines changed

4 files changed

+390
-6
lines changed
 

‎compiler/rustc_codegen_llvm/src/builder.rs

+24
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use rustc_codegen_ssa::traits::*;
1414
use rustc_codegen_ssa::MemFlags;
1515
use rustc_data_structures::small_c_str::SmallCStr;
1616
use rustc_hir::def_id::DefId;
17+
use rustc_middle::bug;
1718
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
1819
use rustc_middle::ty::layout::{
1920
FnAbiError, FnAbiOfHelpers, FnAbiRequest, LayoutError, LayoutOfHelpers, TyAndLayout,
@@ -1217,6 +1218,29 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
12171218
call
12181219
}
12191220

1221+
fn tail_call(
1222+
&mut self,
1223+
llty: Self::Type,
1224+
fn_attrs: Option<&CodegenFnAttrs>,
1225+
fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
1226+
llfn: Self::Value,
1227+
args: &[Self::Value],
1228+
funclet: Option<&Self::Funclet>,
1229+
) {
1230+
let call = self.call(llty, fn_attrs, Some(fn_abi), llfn, args, funclet);
1231+
1232+
// Depending on the pass mode we neet to generate different return instructions for llvm
1233+
match &fn_abi.ret.mode {
1234+
abi::call::PassMode::Ignore | abi::call::PassMode::Indirect { .. } => self.ret_void(),
1235+
abi::call::PassMode::Direct(_) | abi::call::PassMode::Pair { .. } => self.ret(call),
1236+
mode @ abi::call::PassMode::Cast(..) => {
1237+
bug!("Encountered `PassMode::{mode:?}` during codegen")
1238+
}
1239+
}
1240+
1241+
unsafe { llvm::LLVMRustSetTailCallKind(call, llvm::TailCallKind::MustTail) };
1242+
}
1243+
12201244
fn zext(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> &'ll Value {
12211245
unsafe { llvm::LLVMBuildZExt(self.llbuilder, val, dest_ty, UNNAMED) }
12221246
}

‎compiler/rustc_codegen_ssa/src/mir/block.rs

+264-6
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,31 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
239239
}
240240
}
241241

242+
/// **Tail** call `fn_ptr` of `fn_abi` with the arguments `llargs`.
243+
fn do_tail_call<Bx: BuilderMethods<'a, 'tcx>>(
244+
&self,
245+
fx: &mut FunctionCx<'a, 'tcx, Bx>,
246+
bx: &mut Bx,
247+
fn_abi: &'tcx FnAbi<'tcx, Ty<'tcx>>,
248+
fn_ptr: Bx::Value,
249+
llargs: &[Bx::Value],
250+
copied_constant_arguments: &[PlaceRef<'tcx, <Bx as BackendTypes>::Value>],
251+
) {
252+
let fn_ty = bx.fn_decl_backend_type(&fn_abi);
253+
254+
let fn_attrs = if bx.tcx().def_kind(fx.instance.def_id()).has_codegen_attrs() {
255+
Some(bx.tcx().codegen_fn_attrs(fx.instance.def_id()))
256+
} else {
257+
None
258+
};
259+
260+
bx.tail_call(fn_ty, fn_attrs, fn_abi, fn_ptr, &llargs, self.funclet(fx));
261+
262+
for tmp in copied_constant_arguments {
263+
bx.lifetime_end(tmp.llval, tmp.layout.size);
264+
}
265+
}
266+
242267
/// Generates inline assembly with optional `destination` and `unwind`.
243268
fn do_inlineasm<Bx: BuilderMethods<'a, 'tcx>>(
244269
&self,
@@ -1077,6 +1102,242 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
10771102
)
10781103
}
10791104

1105+
fn codegen_tail_call_terminator(
1106+
&mut self,
1107+
helper: TerminatorCodegenHelper<'tcx>,
1108+
bx: &mut Bx,
1109+
terminator: &mir::Terminator<'tcx>,
1110+
func: &mir::Operand<'tcx>,
1111+
args: &[mir::Operand<'tcx>],
1112+
fn_span: Span,
1113+
) {
1114+
let source_info = terminator.source_info;
1115+
let span = source_info.span;
1116+
1117+
// Create the callee. This is a fn ptr or zero-sized and hence a kind of scalar.
1118+
let callee = self.codegen_operand(bx, func);
1119+
1120+
let (instance, mut llfn) = match *callee.layout.ty.kind() {
1121+
ty::FnDef(def_id, substs) => (
1122+
Some(
1123+
ty::Instance::expect_resolve(
1124+
bx.tcx(),
1125+
ty::ParamEnv::reveal_all(),
1126+
def_id,
1127+
substs,
1128+
)
1129+
.polymorphize(bx.tcx()),
1130+
),
1131+
None,
1132+
),
1133+
ty::FnPtr(_) => (None, Some(callee.immediate())),
1134+
_ => bug!("{} is not callable", callee.layout.ty),
1135+
};
1136+
let def = instance.map(|i| i.def);
1137+
1138+
if let Some(ty::InstanceDef::DropGlue(..)) = def {
1139+
bug!("tail-calling drop glue should not be possible");
1140+
}
1141+
1142+
// FIXME(eddyb) avoid computing this if possible, when `instance` is
1143+
// available - right now `sig` is only needed for getting the `abi`
1144+
// and figuring out how many extra args were passed to a C-variadic `fn`.
1145+
let sig = callee.layout.ty.fn_sig(bx.tcx());
1146+
let abi = sig.abi();
1147+
1148+
if let Some(ty::InstanceDef::Intrinsic(def_id)) = def {
1149+
span_bug!(
1150+
fn_span,
1151+
"Attempting to tail-call `{}` intrinsic",
1152+
bx.tcx().item_name(def_id)
1153+
);
1154+
};
1155+
1156+
let extra_args = &args[sig.inputs().skip_binder().len()..];
1157+
let extra_args = bx.tcx().mk_type_list_from_iter(extra_args.iter().map(|op_arg| {
1158+
let op_ty = op_arg.ty(self.mir, bx.tcx());
1159+
self.monomorphize(op_ty)
1160+
}));
1161+
1162+
let fn_abi = match instance {
1163+
Some(instance) => bx.fn_abi_of_instance(instance, extra_args),
1164+
None => bx.fn_abi_of_fn_ptr(sig, extra_args),
1165+
};
1166+
1167+
// The arguments we'll be passing. Plus one to account for outptr, if used.
1168+
let arg_count = fn_abi.args.len() + fn_abi.ret.is_indirect() as usize;
1169+
let mut llargs = Vec::with_capacity(arg_count);
1170+
1171+
if fn_abi.ret.is_indirect() {
1172+
let LocalRef::Place(place) = self.locals[mir::RETURN_PLACE]
1173+
else { bug!() };
1174+
1175+
llargs.push(place.llval);
1176+
}
1177+
1178+
// Split the rust-call tupled arguments off.
1179+
let (first_args, untuple) = if abi == Abi::RustCall && !args.is_empty() {
1180+
let (tup, args) = args.split_last().unwrap();
1181+
(args, Some(tup))
1182+
} else {
1183+
(args, None)
1184+
};
1185+
1186+
// FIXME(explicit_tail_calls): refactor this into a separate function, deduplicate with `Call`
1187+
let mut copied_constant_arguments = vec![];
1188+
'make_args: for (i, arg) in first_args.iter().enumerate() {
1189+
let mut op = self.codegen_operand(bx, arg);
1190+
1191+
if let (0, Some(ty::InstanceDef::Virtual(_, idx))) = (i, def) {
1192+
match op.val {
1193+
Pair(data_ptr, meta) => {
1194+
// In the case of Rc<Self>, we need to explicitly pass a
1195+
// *mut RcBox<Self> with a Scalar (not ScalarPair) ABI. This is a hack
1196+
// that is understood elsewhere in the compiler as a method on
1197+
// `dyn Trait`.
1198+
// To get a `*mut RcBox<Self>`, we just keep unwrapping newtypes until
1199+
// we get a value of a built-in pointer type.
1200+
//
1201+
// This is also relevant for `Pin<&mut Self>`, where we need to peel the `Pin`.
1202+
'descend_newtypes: while !op.layout.ty.is_unsafe_ptr()
1203+
&& !op.layout.ty.is_ref()
1204+
{
1205+
for i in 0..op.layout.fields.count() {
1206+
let field = op.extract_field(bx, i);
1207+
if !field.layout.is_zst() {
1208+
// we found the one non-zero-sized field that is allowed
1209+
// now find *its* non-zero-sized field, or stop if it's a
1210+
// pointer
1211+
op = field;
1212+
continue 'descend_newtypes;
1213+
}
1214+
}
1215+
1216+
span_bug!(span, "receiver has no non-zero-sized fields {:?}", op);
1217+
}
1218+
1219+
// now that we have `*dyn Trait` or `&dyn Trait`, split it up into its
1220+
// data pointer and vtable. Look up the method in the vtable, and pass
1221+
// the data pointer as the first argument
1222+
llfn = Some(meth::VirtualIndex::from_index(idx).get_fn(
1223+
bx,
1224+
meta,
1225+
op.layout.ty,
1226+
&fn_abi,
1227+
));
1228+
llargs.push(data_ptr);
1229+
continue 'make_args;
1230+
}
1231+
Ref(data_ptr, Some(meta), _) => {
1232+
// by-value dynamic dispatch
1233+
llfn = Some(meth::VirtualIndex::from_index(idx).get_fn(
1234+
bx,
1235+
meta,
1236+
op.layout.ty,
1237+
&fn_abi,
1238+
));
1239+
llargs.push(data_ptr);
1240+
continue;
1241+
}
1242+
Immediate(_) => {
1243+
// See comment above explaining why we peel these newtypes
1244+
'descend_newtypes: while !op.layout.ty.is_unsafe_ptr()
1245+
&& !op.layout.ty.is_ref()
1246+
{
1247+
for i in 0..op.layout.fields.count() {
1248+
let field = op.extract_field(bx, i);
1249+
if !field.layout.is_zst() {
1250+
// we found the one non-zero-sized field that is allowed
1251+
// now find *its* non-zero-sized field, or stop if it's a
1252+
// pointer
1253+
op = field;
1254+
continue 'descend_newtypes;
1255+
}
1256+
}
1257+
1258+
span_bug!(span, "receiver has no non-zero-sized fields {:?}", op);
1259+
}
1260+
1261+
// Make sure that we've actually unwrapped the rcvr down
1262+
// to a pointer or ref to `dyn* Trait`.
1263+
if !op.layout.ty.builtin_deref(true).unwrap().ty.is_dyn_star() {
1264+
span_bug!(span, "can't codegen a virtual call on {:#?}", op);
1265+
}
1266+
let place = op.deref(bx.cx());
1267+
let data_ptr = place.project_field(bx, 0);
1268+
let meta_ptr = place.project_field(bx, 1);
1269+
let meta = bx.load_operand(meta_ptr);
1270+
llfn = Some(meth::VirtualIndex::from_index(idx).get_fn(
1271+
bx,
1272+
meta.immediate(),
1273+
op.layout.ty,
1274+
&fn_abi,
1275+
));
1276+
llargs.push(data_ptr.llval);
1277+
continue;
1278+
}
1279+
_ => {
1280+
span_bug!(span, "can't codegen a virtual call on {:#?}", op);
1281+
}
1282+
}
1283+
}
1284+
1285+
// The callee needs to own the argument memory if we pass it
1286+
// by-ref, so make a local copy of non-immediate constants.
1287+
match (arg, op.val) {
1288+
(&mir::Operand::Copy(_), Ref(_, None, _))
1289+
| (&mir::Operand::Constant(_), Ref(_, None, _)) => {
1290+
let tmp = PlaceRef::alloca(bx, op.layout);
1291+
bx.lifetime_start(tmp.llval, tmp.layout.size);
1292+
op.val.store(bx, tmp);
1293+
op.val = Ref(tmp.llval, None, tmp.align);
1294+
copied_constant_arguments.push(tmp);
1295+
}
1296+
_ => {}
1297+
}
1298+
1299+
self.codegen_argument(bx, op, &mut llargs, &fn_abi.args[i]);
1300+
}
1301+
let num_untupled = untuple.map(|tup| {
1302+
self.codegen_arguments_untupled(bx, tup, &mut llargs, &fn_abi.args[first_args.len()..])
1303+
});
1304+
1305+
let needs_location =
1306+
instance.map_or(false, |i| i.def.requires_caller_location(self.cx.tcx()));
1307+
if needs_location {
1308+
let mir_args = if let Some(num_untupled) = num_untupled {
1309+
first_args.len() + num_untupled
1310+
} else {
1311+
args.len()
1312+
};
1313+
assert_eq!(
1314+
fn_abi.args.len(),
1315+
mir_args + 1,
1316+
"#[track_caller] fn's must have 1 more argument in their ABI than in their MIR: {:?} {:?} {:?}",
1317+
instance,
1318+
fn_span,
1319+
fn_abi,
1320+
);
1321+
let location =
1322+
self.get_caller_location(bx, mir::SourceInfo { span: fn_span, ..source_info });
1323+
debug!(
1324+
"codegen_tail_call_terminator({:?}): location={:?} (fn_span {:?})",
1325+
terminator, location, fn_span
1326+
);
1327+
1328+
let last_arg = fn_abi.args.last().unwrap();
1329+
self.codegen_argument(bx, location, &mut llargs, last_arg);
1330+
}
1331+
1332+
let fn_ptr = match (instance, llfn) {
1333+
(Some(instance), None) => bx.get_fn_addr(instance),
1334+
(_, Some(llfn)) => llfn,
1335+
_ => span_bug!(span, "no instance or llfn for tail-call"),
1336+
};
1337+
1338+
helper.do_tail_call(self, bx, fn_abi, fn_ptr, &llargs, &copied_constant_arguments);
1339+
}
1340+
10801341
fn codegen_asm_terminator(
10811342
&mut self,
10821343
helper: TerminatorCodegenHelper<'tcx>,
@@ -1295,12 +1556,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
12951556
mergeable_succ(),
12961557
),
12971558

1298-
mir::TerminatorKind::TailCall { .. } => {
1299-
// FIXME(explicit_tail_calls): implement tail calls in ssa backend
1300-
span_bug!(
1301-
terminator.source_info.span,
1302-
"`TailCall` terminator is not yet supported by `rustc_codegen_ssa`"
1303-
)
1559+
mir::TerminatorKind::TailCall { ref func, ref args, fn_span } => {
1560+
self.codegen_tail_call_terminator(helper, bx, terminator, func, args, fn_span);
1561+
MergingSucc::False
13041562
}
13051563

13061564
mir::TerminatorKind::GeneratorDrop | mir::TerminatorKind::Yield { .. } => {

‎compiler/rustc_codegen_ssa/src/traits/builder.rs

+11
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,17 @@ pub trait BuilderMethods<'a, 'tcx>:
330330
args: &[Self::Value],
331331
funclet: Option<&Self::Funclet>,
332332
) -> Self::Value;
333+
334+
fn tail_call(
335+
&mut self,
336+
llty: Self::Type,
337+
fn_attrs: Option<&CodegenFnAttrs>,
338+
fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
339+
llfn: Self::Value,
340+
args: &[Self::Value],
341+
funclet: Option<&Self::Funclet>,
342+
);
343+
333344
fn zext(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
334345

335346
fn do_not_inline(&mut self, llret: Self::Value);
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Failed to load comments.