Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
177dc5a
Clearer security warnings in std::env::current_exe docs
sourcefrog Jan 8, 2026
8a021d9
rustc_public: rewrite `bridge_impl` to reduce boilerplate
makai410 Feb 15, 2026
945caf3
rustc_public: remove the `CrateDefItems` trait
makai410 Feb 15, 2026
4b7a05e
Stop using `LinkedGraph` in `lexical_region_resolve`
Zalathar Feb 23, 2026
a226650
Remove mutation from macro path URL construction in generate_macro_de…
arferreira Feb 26, 2026
d79efc5
Recover feature lang_items for emscripten
mu001999 Feb 26, 2026
dfd18b7
Print path root when printing path
mu001999 Feb 26, 2026
9092262
fix: mem::conjure_zst panic message to use any::type_name instead of …
mehdiakiki Feb 25, 2026
882ae15
Work around a false `err.emit();` type error in rust-analyzer
Zalathar Feb 27, 2026
31ae3d2
guaranteed tail calls: support indirect arguments
folkertdev Jan 14, 2026
e6cf5a2
test u128 passing on linux and windows
folkertdev Jan 17, 2026
ec7c117
bootstrap: force a CI LLVM stamp bump
jieyouxu Feb 27, 2026
c6d028c
Clarify a confusing green-path function
Zalathar Feb 27, 2026
df774da
Rollup merge of #151143 - folkertdev:tail-call-indirect, r=WaffleLapkin
JonathanBrouwer Feb 27, 2026
91c9c93
Rollup merge of #153012 - Zalathar:lexical, r=petrochenkov
JonathanBrouwer Feb 27, 2026
92455c1
Rollup merge of #153175 - Zalathar:load-green, r=petrochenkov
JonathanBrouwer Feb 27, 2026
e1dbf5a
Rollup merge of #153179 - jieyouxu:force-ci-llvm-stamp, r=madsmtm
JonathanBrouwer Feb 27, 2026
9417f03
Rollup merge of #150828 - sourcefrog:doc-exe-security, r=cuviper
JonathanBrouwer Feb 27, 2026
afbd7e6
Rollup merge of #152673 - makai410:rpub/bridge-impl, r=celinval
JonathanBrouwer Feb 27, 2026
04b39a7
Rollup merge of #152674 - makai410:rpub/sus-trait, r=celinval
JonathanBrouwer Feb 27, 2026
1a4f321
Rollup merge of #153073 - mehdiakiki:fix/conjure-zst-panic-message, r…
JonathanBrouwer Feb 27, 2026
7d2bea0
Rollup merge of #153117 - arferreira:cleanup-macro-path-iter, r=Guill…
JonathanBrouwer Feb 27, 2026
d709823
Rollup merge of #153128 - mu001999-contrib:fix/features, r=joboet
JonathanBrouwer Feb 27, 2026
97e4139
Rollup merge of #153138 - mu001999-contrib:print-path-root, r=petroch…
JonathanBrouwer Feb 27, 2026
853ceef
Rollup merge of #153159 - Zalathar:emit-workaround, r=nnethercote
JonathanBrouwer Feb 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 105 additions & 19 deletions compiler/rustc_codegen_ssa/src/mir/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1146,19 +1146,51 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
(args, None)
};

// Special logic for tail calls with `PassMode::Indirect { on_stack: false, .. }` arguments.
//
// Normally an indirect argument with `on_stack: false` would be passed as a pointer into
// the caller's stack frame. For tail calls, that would be unsound, because the caller's
// stack frame is overwritten by the callee's stack frame.
//
// Therefore we store the argument for the callee in the corresponding caller's slot.
// Because guaranteed tail calls demand that the caller's signature matches the callee's,
// the corresponding slot has the correct type.
//
// To handle cases like the one below, the tail call arguments must first be copied to a
// temporary, and only then copied to the caller's argument slots.
//
// ```
// // A struct big enough that it is not passed via registers.
// pub struct Big([u64; 4]);
//
// fn swapper(a: Big, b: Big) -> (Big, Big) {
// become swapper_helper(b, a);
// }
// ```
let mut tail_call_temporaries = vec![];
if kind == CallKind::Tail {
tail_call_temporaries = vec![None; first_args.len()];
// Copy the arguments that use `PassMode::Indirect { on_stack: false , ..}`
// to temporary stack allocations. See the comment above.
for (i, arg) in first_args.iter().enumerate() {
if !matches!(fn_abi.args[i].mode, PassMode::Indirect { on_stack: false, .. }) {
continue;
}

let op = self.codegen_operand(bx, &arg.node);
let tmp = PlaceRef::alloca(bx, op.layout);
bx.lifetime_start(tmp.val.llval, tmp.layout.size);
op.store_with_annotation(bx, tmp);

tail_call_temporaries[i] = Some(tmp);
}
}

// When generating arguments we sometimes introduce temporary allocations with lifetime
// that extend for the duration of a call. Keep track of those allocations and their sizes
// to generate `lifetime_end` when the call returns.
let mut lifetime_ends_after_call: Vec<(Bx::Value, Size)> = Vec::new();
'make_args: for (i, arg) in first_args.iter().enumerate() {
if kind == CallKind::Tail && matches!(fn_abi.args[i].mode, PassMode::Indirect { .. }) {
// FIXME: https://github.com/rust-lang/rust/pull/144232#discussion_r2218543841
span_bug!(
fn_span,
"arguments using PassMode::Indirect are currently not supported for tail calls"
);
}

let mut op = self.codegen_operand(bx, &arg.node);

if let (0, Some(ty::InstanceKind::Virtual(_, idx))) = (i, instance.map(|i| i.def)) {
Expand Down Expand Up @@ -1209,18 +1241,72 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
}
}

// The callee needs to own the argument memory if we pass it
// by-ref, so make a local copy of non-immediate constants.
match (&arg.node, op.val) {
(&mir::Operand::Copy(_), Ref(PlaceValue { llextra: None, .. }))
| (&mir::Operand::Constant(_), Ref(PlaceValue { llextra: None, .. })) => {
let tmp = PlaceRef::alloca(bx, op.layout);
bx.lifetime_start(tmp.val.llval, tmp.layout.size);
op.store_with_annotation(bx, tmp);
op.val = Ref(tmp.val);
lifetime_ends_after_call.push((tmp.val.llval, tmp.layout.size));
match kind {
CallKind::Normal => {
// The callee needs to own the argument memory if we pass it
// by-ref, so make a local copy of non-immediate constants.
if let &mir::Operand::Copy(_) | &mir::Operand::Constant(_) = &arg.node
&& let Ref(PlaceValue { llextra: None, .. }) = op.val
{
let tmp = PlaceRef::alloca(bx, op.layout);
bx.lifetime_start(tmp.val.llval, tmp.layout.size);
op.store_with_annotation(bx, tmp);
op.val = Ref(tmp.val);
lifetime_ends_after_call.push((tmp.val.llval, tmp.layout.size));
}
}
CallKind::Tail => {
match fn_abi.args[i].mode {
PassMode::Indirect { on_stack: false, .. } => {
let Some(tmp) = tail_call_temporaries[i].take() else {
span_bug!(
fn_span,
"missing temporary for indirect tail call argument #{i}"
)
};

let local = self.mir.args_iter().nth(i).unwrap();

match &self.locals[local] {
LocalRef::Place(arg) => {
bx.typed_place_copy(arg.val, tmp.val, fn_abi.args[i].layout);
op.val = Ref(arg.val);
}
LocalRef::Operand(arg) => {
let Ref(place_value) = arg.val else {
bug!("only `Ref` should use `PassMode::Indirect`");
};
bx.typed_place_copy(
place_value,
tmp.val,
fn_abi.args[i].layout,
);
op.val = arg.val;
}
LocalRef::UnsizedPlace(_) => {
span_bug!(fn_span, "unsized types are not supported")
}
LocalRef::PendingOperand => {
span_bug!(fn_span, "argument local should not be pending")
}
};

bx.lifetime_end(tmp.val.llval, tmp.layout.size);
}
PassMode::Indirect { on_stack: true, .. } => {
// FIXME: some LLVM backends (notably x86) do not correctly pass byval
// arguments to tail calls (as of LLVM 21). See also:
//
// - https://github.com/rust-lang/rust/pull/144232#discussion_r2218543841
// - https://github.com/rust-lang/rust/issues/144855
span_bug!(
fn_span,
"arguments using PassMode::Indirect {{ on_stack: true, .. }} are currently not supported for tail calls"
)
}
_ => (),
}
}
_ => {}
}

self.codegen_argument(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use rustc_index::IndexVec;
use rustc_type_ir::RegionVid;

use crate::infer::SubregionOrigin;
use crate::infer::region_constraints::{Constraint, ConstraintKind, RegionConstraintData};

/// Selects either out-edges or in-edges for [`IndexedConstraintEdges::adjacent_edges`].
#[derive(Clone, Copy, Debug)]
pub(super) enum EdgeDirection {
Out,
In,
}

/// Type alias for the pairs stored in [`RegionConstraintData::constraints`],
/// which we are indexing.
type ConstraintPair<'tcx> = (Constraint<'tcx>, SubregionOrigin<'tcx>);

/// An index from region variables to their corresponding constraint edges,
/// used on some error paths.
pub(super) struct IndexedConstraintEdges<'data, 'tcx> {
out_edges: IndexVec<RegionVid, Vec<&'data ConstraintPair<'tcx>>>,
in_edges: IndexVec<RegionVid, Vec<&'data ConstraintPair<'tcx>>>,
}

impl<'data, 'tcx> IndexedConstraintEdges<'data, 'tcx> {
pub(super) fn build_index(num_vars: usize, data: &'data RegionConstraintData<'tcx>) -> Self {
let mut out_edges = IndexVec::from_fn_n(|_| vec![], num_vars);
let mut in_edges = IndexVec::from_fn_n(|_| vec![], num_vars);

for pair @ (c, _) in &data.constraints {
// Only push a var out-edge for `VarSub...` constraints.
match c.kind {
ConstraintKind::VarSubVar | ConstraintKind::VarSubReg => {
out_edges[c.sub.as_var()].push(pair)
}
ConstraintKind::RegSubVar | ConstraintKind::RegSubReg => {}
}
}

// Index in-edges in reverse order, to match what current tests expect.
// (It's unclear whether this is important or not.)
for pair @ (c, _) in data.constraints.iter().rev() {
// Only push a var in-edge for `...SubVar` constraints.
match c.kind {
ConstraintKind::VarSubVar | ConstraintKind::RegSubVar => {
in_edges[c.sup.as_var()].push(pair)
}
ConstraintKind::VarSubReg | ConstraintKind::RegSubReg => {}
}
}

IndexedConstraintEdges { out_edges, in_edges }
}

/// Returns either the out-edges or in-edges of the specified region var,
/// as selected by `dir`.
pub(super) fn adjacent_edges(
&self,
region_vid: RegionVid,
dir: EdgeDirection,
) -> &[&'data ConstraintPair<'tcx>] {
let edges = match dir {
EdgeDirection::Out => &self.out_edges,
EdgeDirection::In => &self.in_edges,
};
&edges[region_vid]
}
}
Loading