mirror of
https://github.com/rust-lang/rust.git
synced 2026-04-27 18:57:42 +03:00
Auto merge of #153183 - JonathanBrouwer:rollup-APFHc2s, r=JonathanBrouwer
Rollup of 12 pull requests Successful merges: - rust-lang/rust#151143 (explicit tail calls: support indirect arguments) - rust-lang/rust#153012 (Stop using `LinkedGraph` in `lexical_region_resolve`) - rust-lang/rust#153175 (Clarify a confusing green-path function) - rust-lang/rust#153179 (Force a CI LLVM stamp bump) - rust-lang/rust#150828 (Improved security section in rustdoc for `current_exe`) - rust-lang/rust#152673 (rustc_public: rewrite `bridge_impl` to reduce boilerplate) - rust-lang/rust#152674 (rustc_public: remove the `CrateDefItems` trait) - rust-lang/rust#153073 (Fix mem::conjure_zst panic message to use any::type_name instead) - rust-lang/rust#153117 (Remove mutation from macro path URL construction) - rust-lang/rust#153128 (Recover feature lang_items for emscripten) - rust-lang/rust#153138 (Print path root when printing path) - rust-lang/rust#153159 (Work around a false `err.emit();` type error in rust-analyzer)
This commit is contained in:
@@ -1146,19 +1146,51 @@ fn codegen_call_terminator(
|
||||
(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)) {
|
||||
@@ -1209,18 +1241,72 @@ fn codegen_call_terminator(
|
||||
}
|
||||
}
|
||||
|
||||
// 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(
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,6 @@
|
||||
use std::fmt;
|
||||
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_data_structures::graph::linked_graph::{
|
||||
Direction, INCOMING, LinkedGraph, NodeIndex, OUTGOING,
|
||||
};
|
||||
use rustc_data_structures::intern::Interned;
|
||||
use rustc_data_structures::unord::UnordSet;
|
||||
use rustc_index::{IndexSlice, IndexVec};
|
||||
@@ -18,11 +15,14 @@
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
use super::outlives::test_type_match;
|
||||
use crate::infer::lexical_region_resolve::indexed_edges::{EdgeDirection, IndexedConstraintEdges};
|
||||
use crate::infer::region_constraints::{
|
||||
Constraint, ConstraintKind, GenericKind, RegionConstraintData, VarInfos, VerifyBound,
|
||||
ConstraintKind, GenericKind, RegionConstraintData, VarInfos, VerifyBound,
|
||||
};
|
||||
use crate::infer::{RegionRelations, RegionVariableOrigin, SubregionOrigin};
|
||||
|
||||
mod indexed_edges;
|
||||
|
||||
/// This function performs lexical region resolution given a complete
|
||||
/// set of constraints and variable origins. It performs a fixed-point
|
||||
/// iteration to find region values which satisfy all constraints,
|
||||
@@ -118,8 +118,6 @@ struct RegionAndOrigin<'tcx> {
|
||||
origin: SubregionOrigin<'tcx>,
|
||||
}
|
||||
|
||||
type RegionGraph<'tcx> = LinkedGraph<(), Constraint<'tcx>>;
|
||||
|
||||
struct LexicalResolver<'cx, 'tcx> {
|
||||
region_rels: &'cx RegionRelations<'cx, 'tcx>,
|
||||
var_infos: VarInfos<'tcx>,
|
||||
@@ -626,9 +624,8 @@ fn collect_var_errors(
|
||||
// overlapping locations.
|
||||
let mut dup_vec = IndexVec::from_elem_n(None, self.num_vars());
|
||||
|
||||
// Only construct the graph when necessary, because it's moderately
|
||||
// expensive.
|
||||
let mut graph = None;
|
||||
// Only construct the edge index when necessary, because it's moderately expensive.
|
||||
let mut edges: Option<IndexedConstraintEdges<'_, 'tcx>> = None;
|
||||
|
||||
for (node_vid, value) in var_data.values.iter_enumerated() {
|
||||
match *value {
|
||||
@@ -662,56 +659,18 @@ fn collect_var_errors(
|
||||
// influence the constraints on this value for
|
||||
// richer diagnostics in `static_impl_trait`.
|
||||
|
||||
let g = graph.get_or_insert_with(|| self.construct_graph());
|
||||
self.collect_error_for_expanding_node(g, &mut dup_vec, node_vid, errors);
|
||||
let e = edges.get_or_insert_with(|| {
|
||||
IndexedConstraintEdges::build_index(self.num_vars(), &self.data)
|
||||
});
|
||||
self.collect_error_for_expanding_node(e, &mut dup_vec, node_vid, errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn construct_graph(&self) -> RegionGraph<'tcx> {
|
||||
let num_vars = self.num_vars();
|
||||
|
||||
let mut graph = LinkedGraph::new();
|
||||
|
||||
for _ in 0..num_vars {
|
||||
graph.add_node(());
|
||||
}
|
||||
|
||||
// Issue #30438: two distinct dummy nodes, one for incoming
|
||||
// edges (dummy_source) and another for outgoing edges
|
||||
// (dummy_sink). In `dummy -> a -> b -> dummy`, using one
|
||||
// dummy node leads one to think (erroneously) there exists a
|
||||
// path from `b` to `a`. Two dummy nodes sidesteps the issue.
|
||||
let dummy_source = graph.add_node(());
|
||||
let dummy_sink = graph.add_node(());
|
||||
|
||||
for (c, _) in &self.data.constraints {
|
||||
match c.kind {
|
||||
ConstraintKind::VarSubVar => {
|
||||
let sub_vid = c.sub.as_var();
|
||||
let sup_vid = c.sup.as_var();
|
||||
graph.add_edge(NodeIndex(sub_vid.index()), NodeIndex(sup_vid.index()), *c);
|
||||
}
|
||||
ConstraintKind::RegSubVar => {
|
||||
graph.add_edge(dummy_source, NodeIndex(c.sup.as_var().index()), *c);
|
||||
}
|
||||
ConstraintKind::VarSubReg => {
|
||||
graph.add_edge(NodeIndex(c.sub.as_var().index()), dummy_sink, *c);
|
||||
}
|
||||
ConstraintKind::RegSubReg => {
|
||||
// this would be an edge from `dummy_source` to
|
||||
// `dummy_sink`; just ignore it.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
graph
|
||||
}
|
||||
|
||||
fn collect_error_for_expanding_node(
|
||||
&self,
|
||||
graph: &RegionGraph<'tcx>,
|
||||
edges: &IndexedConstraintEdges<'_, 'tcx>,
|
||||
dup_vec: &mut IndexSlice<RegionVid, Option<RegionVid>>,
|
||||
node_idx: RegionVid,
|
||||
errors: &mut Vec<RegionResolutionError<'tcx>>,
|
||||
@@ -719,9 +678,9 @@ fn collect_error_for_expanding_node(
|
||||
// Errors in expanding nodes result from a lower-bound that is
|
||||
// not contained by an upper-bound.
|
||||
let (mut lower_bounds, lower_vid_bounds, lower_dup) =
|
||||
self.collect_bounding_regions(graph, node_idx, INCOMING, Some(dup_vec));
|
||||
self.collect_bounding_regions(edges, node_idx, EdgeDirection::In, Some(dup_vec));
|
||||
let (mut upper_bounds, _, upper_dup) =
|
||||
self.collect_bounding_regions(graph, node_idx, OUTGOING, Some(dup_vec));
|
||||
self.collect_bounding_regions(edges, node_idx, EdgeDirection::Out, Some(dup_vec));
|
||||
|
||||
if lower_dup || upper_dup {
|
||||
return;
|
||||
@@ -829,9 +788,9 @@ fn region_order_key(x: &RegionAndOrigin<'_>) -> u8 {
|
||||
/// those returned by a previous call for another region.
|
||||
fn collect_bounding_regions(
|
||||
&self,
|
||||
graph: &RegionGraph<'tcx>,
|
||||
edges: &IndexedConstraintEdges<'_, 'tcx>,
|
||||
orig_node_idx: RegionVid,
|
||||
dir: Direction,
|
||||
dir: EdgeDirection,
|
||||
mut dup_vec: Option<&mut IndexSlice<RegionVid, Option<RegionVid>>>,
|
||||
) -> (Vec<RegionAndOrigin<'tcx>>, FxHashSet<RegionVid>, bool) {
|
||||
struct WalkState<'tcx> {
|
||||
@@ -850,7 +809,7 @@ struct WalkState<'tcx> {
|
||||
|
||||
// to start off the process, walk the source node in the
|
||||
// direction specified
|
||||
process_edges(&self.data, &mut state, graph, orig_node_idx, dir);
|
||||
process_edges(&mut state, edges, orig_node_idx, dir);
|
||||
|
||||
while let Some(node_idx) = state.stack.pop() {
|
||||
// check whether we've visited this node on some previous walk
|
||||
@@ -867,30 +826,25 @@ struct WalkState<'tcx> {
|
||||
);
|
||||
}
|
||||
|
||||
process_edges(&self.data, &mut state, graph, node_idx, dir);
|
||||
process_edges(&mut state, edges, node_idx, dir);
|
||||
}
|
||||
|
||||
let WalkState { result, dup_found, set, .. } = state;
|
||||
return (result, set, dup_found);
|
||||
|
||||
fn process_edges<'tcx>(
|
||||
this: &RegionConstraintData<'tcx>,
|
||||
state: &mut WalkState<'tcx>,
|
||||
graph: &RegionGraph<'tcx>,
|
||||
edges: &IndexedConstraintEdges<'_, 'tcx>,
|
||||
source_vid: RegionVid,
|
||||
dir: Direction,
|
||||
dir: EdgeDirection,
|
||||
) {
|
||||
debug!("process_edges(source_vid={:?}, dir={:?})", source_vid, dir);
|
||||
|
||||
let source_node_index = NodeIndex(source_vid.index());
|
||||
for (_, edge) in graph.adjacent_edges(source_node_index, dir) {
|
||||
let get_origin =
|
||||
|| this.constraints.iter().find(|(c, _)| *c == edge.data).unwrap().1.clone();
|
||||
|
||||
match edge.data.kind {
|
||||
for (c, origin) in edges.adjacent_edges(source_vid, dir) {
|
||||
match c.kind {
|
||||
ConstraintKind::VarSubVar => {
|
||||
let from_vid = edge.data.sub.as_var();
|
||||
let to_vid = edge.data.sup.as_var();
|
||||
let from_vid = c.sub.as_var();
|
||||
let to_vid = c.sup.as_var();
|
||||
let opp_vid = if from_vid == source_vid { to_vid } else { from_vid };
|
||||
if state.set.insert(opp_vid) {
|
||||
state.stack.push(opp_vid);
|
||||
@@ -898,13 +852,13 @@ fn process_edges<'tcx>(
|
||||
}
|
||||
|
||||
ConstraintKind::RegSubVar => {
|
||||
let origin = get_origin();
|
||||
state.result.push(RegionAndOrigin { region: edge.data.sub, origin });
|
||||
let origin = origin.clone();
|
||||
state.result.push(RegionAndOrigin { region: c.sub, origin });
|
||||
}
|
||||
|
||||
ConstraintKind::VarSubReg => {
|
||||
let origin = get_origin();
|
||||
state.result.push(RegionAndOrigin { region: edge.data.sup, origin });
|
||||
let origin = origin.clone();
|
||||
state.result.push(RegionAndOrigin { region: c.sup, origin });
|
||||
}
|
||||
|
||||
ConstraintKind::RegSubReg => panic!(
|
||||
|
||||
@@ -300,7 +300,7 @@ pub(crate) fn run_in_thread_pool_with_globals<
|
||||
diag.help(
|
||||
"try lowering `-Z threads` or checking the operating system's resource limits",
|
||||
);
|
||||
diag.emit();
|
||||
diag.emit()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -121,17 +121,29 @@ fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location
|
||||
// `f` passes. Note that function arguments are the only situation in which this problem can
|
||||
// arise: every other use of `move` in MIR doesn't actually write to the value it moves
|
||||
// from.
|
||||
if let TerminatorKind::Call { ref args, .. } = terminator.kind {
|
||||
for arg in args {
|
||||
if let Operand::Move(place) = arg.node
|
||||
&& !place.is_indirect_first_projection()
|
||||
&& let Some(i) = self.as_param(place.local)
|
||||
{
|
||||
self.usage[i] |= UsageSummary::MUTATE;
|
||||
self.usage[i] |= UsageSummary::CAPTURE;
|
||||
match terminator.kind {
|
||||
TerminatorKind::Call { ref args, .. } => {
|
||||
for arg in args {
|
||||
if let Operand::Move(place) = arg.node
|
||||
&& !place.is_indirect_first_projection()
|
||||
&& let Some(i) = self.as_param(place.local)
|
||||
{
|
||||
self.usage[i] |= UsageSummary::MUTATE;
|
||||
self.usage[i] |= UsageSummary::CAPTURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Like a call, but more conservative because the backend may introduce writes to an
|
||||
// argument if the argument is passed as `PassMode::Indirect { on_stack: false, ... }`.
|
||||
TerminatorKind::TailCall { .. } => {
|
||||
for usage in self.usage.iter_mut() {
|
||||
*usage |= UsageSummary::MUTATE;
|
||||
*usage |= UsageSummary::CAPTURE;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.super_terminator(terminator, location);
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ pub fn new_parser_from_file<'a>(
|
||||
if let Some(sp) = sp {
|
||||
err.span(sp);
|
||||
}
|
||||
err.emit();
|
||||
err.emit()
|
||||
});
|
||||
new_parser_from_source_file(psess, source_file, strip_tokens)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! such as, a function, a trait, an enum, and any other definitions.
|
||||
|
||||
use crate::ty::{GenericArgs, Span, Ty, index_impl};
|
||||
use crate::{AssocItems, Crate, Symbol, ThreadLocalIndex, with};
|
||||
use crate::{Crate, Symbol, ThreadLocalIndex, with};
|
||||
|
||||
/// A unique identification number for each item accessible for the current compilation unit.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
@@ -111,14 +111,6 @@ fn ty_with_args(&self, args: &GenericArgs) -> Ty {
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for retrieving all items from a definition within a crate.
|
||||
pub trait CrateDefItems: CrateDef {
|
||||
/// Retrieve all associated items from a definition.
|
||||
fn associated_items(&self) -> AssocItems {
|
||||
with(|cx| cx.associated_items(self.def_id()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Attribute {
|
||||
value: String,
|
||||
@@ -174,9 +166,3 @@ fn def_id(&self) -> DefId {
|
||||
impl CrateDefType for $name {}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_crate_def_items {
|
||||
( $name:ident $(;)? ) => {
|
||||
impl CrateDefItems for $name {}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::compiler_interface::with;
|
||||
pub use crate::crate_def::{CrateDef, CrateDefItems, CrateDefType, DefId};
|
||||
pub use crate::crate_def::{CrateDef, CrateDefType, DefId};
|
||||
pub use crate::error::*;
|
||||
use crate::mir::mono::StaticDef;
|
||||
use crate::mir::{Body, Mutability};
|
||||
@@ -238,35 +238,40 @@ pub fn opaque<T: Debug>(value: &T) -> Opaque {
|
||||
}
|
||||
|
||||
macro_rules! bridge_impl {
|
||||
($name: ident, $ty: ty) => {
|
||||
impl rustc_public_bridge::bridge::$name<compiler_interface::BridgeTys> for $ty {
|
||||
fn new(def: crate::DefId) -> Self {
|
||||
Self(def)
|
||||
( $( $name:ident, $ty:ty ),* $(,)? ) => {
|
||||
$(
|
||||
impl rustc_public_bridge::bridge::$name<compiler_interface::BridgeTys> for $ty {
|
||||
fn new(def: crate::DefId) -> Self {
|
||||
Self(def)
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
bridge_impl!(CrateItem, crate::CrateItem);
|
||||
bridge_impl!(AdtDef, crate::ty::AdtDef);
|
||||
bridge_impl!(ForeignModuleDef, crate::ty::ForeignModuleDef);
|
||||
bridge_impl!(ForeignDef, crate::ty::ForeignDef);
|
||||
bridge_impl!(FnDef, crate::ty::FnDef);
|
||||
bridge_impl!(ClosureDef, crate::ty::ClosureDef);
|
||||
bridge_impl!(CoroutineDef, crate::ty::CoroutineDef);
|
||||
bridge_impl!(CoroutineClosureDef, crate::ty::CoroutineClosureDef);
|
||||
bridge_impl!(AliasDef, crate::ty::AliasDef);
|
||||
bridge_impl!(ParamDef, crate::ty::ParamDef);
|
||||
bridge_impl!(BrNamedDef, crate::ty::BrNamedDef);
|
||||
bridge_impl!(TraitDef, crate::ty::TraitDef);
|
||||
bridge_impl!(GenericDef, crate::ty::GenericDef);
|
||||
bridge_impl!(ConstDef, crate::ty::ConstDef);
|
||||
bridge_impl!(ImplDef, crate::ty::ImplDef);
|
||||
bridge_impl!(RegionDef, crate::ty::RegionDef);
|
||||
bridge_impl!(CoroutineWitnessDef, crate::ty::CoroutineWitnessDef);
|
||||
bridge_impl!(AssocDef, crate::ty::AssocDef);
|
||||
bridge_impl!(OpaqueDef, crate::ty::OpaqueDef);
|
||||
bridge_impl!(StaticDef, crate::mir::mono::StaticDef);
|
||||
#[rustfmt::skip]
|
||||
bridge_impl!(
|
||||
CrateItem, crate::CrateItem,
|
||||
AdtDef, crate::ty::AdtDef,
|
||||
ForeignModuleDef, crate::ty::ForeignModuleDef,
|
||||
ForeignDef, crate::ty::ForeignDef,
|
||||
FnDef, crate::ty::FnDef,
|
||||
ClosureDef, crate::ty::ClosureDef,
|
||||
CoroutineDef, crate::ty::CoroutineDef,
|
||||
CoroutineClosureDef, crate::ty::CoroutineClosureDef,
|
||||
AliasDef, crate::ty::AliasDef,
|
||||
ParamDef, crate::ty::ParamDef,
|
||||
BrNamedDef, crate::ty::BrNamedDef,
|
||||
TraitDef, crate::ty::TraitDef,
|
||||
GenericDef, crate::ty::GenericDef,
|
||||
ConstDef, crate::ty::ConstDef,
|
||||
ImplDef, crate::ty::ImplDef,
|
||||
RegionDef, crate::ty::RegionDef,
|
||||
CoroutineWitnessDef, crate::ty::CoroutineWitnessDef,
|
||||
AssocDef, crate::ty::AssocDef,
|
||||
OpaqueDef, crate::ty::OpaqueDef,
|
||||
StaticDef, crate::mir::mono::StaticDef
|
||||
);
|
||||
|
||||
impl rustc_public_bridge::bridge::Prov<compiler_interface::BridgeTys> for crate::ty::Prov {
|
||||
fn new(aid: crate::mir::alloc::AllocId) -> Self {
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
use super::mir::{Body, Mutability, Safety};
|
||||
use super::{DefId, Error, Symbol, with};
|
||||
use crate::abi::{FnAbi, Layout};
|
||||
use crate::crate_def::{CrateDef, CrateDefItems, CrateDefType};
|
||||
use crate::crate_def::{CrateDef, CrateDefType};
|
||||
use crate::mir::alloc::{AllocId, read_target_int, read_target_uint};
|
||||
use crate::mir::mono::StaticDef;
|
||||
use crate::target::MachineInfo;
|
||||
use crate::{Filename, IndexedVal, Opaque, ThreadLocalIndex};
|
||||
use crate::{AssocItems, Filename, IndexedVal, Opaque, ThreadLocalIndex};
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Ty(usize, ThreadLocalIndex);
|
||||
@@ -943,14 +943,14 @@ pub fn is_union(&self) -> bool {
|
||||
pub TraitDef;
|
||||
}
|
||||
|
||||
impl_crate_def_items! {
|
||||
TraitDef;
|
||||
}
|
||||
|
||||
impl TraitDef {
|
||||
pub fn declaration(trait_def: &TraitDef) -> TraitDecl {
|
||||
with(|cx| cx.trait_decl(trait_def))
|
||||
}
|
||||
|
||||
pub fn associated_items(&self) -> AssocItems {
|
||||
with(|cx| cx.associated_items(self.def_id()))
|
||||
}
|
||||
}
|
||||
|
||||
crate_def! {
|
||||
@@ -969,15 +969,15 @@ pub fn declaration(trait_def: &TraitDef) -> TraitDecl {
|
||||
pub ImplDef;
|
||||
}
|
||||
|
||||
impl_crate_def_items! {
|
||||
ImplDef;
|
||||
}
|
||||
|
||||
impl ImplDef {
|
||||
/// Retrieve information about this implementation.
|
||||
pub fn trait_impl(&self) -> ImplTrait {
|
||||
with(|cx| cx.trait_impl(self))
|
||||
}
|
||||
|
||||
pub fn associated_items(&self) -> AssocItems {
|
||||
with(|cx| cx.associated_items(self.def_id()))
|
||||
}
|
||||
}
|
||||
|
||||
crate_def! {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
use rustc_data_structures::stack::ensure_sufficient_stack;
|
||||
use rustc_data_structures::{outline, sharded, sync};
|
||||
use rustc_errors::{Diag, FatalError, StashKey};
|
||||
use rustc_middle::dep_graph::{DepGraphData, DepNodeKey};
|
||||
use rustc_middle::dep_graph::{DepGraphData, DepNodeKey, SerializedDepNodeIndex};
|
||||
use rustc_middle::query::plumbing::QueryVTable;
|
||||
use rustc_middle::query::{
|
||||
ActiveKeyStatus, CycleError, CycleErrorHandling, EnsureMode, QueryCache, QueryJob, QueryJobId,
|
||||
@@ -455,8 +455,18 @@ fn execute_job_incr<'tcx, C: QueryCache>(
|
||||
|
||||
// The diagnostics for this query will be promoted to the current session during
|
||||
// `try_mark_green()`, so we can ignore them here.
|
||||
if let Some(ret) = start_query(tcx, job_id, false, || {
|
||||
try_load_from_disk_and_cache_in_memory(query, dep_graph_data, tcx, &key, dep_node)
|
||||
if let Some(ret) = start_query(tcx, job_id, false, || try {
|
||||
let (prev_index, dep_node_index) = dep_graph_data.try_mark_green(tcx, dep_node)?;
|
||||
let value = load_from_disk_or_invoke_provider_green(
|
||||
tcx,
|
||||
dep_graph_data,
|
||||
query,
|
||||
&key,
|
||||
dep_node,
|
||||
prev_index,
|
||||
dep_node_index,
|
||||
);
|
||||
(value, dep_node_index)
|
||||
}) {
|
||||
return ret;
|
||||
}
|
||||
@@ -490,29 +500,32 @@ fn execute_job_incr<'tcx, C: QueryCache>(
|
||||
(result, dep_node_index)
|
||||
}
|
||||
|
||||
/// Given that the dep node for this query+key is green, obtain a value for it
|
||||
/// by loading one from disk if possible, or by invoking its query provider if
|
||||
/// necessary.
|
||||
#[inline(always)]
|
||||
fn try_load_from_disk_and_cache_in_memory<'tcx, C: QueryCache>(
|
||||
query: &'tcx QueryVTable<'tcx, C>,
|
||||
dep_graph_data: &DepGraphData,
|
||||
fn load_from_disk_or_invoke_provider_green<'tcx, C: QueryCache>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
dep_graph_data: &DepGraphData,
|
||||
query: &'tcx QueryVTable<'tcx, C>,
|
||||
key: &C::Key,
|
||||
dep_node: &DepNode,
|
||||
) -> Option<(C::Value, DepNodeIndex)> {
|
||||
prev_index: SerializedDepNodeIndex,
|
||||
dep_node_index: DepNodeIndex,
|
||||
) -> C::Value {
|
||||
// Note this function can be called concurrently from the same query
|
||||
// We must ensure that this is handled correctly.
|
||||
|
||||
let (prev_dep_node_index, dep_node_index) = dep_graph_data.try_mark_green(tcx, dep_node)?;
|
||||
|
||||
debug_assert!(dep_graph_data.is_index_green(prev_dep_node_index));
|
||||
debug_assert!(dep_graph_data.is_index_green(prev_index));
|
||||
|
||||
// First we try to load the result from the on-disk cache.
|
||||
// Some things are never cached on disk.
|
||||
if let Some(result) = query.try_load_from_disk(tcx, key, prev_dep_node_index, dep_node_index) {
|
||||
if let Some(value) = query.try_load_from_disk(tcx, key, prev_index, dep_node_index) {
|
||||
if std::intrinsics::unlikely(tcx.sess.opts.unstable_opts.query_dep_graph) {
|
||||
dep_graph_data.mark_debug_loaded_from_disk(*dep_node)
|
||||
}
|
||||
|
||||
let prev_fingerprint = dep_graph_data.prev_value_fingerprint_of(prev_dep_node_index);
|
||||
let prev_fingerprint = dep_graph_data.prev_value_fingerprint_of(prev_index);
|
||||
// If `-Zincremental-verify-ich` is specified, re-hash results from
|
||||
// the cache and make sure that they have the expected fingerprint.
|
||||
//
|
||||
@@ -527,14 +540,14 @@ fn try_load_from_disk_and_cache_in_memory<'tcx, C: QueryCache>(
|
||||
incremental_verify_ich(
|
||||
tcx,
|
||||
dep_graph_data,
|
||||
&result,
|
||||
prev_dep_node_index,
|
||||
&value,
|
||||
prev_index,
|
||||
query.hash_result,
|
||||
query.format_value,
|
||||
);
|
||||
}
|
||||
|
||||
return Some((result, dep_node_index));
|
||||
return value;
|
||||
}
|
||||
|
||||
// We always expect to find a cached result for things that
|
||||
@@ -548,7 +561,7 @@ fn try_load_from_disk_and_cache_in_memory<'tcx, C: QueryCache>(
|
||||
// Sanity check for the logic in `ensure`: if the node is green and the result loadable,
|
||||
// we should actually be able to load it.
|
||||
debug_assert!(
|
||||
!query.is_loadable_from_disk(tcx, key, prev_dep_node_index),
|
||||
!query.is_loadable_from_disk(tcx, key, prev_index),
|
||||
"missing on-disk cache entry for loadable {dep_node:?}"
|
||||
);
|
||||
|
||||
@@ -558,7 +571,7 @@ fn try_load_from_disk_and_cache_in_memory<'tcx, C: QueryCache>(
|
||||
|
||||
// The dep-graph for this computation is already in-place.
|
||||
// Call the query provider.
|
||||
let result = tcx.dep_graph.with_ignore(|| (query.invoke_provider_fn)(tcx, *key));
|
||||
let value = tcx.dep_graph.with_ignore(|| (query.invoke_provider_fn)(tcx, *key));
|
||||
|
||||
prof_timer.finish_with_query_invocation_id(dep_node_index.into());
|
||||
|
||||
@@ -574,13 +587,13 @@ fn try_load_from_disk_and_cache_in_memory<'tcx, C: QueryCache>(
|
||||
incremental_verify_ich(
|
||||
tcx,
|
||||
dep_graph_data,
|
||||
&result,
|
||||
prev_dep_node_index,
|
||||
&value,
|
||||
prev_index,
|
||||
query.hash_result,
|
||||
query.format_value,
|
||||
);
|
||||
|
||||
Some((result, dep_node_index))
|
||||
value
|
||||
}
|
||||
|
||||
/// Return value struct for [`check_if_ensure_can_skip_execution`].
|
||||
|
||||
@@ -2504,7 +2504,7 @@ fn resolve_main(&mut self) {
|
||||
|
||||
fn names_to_string(names: impl Iterator<Item = Symbol>) -> String {
|
||||
let mut result = String::new();
|
||||
for (i, name) in names.filter(|name| *name != kw::PathRoot).enumerate() {
|
||||
for (i, name) in names.enumerate().filter(|(_, name)| *name != kw::PathRoot) {
|
||||
if i > 0 {
|
||||
result.push_str("::");
|
||||
}
|
||||
|
||||
@@ -1615,7 +1615,7 @@ pub fn build_target_config(
|
||||
let mut err =
|
||||
early_dcx.early_struct_fatal(format!("error loading target specification: {e}"));
|
||||
err.help("run `rustc --print target-list` for a list of built-in targets");
|
||||
err.emit();
|
||||
err.emit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1488,12 +1488,13 @@ impl<T> SizedTypeProperties for T {}
|
||||
///
|
||||
/// [inhabited]: https://doc.rust-lang.org/reference/glossary.html#inhabited
|
||||
#[unstable(feature = "mem_conjure_zst", issue = "95383")]
|
||||
#[rustc_const_unstable(feature = "mem_conjure_zst", issue = "95383")]
|
||||
pub const unsafe fn conjure_zst<T>() -> T {
|
||||
const_assert!(
|
||||
size_of::<T>() == 0,
|
||||
"mem::conjure_zst invoked on a nonzero-sized type",
|
||||
"mem::conjure_zst invoked on type {t}, which is not zero-sized",
|
||||
t: &str = stringify!(T)
|
||||
"mem::conjure_zst invoked on a non-zero-sized type",
|
||||
"mem::conjure_zst invoked on type {name}, which is not zero-sized",
|
||||
name: &str = crate::any::type_name::<T>()
|
||||
);
|
||||
|
||||
// SAFETY: because the caller must guarantee that it's inhabited and zero-sized,
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#![no_std]
|
||||
#![unstable(feature = "panic_unwind", issue = "32837")]
|
||||
#![doc(issue_tracker_base_url = "https://github.com/rust-lang/rust/issues/")]
|
||||
#![cfg_attr(all(target_os = "emscripten", not(emscripten_wasm_eh)), lang_items)]
|
||||
#![feature(cfg_emscripten_wasm_eh)]
|
||||
#![feature(core_intrinsics)]
|
||||
#![feature(panic_unwind)]
|
||||
|
||||
+12
-19
@@ -712,28 +712,21 @@ pub fn temp_dir() -> PathBuf {
|
||||
///
|
||||
/// # Security
|
||||
///
|
||||
/// The output of this function should not be trusted for anything
|
||||
/// that might have security implications. Basically, if users can run
|
||||
/// the executable, they can change the output arbitrarily.
|
||||
/// The output of this function must be treated with care to avoid security
|
||||
/// vulnerabilities, particularly in processes that run with privileges higher
|
||||
/// than the user, such as setuid or setgid programs.
|
||||
///
|
||||
/// As an example, you can easily introduce a race condition. It goes
|
||||
/// like this:
|
||||
/// For example, on some Unix platforms, the result is calculated by
|
||||
/// searching `$PATH` for an executable matching `argv[0]`, but both the
|
||||
/// environment and arguments can be be set arbitrarily by the user who
|
||||
/// invokes the program.
|
||||
///
|
||||
/// 1. You get the path to the current executable using `current_exe()`, and
|
||||
/// store it in a variable.
|
||||
/// 2. Time passes. A malicious actor removes the current executable, and
|
||||
/// replaces it with a malicious one.
|
||||
/// 3. You then use the stored path to re-execute the current
|
||||
/// executable.
|
||||
/// On Linux, if `fs.secure_hardlinks` is not set, an attacker who can
|
||||
/// create hardlinks to the executable may be able to cause this function
|
||||
/// to return an attacker-controlled path, which they later replace with
|
||||
/// a different program.
|
||||
///
|
||||
/// You expected to safely execute the current executable, but you're
|
||||
/// instead executing something completely different. The code you
|
||||
/// just executed runs with your privileges.
|
||||
///
|
||||
/// This sort of behavior has been known to [lead to privilege escalation] when
|
||||
/// used incorrectly.
|
||||
///
|
||||
/// [lead to privilege escalation]: https://securityvulns.com/Wdocument183.html
|
||||
/// This list of illustrative example attacks is not exhaustive.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Change this file to make users of the `download-ci-llvm` configuration download
|
||||
a new version of LLVM from CI, even if the LLVM submodule hasn’t changed.
|
||||
|
||||
Last change is for: https://github.com/rust-lang/rust/pull/147935
|
||||
Last change is for: https://github.com/rust-lang/rust/pull/153179
|
||||
|
||||
@@ -386,33 +386,32 @@ fn generate_macro_def_id_path(
|
||||
} else {
|
||||
ItemType::Macro
|
||||
};
|
||||
let mut path = clean::inline::get_item_path(tcx, def_id, item_type);
|
||||
if path.len() < 2 {
|
||||
// The minimum we can have is the crate name followed by the macro name. If shorter, then
|
||||
// it means that `relative` was empty, which is an error.
|
||||
debug!("macro path cannot be empty!");
|
||||
let path = clean::inline::get_item_path(tcx, def_id, item_type);
|
||||
// The minimum we can have is the crate name followed by the macro name. If shorter, then
|
||||
// it means that `relative` was empty, which is an error.
|
||||
let [module_path @ .., last] = path.as_slice() else {
|
||||
debug!("macro path is empty!");
|
||||
return Err(HrefError::NotInExternalCache);
|
||||
};
|
||||
if module_path.is_empty() {
|
||||
debug!("macro path too short: missing crate prefix (got 1 element, need at least 2)");
|
||||
return Err(HrefError::NotInExternalCache);
|
||||
}
|
||||
|
||||
// FIXME: Try to use `iter().chain().once()` instead.
|
||||
let mut prev = None;
|
||||
if let Some(last) = path.pop() {
|
||||
path.push(Symbol::intern(&format!("{}.{last}.html", item_type.as_str())));
|
||||
prev = Some(last);
|
||||
}
|
||||
|
||||
let url = match cache.extern_locations[&def_id.krate] {
|
||||
ExternalLocation::Remote { ref url, is_absolute } => {
|
||||
let mut prefix = remote_url_prefix(url, is_absolute, cx.current.len());
|
||||
prefix.extend(path.iter().copied());
|
||||
prefix.extend(module_path.iter().copied());
|
||||
prefix.push_fmt(format_args!("{}.{last}.html", item_type.as_str()));
|
||||
prefix.finish()
|
||||
}
|
||||
ExternalLocation::Local => {
|
||||
// `root_path` always end with a `/`.
|
||||
format!(
|
||||
"{root_path}{path}",
|
||||
"{root_path}{path}/{item_type}.{last}.html",
|
||||
root_path = root_path.unwrap_or(""),
|
||||
path = fmt::from_fn(|f| path.iter().joined("/", f))
|
||||
path = fmt::from_fn(|f| module_path.iter().joined("/", f)),
|
||||
item_type = item_type.as_str(),
|
||||
)
|
||||
}
|
||||
ExternalLocation::Unknown => {
|
||||
@@ -420,10 +419,6 @@ fn generate_macro_def_id_path(
|
||||
return Err(HrefError::NotInExternalCache);
|
||||
}
|
||||
};
|
||||
if let Some(prev) = prev {
|
||||
path.pop();
|
||||
path.push(prev);
|
||||
}
|
||||
Ok(HrefInfo { url, kind: item_type, rust_path: path })
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
//@ add-minicore
|
||||
//@ min-llvm-version: 22
|
||||
//@ assembly-output: emit-asm
|
||||
//@ needs-llvm-components: x86
|
||||
//@ compile-flags: --target=x86_64-unknown-linux-gnu
|
||||
//@ compile-flags: -Copt-level=3 -C llvm-args=-x86-asm-syntax=intel
|
||||
|
||||
#![feature(no_core, explicit_tail_calls)]
|
||||
#![expect(incomplete_features)]
|
||||
#![no_core]
|
||||
#![crate_type = "lib"]
|
||||
|
||||
// Test tail calls with `PassMode::Indirect { on_stack: false, .. }` arguments.
|
||||
//
|
||||
// Normally an indirect argument with `on_stack: false` would be passed as a pointer to 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.
|
||||
//
|
||||
// The solution is to write the argument into the caller's argument place (stored somewhere further
|
||||
// up the stack), and forward that place.
|
||||
|
||||
extern crate minicore;
|
||||
use minicore::*;
|
||||
|
||||
#[repr(C)]
|
||||
struct S {
|
||||
x: u64,
|
||||
y: u64,
|
||||
z: u64,
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
safe fn force_usage(_: u64, _: u64, _: u64) -> u64;
|
||||
}
|
||||
|
||||
// CHECK-LABEL: callee:
|
||||
// CHECK-NEXT: .cfi_startproc
|
||||
//
|
||||
// CHECK-NEXT: mov rax, qword ptr [rdi]
|
||||
// CHECK-NEXT: mov rsi, qword ptr [rdi + 8]
|
||||
// CHECK-NEXT: mov rdx, qword ptr [rdi + 16]
|
||||
// CHECK-NEXT: mov rdi, rax
|
||||
//
|
||||
// CHECK-NEXT: jmp qword ptr [rip + force_usage@GOTPCREL]
|
||||
#[inline(never)]
|
||||
#[unsafe(no_mangle)]
|
||||
fn callee(s: S) -> u64 {
|
||||
force_usage(s.x, s.y, s.z)
|
||||
}
|
||||
|
||||
// CHECK-LABEL: caller1:
|
||||
// CHECK-NEXT: .cfi_startproc
|
||||
//
|
||||
// Just forward the argument:
|
||||
//
|
||||
// CHECK-NEXT: jmp qword ptr [rip + callee@GOTPCREL]
|
||||
#[unsafe(no_mangle)]
|
||||
fn caller1(s: S) -> u64 {
|
||||
become callee(s);
|
||||
}
|
||||
|
||||
// CHECK-LABEL: caller2:
|
||||
// CHECK-NEXT: .cfi_startproc
|
||||
//
|
||||
// Construct the S value directly into the argument slot:
|
||||
//
|
||||
// CHECK-NEXT: mov qword ptr [rdi], 1
|
||||
// CHECK-NEXT: mov qword ptr [rdi + 8], 2
|
||||
// CHECK-NEXT: mov qword ptr [rdi + 16], 3
|
||||
//
|
||||
// CHECK-NEXT: jmp qword ptr [rip + callee@GOTPCREL]
|
||||
#[unsafe(no_mangle)]
|
||||
fn caller2(_: S) -> u64 {
|
||||
let s = S { x: 1, y: 2, z: 3 };
|
||||
become callee(s);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//@ add-minicore
|
||||
//@ revisions: win linux
|
||||
//@ min-llvm-version: 22
|
||||
//
|
||||
//@ compile-flags: -Copt-level=3
|
||||
//@[linux] compile-flags: --target x86_64-unknown-linux-gnu
|
||||
//@[linux] needs-llvm-components: x86
|
||||
//@[win] compile-flags: --target x86_64-pc-windows-msvc
|
||||
//@[win] needs-llvm-components: x86
|
||||
|
||||
#![crate_type = "lib"]
|
||||
#![feature(no_core, lang_items, explicit_tail_calls)]
|
||||
#![allow(incomplete_features)]
|
||||
#![no_core]
|
||||
|
||||
// Test passing of i128/u128, which is passed directly on x86, but indirectly on win64.
|
||||
|
||||
extern crate minicore;
|
||||
use minicore::*;
|
||||
|
||||
// linux: define noundef i128 @foo(i128 noundef %a, i128 noundef %b)
|
||||
// win: define <16 x i8> @foo(ptr {{.*}} %a, ptr {{.*}} %b)
|
||||
#[unsafe(no_mangle)]
|
||||
#[inline(never)]
|
||||
extern "C" fn foo(a: u128, b: u128) -> u128 {
|
||||
// linux: start:
|
||||
// linux-NEXT: musttail call noundef i128 @bar(i128 noundef %b, i128 noundef %a)
|
||||
//
|
||||
//
|
||||
// win: start:
|
||||
// win-NEXT: %0 = load i128, ptr %b
|
||||
// win-NEXT: %1 = load i128, ptr %a
|
||||
// win-NEXT: store i128 %0, ptr %a
|
||||
// win-NEXT: store i128 %1, ptr %b
|
||||
// win-NEXT: musttail call <16 x i8> @bar(ptr {{.*}} %a, ptr {{.*}} %b)
|
||||
become bar(b, a)
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
safe fn bar(a: u128, b: u128) -> u128;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
//@ known-bug: #144293
|
||||
// Same as recursion-etc but eggs LLVM emission into giving indirect arguments.
|
||||
#![expect(incomplete_features)]
|
||||
#![feature(explicit_tail_calls)]
|
||||
|
||||
use std::hint::black_box;
|
||||
|
||||
struct U64Wrapper {
|
||||
pub x: u64,
|
||||
pub arbitrary: String,
|
||||
}
|
||||
|
||||
fn count(curr: U64Wrapper, top: U64Wrapper) -> U64Wrapper {
|
||||
if black_box(curr.x) >= top.x {
|
||||
curr
|
||||
} else {
|
||||
become count(
|
||||
U64Wrapper {
|
||||
x: curr.x + 1,
|
||||
arbitrary: curr.arbitrary,
|
||||
},
|
||||
top,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!(
|
||||
"{}",
|
||||
count(
|
||||
U64Wrapper {
|
||||
x: 0,
|
||||
arbitrary: "hello!".into()
|
||||
},
|
||||
black_box(U64Wrapper {
|
||||
x: 1000000,
|
||||
arbitrary: "goodbye!".into()
|
||||
})
|
||||
)
|
||||
.x
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
//@ run-pass
|
||||
//@ ignore-backends: gcc
|
||||
//
|
||||
//@ ignore-wasm
|
||||
//@ ignore-riscv64
|
||||
#![feature(explicit_tail_calls)]
|
||||
#![expect(incomplete_features)]
|
||||
|
||||
// Test tail calls with `PassMode::Indirect { on_stack: false, .. }` arguments.
|
||||
//
|
||||
// Normally an indirect argument with `on_stack: false` would be passed as a pointer to 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.
|
||||
//
|
||||
// The solution is to write the argument into the caller's argument place (stored somewhere further
|
||||
// up the stack), and forward that place.
|
||||
|
||||
// A struct big enough that it is not passed via registers, so that the rust calling convention uses
|
||||
// `Indirect { on_stack: false, .. }`.
|
||||
#[repr(C)]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Big([u64; 4]);
|
||||
|
||||
#[inline(never)]
|
||||
fn update_in_caller(y: Big) -> u64 {
|
||||
#[inline(never)]
|
||||
fn helper(x: Big) -> u64 {
|
||||
x.0.iter().sum()
|
||||
}
|
||||
|
||||
let x = Big([y.0[0], 2, 3, 4]);
|
||||
|
||||
// `x` is actually stored in `y`'s space.
|
||||
become helper(x)
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn swapper<T>(a: T, b: T) -> (T, T) {
|
||||
#[inline(never)]
|
||||
fn helper<T>(a: T, b: T) -> (T, T) {
|
||||
(a, b)
|
||||
}
|
||||
|
||||
become helper(b, a)
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn swapper_derived(a: Big, _: (u64, u64), b: Big, _: (u64, u64)) -> ((u64, u64), (u64, u64)) {
|
||||
#[inline(never)]
|
||||
fn helper(_: Big, x: (u64, u64), _: Big, y: (u64, u64)) -> ((u64, u64), (u64, u64)) {
|
||||
(x, y)
|
||||
}
|
||||
|
||||
// Read the values at various points in the swapping process, testing that they have the correct
|
||||
// value at every point.
|
||||
become helper(b, (a.0[0], b.0[0]), a, (a.0[0], b.0[0]));
|
||||
}
|
||||
|
||||
fn main() {
|
||||
assert_eq!(update_in_caller(Big::default()), 0 + 2 + 3 + 4);
|
||||
|
||||
assert_eq!(swapper(u8::MIN, u8::MAX), (u8::MAX, u8::MIN));
|
||||
// i128 uses `PassMode::Indirect { on_stack: false, .. }` on x86_64 MSVC.
|
||||
assert_eq!(swapper(i128::MIN, i128::MAX), (i128::MAX, i128::MIN));
|
||||
assert_eq!(swapper(Big([1; 4]), Big([2; 4])), (Big([2; 4]), Big([1; 4])));
|
||||
|
||||
assert_eq!(swapper_derived(Big([1; 4]), (0, 0), Big([2; 4]), (0, 0)), ((1, 2), (1, 2)));
|
||||
}
|
||||
@@ -35,9 +35,9 @@ fn main() {
|
||||
macro_rules! Type {
|
||||
() => {
|
||||
::std::cell::Cell
|
||||
//~^ ERROR expected value, found struct `std::cell::Cell`
|
||||
//~| ERROR expected value, found struct `std::cell::Cell`
|
||||
//~| ERROR expected value, found struct `std::cell::Cell`
|
||||
//~^ ERROR expected value, found struct `::std::cell::Cell`
|
||||
//~| ERROR expected value, found struct `::std::cell::Cell`
|
||||
//~| ERROR expected value, found struct `::std::cell::Cell`
|
||||
};
|
||||
(alias) => {
|
||||
Alias
|
||||
|
||||
@@ -70,7 +70,7 @@ LL - let _ = foo.bar;
|
||||
LL + let _ = foo::bar;
|
||||
|
|
||||
|
||||
error[E0423]: expected value, found struct `std::cell::Cell`
|
||||
error[E0423]: expected value, found struct `::std::cell::Cell`
|
||||
--> $DIR/dot-notation-type-namespace-suggest-path-sep.rs:37:9
|
||||
|
|
||||
LL | ::std::cell::Cell
|
||||
@@ -86,7 +86,7 @@ LL - Type!().get();
|
||||
LL + <Type!()>::get();
|
||||
|
|
||||
|
||||
error[E0423]: expected value, found struct `std::cell::Cell`
|
||||
error[E0423]: expected value, found struct `::std::cell::Cell`
|
||||
--> $DIR/dot-notation-type-namespace-suggest-path-sep.rs:37:9
|
||||
|
|
||||
LL | ::std::cell::Cell
|
||||
@@ -166,7 +166,7 @@ LL - Vec.new
|
||||
LL + Vec::new
|
||||
|
|
||||
|
||||
error[E0423]: expected value, found struct `std::cell::Cell`
|
||||
error[E0423]: expected value, found struct `::std::cell::Cell`
|
||||
--> $DIR/dot-notation-type-namespace-suggest-path-sep.rs:37:9
|
||||
|
|
||||
LL | ::std::cell::Cell
|
||||
|
||||
@@ -15,8 +15,8 @@ fn main() {
|
||||
macro_rules! Trait {
|
||||
() => {
|
||||
::std::iter::Iterator
|
||||
//~^ ERROR expected value, found trait `std::iter::Iterator`
|
||||
//~| ERROR expected value, found trait `std::iter::Iterator`
|
||||
//~^ ERROR expected value, found trait `::std::iter::Iterator`
|
||||
//~| ERROR expected value, found trait `::std::iter::Iterator`
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ LL - let _ = Into::<()>.into;
|
||||
LL + let _ = Into::<()>::into;
|
||||
|
|
||||
|
||||
error[E0423]: expected value, found trait `std::iter::Iterator`
|
||||
error[E0423]: expected value, found trait `::std::iter::Iterator`
|
||||
--> $DIR/issue-100365.rs:17:9
|
||||
|
|
||||
LL | ::std::iter::Iterator
|
||||
@@ -45,7 +45,7 @@ LL | Trait!().map(std::convert::identity); // no `help` here!
|
||||
|
|
||||
= note: this error originates in the macro `Trait` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0423]: expected value, found trait `std::iter::Iterator`
|
||||
error[E0423]: expected value, found trait `::std::iter::Iterator`
|
||||
--> $DIR/issue-100365.rs:17:9
|
||||
|
|
||||
LL | ::std::iter::Iterator
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
enum E {}
|
||||
trait Tr {}
|
||||
|
||||
pub(in E) struct S; //~ ERROR expected module, found enum `E`
|
||||
pub(in Tr) struct Z; //~ ERROR expected module, found trait `Tr`
|
||||
pub(in E) struct S; //~ ERROR expected module, found enum `::E`
|
||||
pub(in Tr) struct Z; //~ ERROR expected module, found trait `::Tr`
|
||||
pub(in std::vec) struct F; //~ ERROR visibilities can only be restricted to ancestor modules
|
||||
pub(in nonexistent) struct G; //~ ERROR cannot find
|
||||
pub(in too_soon) struct H; //~ ERROR cannot find
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
error[E0577]: expected module, found enum `E`
|
||||
error[E0577]: expected module, found enum `::E`
|
||||
--> $DIR/resolve-bad-visibility.rs:5:8
|
||||
|
|
||||
LL | pub(in E) struct S;
|
||||
| ^ not a module
|
||||
|
||||
error[E0577]: expected module, found trait `Tr`
|
||||
error[E0577]: expected module, found trait `::Tr`
|
||||
--> $DIR/resolve-bad-visibility.rs:6:8
|
||||
|
|
||||
LL | pub(in Tr) struct Z;
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
use *; //~ ERROR cannot glob-import all possible crates
|
||||
|
||||
fn main() {
|
||||
let s = ::xcrate; //~ ERROR expected value, found crate `xcrate`
|
||||
let s = ::xcrate; //~ ERROR expected value, found crate `::xcrate`
|
||||
//~^ NOTE not a value
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ error: cannot glob-import all possible crates
|
||||
LL | use *;
|
||||
| ^
|
||||
|
||||
error[E0423]: expected value, found crate `xcrate`
|
||||
error[E0423]: expected value, found crate `::xcrate`
|
||||
--> $DIR/single-segment.rs:9:13
|
||||
|
|
||||
LL | let s = ::xcrate;
|
||||
|
||||
Reference in New Issue
Block a user