Auto merge of #150013 - matthiaskrgr:rollup-srl9jrb, r=matthiaskrgr
Rollup of 7 pull requests Successful merges: - rust-lang/rust#149744 (test: update duplicate many_digits test to use f64 instead of f32) - rust-lang/rust#149946 (mir_build: Move and rename code for partitioning match candidates) - rust-lang/rust#149987 (Move ambient cdb discovery from compiletest to bootstrap) - rust-lang/rust#149990 (Improve amdgpu docs: Mention device-libs and xnack) - rust-lang/rust#149994 (Allow vector types for amdgpu) - rust-lang/rust#149997 (Link POSIX instead of Linux manual for Instant) - rust-lang/rust#150010 (Correct library linking for hexagon targets in run-make tests) r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
ee447067e1
16 changed files with 460 additions and 419 deletions
353
compiler/rustc_mir_build/src/builder/matches/buckets.rs
Normal file
353
compiler/rustc_mir_build/src/builder/matches/buckets.rs
Normal file
|
|
@ -0,0 +1,353 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use rustc_data_structures::fx::FxIndexMap;
|
||||
use rustc_middle::mir::{BinOp, Place};
|
||||
use rustc_middle::span_bug;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::builder::Builder;
|
||||
use crate::builder::matches::test::is_switch_ty;
|
||||
use crate::builder::matches::{Candidate, Test, TestBranch, TestCase, TestKind};
|
||||
|
||||
/// Output of [`Builder::partition_candidates_into_buckets`].
|
||||
pub(crate) struct PartitionedCandidates<'tcx, 'b, 'c> {
|
||||
/// For each possible outcome of the test, the candidates that are matched in that outcome.
|
||||
pub(crate) target_candidates: FxIndexMap<TestBranch<'tcx>, Vec<&'b mut Candidate<'tcx>>>,
|
||||
/// The remaining candidates that weren't associated with any test outcome.
|
||||
pub(crate) remaining_candidates: &'b mut [&'c mut Candidate<'tcx>],
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
/// Given a test, we partition the input candidates into several buckets.
|
||||
/// If a candidate matches in exactly one of the branches of `test`
|
||||
/// (and no other branches), we put it into the corresponding bucket.
|
||||
/// If it could match in more than one of the branches of `test`, the test
|
||||
/// doesn't usefully apply to it, and we stop partitioning candidates.
|
||||
///
|
||||
/// Importantly, we also **mutate** the branched candidates to remove match pairs
|
||||
/// that are entailed by the outcome of the test, and add any sub-pairs of the
|
||||
/// removed pairs.
|
||||
///
|
||||
/// For example:
|
||||
/// ```
|
||||
/// # let (x, y, z) = (true, true, true);
|
||||
/// match (x, y, z) {
|
||||
/// (true , _ , true ) => true, // (0)
|
||||
/// (false, false, _ ) => false, // (1)
|
||||
/// (_ , true , _ ) => true, // (2)
|
||||
/// (true , _ , false) => false, // (3)
|
||||
/// }
|
||||
/// # ;
|
||||
/// ```
|
||||
///
|
||||
/// Assume we are testing on `x`. Conceptually, there are 2 overlapping candidate sets:
|
||||
/// - If the outcome is that `x` is true, candidates {0, 2, 3} are possible
|
||||
/// - If the outcome is that `x` is false, candidates {1, 2} are possible
|
||||
///
|
||||
/// Following our algorithm:
|
||||
/// - Candidate 0 is bucketed into outcome `x == true`
|
||||
/// - Candidate 1 is bucketed into outcome `x == false`
|
||||
/// - Candidate 2 remains unbucketed, because testing `x` has no effect on it
|
||||
/// - Candidate 3 remains unbucketed, because a previous candidate (2) was unbucketed
|
||||
/// - This helps preserve the illusion that candidates are tested "in order"
|
||||
///
|
||||
/// The bucketed candidates are mutated to remove entailed match pairs:
|
||||
/// - candidate 0 becomes `[z @ true]` since we know that `x` was `true`;
|
||||
/// - candidate 1 becomes `[y @ false]` since we know that `x` was `false`.
|
||||
pub(super) fn partition_candidates_into_buckets<'b, 'c>(
|
||||
&mut self,
|
||||
match_place: Place<'tcx>,
|
||||
test: &Test<'tcx>,
|
||||
mut candidates: &'b mut [&'c mut Candidate<'tcx>],
|
||||
) -> PartitionedCandidates<'tcx, 'b, 'c> {
|
||||
// For each of the possible outcomes, collect a vector of candidates that apply if the test
|
||||
// has that particular outcome.
|
||||
let mut target_candidates: FxIndexMap<_, Vec<&mut Candidate<'_>>> = Default::default();
|
||||
|
||||
let total_candidate_count = candidates.len();
|
||||
|
||||
// Partition the candidates into the appropriate vector in `target_candidates`.
|
||||
// Note that at some point we may encounter a candidate where the test is not relevant;
|
||||
// at that point, we stop partitioning.
|
||||
while let Some(candidate) = candidates.first_mut() {
|
||||
let Some(branch) =
|
||||
self.choose_bucket_for_candidate(match_place, test, candidate, &target_candidates)
|
||||
else {
|
||||
break;
|
||||
};
|
||||
let (candidate, rest) = candidates.split_first_mut().unwrap();
|
||||
target_candidates.entry(branch).or_insert_with(Vec::new).push(candidate);
|
||||
candidates = rest;
|
||||
}
|
||||
|
||||
// At least the first candidate ought to be tested
|
||||
assert!(
|
||||
total_candidate_count > candidates.len(),
|
||||
"{total_candidate_count}, {candidates:#?}"
|
||||
);
|
||||
debug!("tested_candidates: {}", total_candidate_count - candidates.len());
|
||||
debug!("untested_candidates: {}", candidates.len());
|
||||
|
||||
PartitionedCandidates { target_candidates, remaining_candidates: candidates }
|
||||
}
|
||||
|
||||
/// Given that we are performing `test` against `test_place`, this job
|
||||
/// sorts out what the status of `candidate` will be after the test. See
|
||||
/// `test_candidates` for the usage of this function. The candidate may
|
||||
/// be modified to update its `match_pairs`.
|
||||
///
|
||||
/// So, for example, if this candidate is `x @ Some(P0)` and the `Test` is
|
||||
/// a variant test, then we would modify the candidate to be `(x as
|
||||
/// Option).0 @ P0` and return the index corresponding to the variant
|
||||
/// `Some`.
|
||||
///
|
||||
/// However, in some cases, the test may just not be relevant to candidate.
|
||||
/// For example, suppose we are testing whether `foo.x == 22`, but in one
|
||||
/// match arm we have `Foo { x: _, ... }`... in that case, the test for
|
||||
/// the value of `x` has no particular relevance to this candidate. In
|
||||
/// such cases, this function just returns None without doing anything.
|
||||
/// This is used by the overall `match_candidates` algorithm to structure
|
||||
/// the match as a whole. See `match_candidates` for more details.
|
||||
///
|
||||
/// FIXME(#29623). In some cases, we have some tricky choices to make. for
|
||||
/// example, if we are testing that `x == 22`, but the candidate is `x @
|
||||
/// 13..55`, what should we do? In the event that the test is true, we know
|
||||
/// that the candidate applies, but in the event of false, we don't know
|
||||
/// that it *doesn't* apply. For now, we return false, indicate that the
|
||||
/// test does not apply to this candidate, but it might be we can get
|
||||
/// tighter match code if we do something a bit different.
|
||||
fn choose_bucket_for_candidate(
|
||||
&mut self,
|
||||
test_place: Place<'tcx>,
|
||||
test: &Test<'tcx>,
|
||||
candidate: &mut Candidate<'tcx>,
|
||||
// Other candidates that have already been partitioned into a bucket for this test, if any
|
||||
prior_candidates: &FxIndexMap<TestBranch<'tcx>, Vec<&mut Candidate<'tcx>>>,
|
||||
) -> Option<TestBranch<'tcx>> {
|
||||
// Find the match_pair for this place (if any). At present,
|
||||
// afaik, there can be at most one. (In the future, if we
|
||||
// adopted a more general `@` operator, there might be more
|
||||
// than one, but it'd be very unusual to have two sides that
|
||||
// both require tests; you'd expect one side to be simplified
|
||||
// away.)
|
||||
let (match_pair_index, match_pair) = candidate
|
||||
.match_pairs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|&(_, mp)| mp.place == Some(test_place))?;
|
||||
|
||||
// If true, the match pair is completely entailed by its corresponding test
|
||||
// branch, so it can be removed. If false, the match pair is _compatible_
|
||||
// with its test branch, but still needs a more specific test.
|
||||
let fully_matched;
|
||||
let ret = match (&test.kind, &match_pair.test_case) {
|
||||
// If we are performing a variant switch, then this
|
||||
// informs variant patterns, but nothing else.
|
||||
(
|
||||
&TestKind::Switch { adt_def: tested_adt_def },
|
||||
&TestCase::Variant { adt_def, variant_index },
|
||||
) => {
|
||||
assert_eq!(adt_def, tested_adt_def);
|
||||
fully_matched = true;
|
||||
Some(TestBranch::Variant(variant_index))
|
||||
}
|
||||
|
||||
// If we are performing a switch over integers, then this informs integer
|
||||
// equality, but nothing else.
|
||||
//
|
||||
// FIXME(#29623) we could use PatKind::Range to rule
|
||||
// things out here, in some cases.
|
||||
//
|
||||
// FIXME(Zalathar): Is the `is_switch_ty` test unnecessary?
|
||||
(TestKind::SwitchInt, &TestCase::Constant { value })
|
||||
if is_switch_ty(match_pair.pattern_ty) =>
|
||||
{
|
||||
// An important invariant of candidate bucketing is that a candidate
|
||||
// must not match in multiple branches. For `SwitchInt` tests, adding
|
||||
// a new value might invalidate that property for range patterns that
|
||||
// have already been partitioned into the failure arm, so we must take care
|
||||
// not to add such values here.
|
||||
let is_covering_range = |test_case: &TestCase<'tcx>| {
|
||||
test_case.as_range().is_some_and(|range| {
|
||||
matches!(range.contains(value, self.tcx), None | Some(true))
|
||||
})
|
||||
};
|
||||
let is_conflicting_candidate = |candidate: &&mut Candidate<'tcx>| {
|
||||
candidate
|
||||
.match_pairs
|
||||
.iter()
|
||||
.any(|mp| mp.place == Some(test_place) && is_covering_range(&mp.test_case))
|
||||
};
|
||||
if prior_candidates
|
||||
.get(&TestBranch::Failure)
|
||||
.is_some_and(|candidates| candidates.iter().any(is_conflicting_candidate))
|
||||
{
|
||||
fully_matched = false;
|
||||
None
|
||||
} else {
|
||||
fully_matched = true;
|
||||
Some(TestBranch::Constant(value))
|
||||
}
|
||||
}
|
||||
(TestKind::SwitchInt, TestCase::Range(range)) => {
|
||||
// When performing a `SwitchInt` test, a range pattern can be
|
||||
// sorted into the failure arm if it doesn't contain _any_ of
|
||||
// the values being tested. (This restricts what values can be
|
||||
// added to the test by subsequent candidates.)
|
||||
fully_matched = false;
|
||||
let not_contained = prior_candidates
|
||||
.keys()
|
||||
.filter_map(|br| br.as_constant())
|
||||
.all(|val| matches!(range.contains(val, self.tcx), Some(false)));
|
||||
|
||||
not_contained.then(|| {
|
||||
// No switch values are contained in the pattern range,
|
||||
// so the pattern can be matched only if this test fails.
|
||||
TestBranch::Failure
|
||||
})
|
||||
}
|
||||
|
||||
(TestKind::If, TestCase::Constant { value }) => {
|
||||
fully_matched = true;
|
||||
let value = value.try_to_bool().unwrap_or_else(|| {
|
||||
span_bug!(test.span, "expected boolean value but got {value:?}")
|
||||
});
|
||||
Some(if value { TestBranch::Success } else { TestBranch::Failure })
|
||||
}
|
||||
|
||||
(
|
||||
&TestKind::Len { len: test_len, op: BinOp::Eq },
|
||||
&TestCase::Slice { len, variable_length },
|
||||
) => {
|
||||
match (test_len.cmp(&(len as u64)), variable_length) {
|
||||
(Ordering::Equal, false) => {
|
||||
// on true, min_len = len = $actual_length,
|
||||
// on false, len != $actual_length
|
||||
fully_matched = true;
|
||||
Some(TestBranch::Success)
|
||||
}
|
||||
(Ordering::Less, _) => {
|
||||
// test_len < pat_len. If $actual_len = test_len,
|
||||
// then $actual_len < pat_len and we don't have
|
||||
// enough elements.
|
||||
fully_matched = false;
|
||||
Some(TestBranch::Failure)
|
||||
}
|
||||
(Ordering::Equal | Ordering::Greater, true) => {
|
||||
// This can match both if $actual_len = test_len >= pat_len,
|
||||
// and if $actual_len > test_len. We can't advance.
|
||||
fully_matched = false;
|
||||
None
|
||||
}
|
||||
(Ordering::Greater, false) => {
|
||||
// test_len != pat_len, so if $actual_len = test_len, then
|
||||
// $actual_len != pat_len.
|
||||
fully_matched = false;
|
||||
Some(TestBranch::Failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
(
|
||||
&TestKind::Len { len: test_len, op: BinOp::Ge },
|
||||
&TestCase::Slice { len, variable_length },
|
||||
) => {
|
||||
// the test is `$actual_len >= test_len`
|
||||
match (test_len.cmp(&(len as u64)), variable_length) {
|
||||
(Ordering::Equal, true) => {
|
||||
// $actual_len >= test_len = pat_len,
|
||||
// so we can match.
|
||||
fully_matched = true;
|
||||
Some(TestBranch::Success)
|
||||
}
|
||||
(Ordering::Less, _) | (Ordering::Equal, false) => {
|
||||
// test_len <= pat_len. If $actual_len < test_len,
|
||||
// then it is also < pat_len, so the test passing is
|
||||
// necessary (but insufficient).
|
||||
fully_matched = false;
|
||||
Some(TestBranch::Success)
|
||||
}
|
||||
(Ordering::Greater, false) => {
|
||||
// test_len > pat_len. If $actual_len >= test_len > pat_len,
|
||||
// then we know we won't have a match.
|
||||
fully_matched = false;
|
||||
Some(TestBranch::Failure)
|
||||
}
|
||||
(Ordering::Greater, true) => {
|
||||
// test_len < pat_len, and is therefore less
|
||||
// strict. This can still go both ways.
|
||||
fully_matched = false;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(TestKind::Range(test), TestCase::Range(pat)) => {
|
||||
if test == pat {
|
||||
fully_matched = true;
|
||||
Some(TestBranch::Success)
|
||||
} else {
|
||||
fully_matched = false;
|
||||
// If the testing range does not overlap with pattern range,
|
||||
// the pattern can be matched only if this test fails.
|
||||
if !test.overlaps(pat, self.tcx)? { Some(TestBranch::Failure) } else { None }
|
||||
}
|
||||
}
|
||||
(TestKind::Range(range), &TestCase::Constant { value }) => {
|
||||
fully_matched = false;
|
||||
if !range.contains(value, self.tcx)? {
|
||||
// `value` is not contained in the testing range,
|
||||
// so `value` can be matched only if this test fails.
|
||||
Some(TestBranch::Failure)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
(TestKind::Eq { value: test_val, .. }, TestCase::Constant { value: case_val }) => {
|
||||
if test_val == case_val {
|
||||
fully_matched = true;
|
||||
Some(TestBranch::Success)
|
||||
} else {
|
||||
fully_matched = false;
|
||||
Some(TestBranch::Failure)
|
||||
}
|
||||
}
|
||||
|
||||
(TestKind::Deref { temp: test_temp, .. }, TestCase::Deref { temp, .. })
|
||||
if test_temp == temp =>
|
||||
{
|
||||
fully_matched = true;
|
||||
Some(TestBranch::Success)
|
||||
}
|
||||
|
||||
(TestKind::Never, _) => {
|
||||
fully_matched = true;
|
||||
Some(TestBranch::Success)
|
||||
}
|
||||
|
||||
(
|
||||
TestKind::Switch { .. }
|
||||
| TestKind::SwitchInt { .. }
|
||||
| TestKind::If
|
||||
| TestKind::Len { .. }
|
||||
| TestKind::Range { .. }
|
||||
| TestKind::Eq { .. }
|
||||
| TestKind::Deref { .. },
|
||||
_,
|
||||
) => {
|
||||
fully_matched = false;
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if fully_matched {
|
||||
// Replace the match pair by its sub-pairs.
|
||||
let match_pair = candidate.match_pairs.remove(match_pair_index);
|
||||
candidate.match_pairs.extend(match_pair.subpairs);
|
||||
// Move or-patterns to the end.
|
||||
candidate.sort_match_pairs();
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ use tracing::{debug, instrument};
|
|||
|
||||
use crate::builder::ForGuard::{self, OutsideGuard, RefWithinGuard};
|
||||
use crate::builder::expr::as_place::PlaceBuilder;
|
||||
use crate::builder::matches::buckets::PartitionedCandidates;
|
||||
use crate::builder::matches::user_ty::ProjectedUserTypesNode;
|
||||
use crate::builder::scope::DropKind;
|
||||
use crate::builder::{
|
||||
|
|
@ -34,6 +35,7 @@ use crate::builder::{
|
|||
};
|
||||
|
||||
// helper functions, broken out by category:
|
||||
mod buckets;
|
||||
mod match_pair;
|
||||
mod test;
|
||||
mod user_ty;
|
||||
|
|
@ -1068,7 +1070,7 @@ struct Candidate<'tcx> {
|
|||
/// Key mutations include:
|
||||
///
|
||||
/// - When a match pair is fully satisfied by a test, it is removed from the
|
||||
/// list, and its subpairs are added instead (see [`Builder::sort_candidate`]).
|
||||
/// list, and its subpairs are added instead (see [`Builder::choose_bucket_for_candidate`]).
|
||||
/// - During or-pattern expansion, any leading or-pattern is removed, and is
|
||||
/// converted into subcandidates (see [`Builder::expand_and_match_or_candidates`]).
|
||||
/// - After a candidate's subcandidates have been lowered, a copy of any remaining
|
||||
|
|
@ -1253,7 +1255,7 @@ struct Ascription<'tcx> {
|
|||
///
|
||||
/// Created by [`MatchPairTree::for_pattern`], and then inspected primarily by:
|
||||
/// - [`Builder::pick_test_for_match_pair`] (to choose a test)
|
||||
/// - [`Builder::sort_candidate`] (to see how the test interacts with a match pair)
|
||||
/// - [`Builder::choose_bucket_for_candidate`] (to see how the test interacts with a match pair)
|
||||
///
|
||||
/// Note that or-patterns are not tested directly like the other variants.
|
||||
/// Instead they participate in or-pattern expansion, where they are transformed into
|
||||
|
|
@ -2172,86 +2174,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
|||
(match_place, test)
|
||||
}
|
||||
|
||||
/// Given a test, we partition the input candidates into several buckets.
|
||||
/// If a candidate matches in exactly one of the branches of `test`
|
||||
/// (and no other branches), we put it into the corresponding bucket.
|
||||
/// If it could match in more than one of the branches of `test`, the test
|
||||
/// doesn't usefully apply to it, and we stop partitioning candidates.
|
||||
///
|
||||
/// Importantly, we also **mutate** the branched candidates to remove match pairs
|
||||
/// that are entailed by the outcome of the test, and add any sub-pairs of the
|
||||
/// removed pairs.
|
||||
///
|
||||
/// This returns a pair of
|
||||
/// - the candidates that weren't sorted;
|
||||
/// - for each possible outcome of the test, the candidates that match in that outcome.
|
||||
///
|
||||
/// For example:
|
||||
/// ```
|
||||
/// # let (x, y, z) = (true, true, true);
|
||||
/// match (x, y, z) {
|
||||
/// (true , _ , true ) => true, // (0)
|
||||
/// (false, false, _ ) => false, // (1)
|
||||
/// (_ , true , _ ) => true, // (2)
|
||||
/// (true , _ , false) => false, // (3)
|
||||
/// }
|
||||
/// # ;
|
||||
/// ```
|
||||
///
|
||||
/// Assume we are testing on `x`. Conceptually, there are 2 overlapping candidate sets:
|
||||
/// - If the outcome is that `x` is true, candidates {0, 2, 3} are possible
|
||||
/// - If the outcome is that `x` is false, candidates {1, 2} are possible
|
||||
///
|
||||
/// Following our algorithm:
|
||||
/// - Candidate 0 is sorted into outcome `x == true`
|
||||
/// - Candidate 1 is sorted into outcome `x == false`
|
||||
/// - Candidate 2 remains unsorted, because testing `x` has no effect on it
|
||||
/// - Candidate 3 remains unsorted, because a previous candidate (2) was unsorted
|
||||
/// - This helps preserve the illusion that candidates are tested "in order"
|
||||
///
|
||||
/// The sorted candidates are mutated to remove entailed match pairs:
|
||||
/// - candidate 0 becomes `[z @ true]` since we know that `x` was `true`;
|
||||
/// - candidate 1 becomes `[y @ false]` since we know that `x` was `false`.
|
||||
fn sort_candidates<'b, 'c>(
|
||||
&mut self,
|
||||
match_place: Place<'tcx>,
|
||||
test: &Test<'tcx>,
|
||||
mut candidates: &'b mut [&'c mut Candidate<'tcx>],
|
||||
) -> (
|
||||
&'b mut [&'c mut Candidate<'tcx>],
|
||||
FxIndexMap<TestBranch<'tcx>, Vec<&'b mut Candidate<'tcx>>>,
|
||||
) {
|
||||
// For each of the possible outcomes, collect vector of candidates that apply if the test
|
||||
// has that particular outcome.
|
||||
let mut target_candidates: FxIndexMap<_, Vec<&mut Candidate<'_>>> = Default::default();
|
||||
|
||||
let total_candidate_count = candidates.len();
|
||||
|
||||
// Sort the candidates into the appropriate vector in `target_candidates`. Note that at some
|
||||
// point we may encounter a candidate where the test is not relevant; at that point, we stop
|
||||
// sorting.
|
||||
while let Some(candidate) = candidates.first_mut() {
|
||||
let Some(branch) =
|
||||
self.sort_candidate(match_place, test, candidate, &target_candidates)
|
||||
else {
|
||||
break;
|
||||
};
|
||||
let (candidate, rest) = candidates.split_first_mut().unwrap();
|
||||
target_candidates.entry(branch).or_insert_with(Vec::new).push(candidate);
|
||||
candidates = rest;
|
||||
}
|
||||
|
||||
// At least the first candidate ought to be tested
|
||||
assert!(
|
||||
total_candidate_count > candidates.len(),
|
||||
"{total_candidate_count}, {candidates:#?}"
|
||||
);
|
||||
debug!("tested_candidates: {}", total_candidate_count - candidates.len());
|
||||
debug!("untested_candidates: {}", candidates.len());
|
||||
|
||||
(candidates, target_candidates)
|
||||
}
|
||||
|
||||
/// This is the most subtle part of the match lowering algorithm. At this point, there are
|
||||
/// no fully-satisfied candidates, and no or-patterns to expand, so we actually need to
|
||||
/// perform some sort of test to make progress.
|
||||
|
|
@ -2363,8 +2285,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
|||
// For each of the N possible test outcomes, build the vector of candidates that applies if
|
||||
// the test has that particular outcome. This also mutates the candidates to remove match
|
||||
// pairs that are fully satisfied by the relevant outcome.
|
||||
let (remaining_candidates, target_candidates) =
|
||||
self.sort_candidates(match_place, &test, candidates);
|
||||
let PartitionedCandidates { target_candidates, remaining_candidates } =
|
||||
self.partition_candidates_into_buckets(match_place, &test, candidates);
|
||||
|
||||
// The block that we should branch to if none of the `target_candidates` match.
|
||||
let remainder_start = self.cfg.start_new_block();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
// identify what tests are needed, perform the tests, and then filter
|
||||
// the candidates based on the result.
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::sync::Arc;
|
||||
|
||||
use rustc_data_structures::fx::FxIndexMap;
|
||||
|
|
@ -20,7 +19,7 @@ use rustc_span::{DUMMY_SP, Span, Symbol, sym};
|
|||
use tracing::{debug, instrument};
|
||||
|
||||
use crate::builder::Builder;
|
||||
use crate::builder::matches::{Candidate, MatchPairTree, Test, TestBranch, TestCase, TestKind};
|
||||
use crate::builder::matches::{MatchPairTree, Test, TestBranch, TestCase, TestKind};
|
||||
|
||||
impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
/// Identifies what test is needed to decide if `match_pair` is applicable.
|
||||
|
|
@ -486,266 +485,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
|||
TerminatorKind::if_(Operand::Move(eq_result), success_block, fail_block),
|
||||
);
|
||||
}
|
||||
|
||||
/// Given that we are performing `test` against `test_place`, this job
|
||||
/// sorts out what the status of `candidate` will be after the test. See
|
||||
/// `test_candidates` for the usage of this function. The candidate may
|
||||
/// be modified to update its `match_pairs`.
|
||||
///
|
||||
/// So, for example, if this candidate is `x @ Some(P0)` and the `Test` is
|
||||
/// a variant test, then we would modify the candidate to be `(x as
|
||||
/// Option).0 @ P0` and return the index corresponding to the variant
|
||||
/// `Some`.
|
||||
///
|
||||
/// However, in some cases, the test may just not be relevant to candidate.
|
||||
/// For example, suppose we are testing whether `foo.x == 22`, but in one
|
||||
/// match arm we have `Foo { x: _, ... }`... in that case, the test for
|
||||
/// the value of `x` has no particular relevance to this candidate. In
|
||||
/// such cases, this function just returns None without doing anything.
|
||||
/// This is used by the overall `match_candidates` algorithm to structure
|
||||
/// the match as a whole. See `match_candidates` for more details.
|
||||
///
|
||||
/// FIXME(#29623). In some cases, we have some tricky choices to make. for
|
||||
/// example, if we are testing that `x == 22`, but the candidate is `x @
|
||||
/// 13..55`, what should we do? In the event that the test is true, we know
|
||||
/// that the candidate applies, but in the event of false, we don't know
|
||||
/// that it *doesn't* apply. For now, we return false, indicate that the
|
||||
/// test does not apply to this candidate, but it might be we can get
|
||||
/// tighter match code if we do something a bit different.
|
||||
pub(super) fn sort_candidate(
|
||||
&mut self,
|
||||
test_place: Place<'tcx>,
|
||||
test: &Test<'tcx>,
|
||||
candidate: &mut Candidate<'tcx>,
|
||||
sorted_candidates: &FxIndexMap<TestBranch<'tcx>, Vec<&mut Candidate<'tcx>>>,
|
||||
) -> Option<TestBranch<'tcx>> {
|
||||
// Find the match_pair for this place (if any). At present,
|
||||
// afaik, there can be at most one. (In the future, if we
|
||||
// adopted a more general `@` operator, there might be more
|
||||
// than one, but it'd be very unusual to have two sides that
|
||||
// both require tests; you'd expect one side to be simplified
|
||||
// away.)
|
||||
let (match_pair_index, match_pair) = candidate
|
||||
.match_pairs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|&(_, mp)| mp.place == Some(test_place))?;
|
||||
|
||||
// If true, the match pair is completely entailed by its corresponding test
|
||||
// branch, so it can be removed. If false, the match pair is _compatible_
|
||||
// with its test branch, but still needs a more specific test.
|
||||
let fully_matched;
|
||||
let ret = match (&test.kind, &match_pair.test_case) {
|
||||
// If we are performing a variant switch, then this
|
||||
// informs variant patterns, but nothing else.
|
||||
(
|
||||
&TestKind::Switch { adt_def: tested_adt_def },
|
||||
&TestCase::Variant { adt_def, variant_index },
|
||||
) => {
|
||||
assert_eq!(adt_def, tested_adt_def);
|
||||
fully_matched = true;
|
||||
Some(TestBranch::Variant(variant_index))
|
||||
}
|
||||
|
||||
// If we are performing a switch over integers, then this informs integer
|
||||
// equality, but nothing else.
|
||||
//
|
||||
// FIXME(#29623) we could use PatKind::Range to rule
|
||||
// things out here, in some cases.
|
||||
(TestKind::SwitchInt, &TestCase::Constant { value })
|
||||
if is_switch_ty(match_pair.pattern_ty) =>
|
||||
{
|
||||
// An important invariant of candidate sorting is that a candidate
|
||||
// must not match in multiple branches. For `SwitchInt` tests, adding
|
||||
// a new value might invalidate that property for range patterns that
|
||||
// have already been sorted into the failure arm, so we must take care
|
||||
// not to add such values here.
|
||||
let is_covering_range = |test_case: &TestCase<'tcx>| {
|
||||
test_case.as_range().is_some_and(|range| {
|
||||
matches!(range.contains(value, self.tcx), None | Some(true))
|
||||
})
|
||||
};
|
||||
let is_conflicting_candidate = |candidate: &&mut Candidate<'tcx>| {
|
||||
candidate
|
||||
.match_pairs
|
||||
.iter()
|
||||
.any(|mp| mp.place == Some(test_place) && is_covering_range(&mp.test_case))
|
||||
};
|
||||
if sorted_candidates
|
||||
.get(&TestBranch::Failure)
|
||||
.is_some_and(|candidates| candidates.iter().any(is_conflicting_candidate))
|
||||
{
|
||||
fully_matched = false;
|
||||
None
|
||||
} else {
|
||||
fully_matched = true;
|
||||
Some(TestBranch::Constant(value))
|
||||
}
|
||||
}
|
||||
(TestKind::SwitchInt, TestCase::Range(range)) => {
|
||||
// When performing a `SwitchInt` test, a range pattern can be
|
||||
// sorted into the failure arm if it doesn't contain _any_ of
|
||||
// the values being tested. (This restricts what values can be
|
||||
// added to the test by subsequent candidates.)
|
||||
fully_matched = false;
|
||||
let not_contained = sorted_candidates
|
||||
.keys()
|
||||
.filter_map(|br| br.as_constant())
|
||||
.all(|val| matches!(range.contains(val, self.tcx), Some(false)));
|
||||
|
||||
not_contained.then(|| {
|
||||
// No switch values are contained in the pattern range,
|
||||
// so the pattern can be matched only if this test fails.
|
||||
TestBranch::Failure
|
||||
})
|
||||
}
|
||||
|
||||
(TestKind::If, TestCase::Constant { value }) => {
|
||||
fully_matched = true;
|
||||
let value = value.try_to_bool().unwrap_or_else(|| {
|
||||
span_bug!(test.span, "expected boolean value but got {value:?}")
|
||||
});
|
||||
Some(if value { TestBranch::Success } else { TestBranch::Failure })
|
||||
}
|
||||
|
||||
(
|
||||
&TestKind::Len { len: test_len, op: BinOp::Eq },
|
||||
&TestCase::Slice { len, variable_length },
|
||||
) => {
|
||||
match (test_len.cmp(&(len as u64)), variable_length) {
|
||||
(Ordering::Equal, false) => {
|
||||
// on true, min_len = len = $actual_length,
|
||||
// on false, len != $actual_length
|
||||
fully_matched = true;
|
||||
Some(TestBranch::Success)
|
||||
}
|
||||
(Ordering::Less, _) => {
|
||||
// test_len < pat_len. If $actual_len = test_len,
|
||||
// then $actual_len < pat_len and we don't have
|
||||
// enough elements.
|
||||
fully_matched = false;
|
||||
Some(TestBranch::Failure)
|
||||
}
|
||||
(Ordering::Equal | Ordering::Greater, true) => {
|
||||
// This can match both if $actual_len = test_len >= pat_len,
|
||||
// and if $actual_len > test_len. We can't advance.
|
||||
fully_matched = false;
|
||||
None
|
||||
}
|
||||
(Ordering::Greater, false) => {
|
||||
// test_len != pat_len, so if $actual_len = test_len, then
|
||||
// $actual_len != pat_len.
|
||||
fully_matched = false;
|
||||
Some(TestBranch::Failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
(
|
||||
&TestKind::Len { len: test_len, op: BinOp::Ge },
|
||||
&TestCase::Slice { len, variable_length },
|
||||
) => {
|
||||
// the test is `$actual_len >= test_len`
|
||||
match (test_len.cmp(&(len as u64)), variable_length) {
|
||||
(Ordering::Equal, true) => {
|
||||
// $actual_len >= test_len = pat_len,
|
||||
// so we can match.
|
||||
fully_matched = true;
|
||||
Some(TestBranch::Success)
|
||||
}
|
||||
(Ordering::Less, _) | (Ordering::Equal, false) => {
|
||||
// test_len <= pat_len. If $actual_len < test_len,
|
||||
// then it is also < pat_len, so the test passing is
|
||||
// necessary (but insufficient).
|
||||
fully_matched = false;
|
||||
Some(TestBranch::Success)
|
||||
}
|
||||
(Ordering::Greater, false) => {
|
||||
// test_len > pat_len. If $actual_len >= test_len > pat_len,
|
||||
// then we know we won't have a match.
|
||||
fully_matched = false;
|
||||
Some(TestBranch::Failure)
|
||||
}
|
||||
(Ordering::Greater, true) => {
|
||||
// test_len < pat_len, and is therefore less
|
||||
// strict. This can still go both ways.
|
||||
fully_matched = false;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(TestKind::Range(test), TestCase::Range(pat)) => {
|
||||
if test == pat {
|
||||
fully_matched = true;
|
||||
Some(TestBranch::Success)
|
||||
} else {
|
||||
fully_matched = false;
|
||||
// If the testing range does not overlap with pattern range,
|
||||
// the pattern can be matched only if this test fails.
|
||||
if !test.overlaps(pat, self.tcx)? { Some(TestBranch::Failure) } else { None }
|
||||
}
|
||||
}
|
||||
(TestKind::Range(range), &TestCase::Constant { value }) => {
|
||||
fully_matched = false;
|
||||
if !range.contains(value, self.tcx)? {
|
||||
// `value` is not contained in the testing range,
|
||||
// so `value` can be matched only if this test fails.
|
||||
Some(TestBranch::Failure)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
(TestKind::Eq { value: test_val, .. }, TestCase::Constant { value: case_val }) => {
|
||||
if test_val == case_val {
|
||||
fully_matched = true;
|
||||
Some(TestBranch::Success)
|
||||
} else {
|
||||
fully_matched = false;
|
||||
Some(TestBranch::Failure)
|
||||
}
|
||||
}
|
||||
|
||||
(TestKind::Deref { temp: test_temp, .. }, TestCase::Deref { temp, .. })
|
||||
if test_temp == temp =>
|
||||
{
|
||||
fully_matched = true;
|
||||
Some(TestBranch::Success)
|
||||
}
|
||||
|
||||
(TestKind::Never, _) => {
|
||||
fully_matched = true;
|
||||
Some(TestBranch::Success)
|
||||
}
|
||||
|
||||
(
|
||||
TestKind::Switch { .. }
|
||||
| TestKind::SwitchInt { .. }
|
||||
| TestKind::If
|
||||
| TestKind::Len { .. }
|
||||
| TestKind::Range { .. }
|
||||
| TestKind::Eq { .. }
|
||||
| TestKind::Deref { .. },
|
||||
_,
|
||||
) => {
|
||||
fully_matched = false;
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if fully_matched {
|
||||
// Replace the match pair by its sub-pairs.
|
||||
let match_pair = candidate.match_pairs.remove(match_pair_index);
|
||||
candidate.match_pairs.extend(match_pair.subpairs);
|
||||
// Move or-patterns to the end.
|
||||
candidate.sort_match_pairs();
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
fn is_switch_ty(ty: Ty<'_>) -> bool {
|
||||
/// Returns true if this type be used with [`TestKind::SwitchInt`].
|
||||
pub(crate) fn is_switch_ty(ty: Ty<'_>) -> bool {
|
||||
ty.is_integral() || ty.is_char()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ fn do_check_simd_vector_abi<'tcx>(
|
|||
continue;
|
||||
}
|
||||
};
|
||||
if !have_feature(Symbol::intern(feature)) {
|
||||
if !feature.is_empty() && !have_feature(Symbol::intern(feature)) {
|
||||
// Emit error.
|
||||
let (span, _hir_id) = loc();
|
||||
tcx.dcx().emit_err(errors::AbiErrorDisabledVectorType {
|
||||
|
|
|
|||
|
|
@ -918,6 +918,7 @@ const AARCH64_FEATURES_FOR_CORRECT_VECTOR_ABI: &'static [(u64, &'static str)] =
|
|||
// We might want to add "helium" too.
|
||||
const ARM_FEATURES_FOR_CORRECT_VECTOR_ABI: &'static [(u64, &'static str)] = &[(128, "neon")];
|
||||
|
||||
const AMDGPU_FEATURES_FOR_CORRECT_VECTOR_ABI: &'static [(u64, &'static str)] = &[(1024, "")];
|
||||
const POWERPC_FEATURES_FOR_CORRECT_VECTOR_ABI: &'static [(u64, &'static str)] = &[(128, "altivec")];
|
||||
const WASM_FEATURES_FOR_CORRECT_VECTOR_ABI: &'static [(u64, &'static str)] = &[(128, "simd128")];
|
||||
const S390X_FEATURES_FOR_CORRECT_VECTOR_ABI: &'static [(u64, &'static str)] = &[(128, "vector")];
|
||||
|
|
@ -996,12 +997,12 @@ impl Target {
|
|||
Arch::Mips | Arch::Mips32r6 | Arch::Mips64 | Arch::Mips64r6 => {
|
||||
MIPS_FEATURES_FOR_CORRECT_VECTOR_ABI
|
||||
}
|
||||
Arch::AmdGpu => AMDGPU_FEATURES_FOR_CORRECT_VECTOR_ABI,
|
||||
Arch::Nvptx64 | Arch::Bpf | Arch::M68k => &[], // no vector ABI
|
||||
Arch::CSky => CSKY_FEATURES_FOR_CORRECT_VECTOR_ABI,
|
||||
// FIXME: for some tier3 targets, we are overly cautious and always give warnings
|
||||
// when passing args in vector registers.
|
||||
Arch::AmdGpu
|
||||
| Arch::Avr
|
||||
Arch::Avr
|
||||
| Arch::Msp430
|
||||
| Arch::PowerPC64LE
|
||||
| Arch::SpirV
|
||||
|
|
|
|||
|
|
@ -204,8 +204,8 @@ fn many_digits() {
|
|||
"1.175494140627517859246175898662808184331245864732796240031385942718174675986064769972472277004271745681762695312500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e-38"
|
||||
);
|
||||
assert_float_result_bits_eq!(
|
||||
0x7ffffe,
|
||||
f32,
|
||||
"1.175494140627517859246175898662808184331245864732796240031385942718174675986064769972472277004271745681762695312500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e-38"
|
||||
0x7ffffffffffffe,
|
||||
f64,
|
||||
"2.8480945388892171379514712013899006561925415836684281158317117472799232118121416898288331376688117187382156519616555919469923055562697333532151065976805407006156569379201589813489881051279092585294136284133893120582268661680741374638184352784785121471797815387841323043061183721896440504100014432145713413240639315126126485370149254502658324386101932795656342787961697168715161403422599767855490566539165801964388919848888980308652852766053138934233069066651644954964960514065582826296567812754916294792554028205611526494813491373571799099361175786448799007387647056059512705071170383000860694587462575533337769237800154235626508997957207085308694919708914563870082480025309479610576861709891209343110514894825624524442569257542956506767950486391782760620117187500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e-306"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -275,7 +275,7 @@ impl Instant {
|
|||
#[cfg(not(target_vendor = "apple"))]
|
||||
pub(crate) const CLOCK_ID: libc::clockid_t = libc::CLOCK_MONOTONIC;
|
||||
pub fn now() -> Instant {
|
||||
// https://www.manpagez.com/man/3/clock_gettime/
|
||||
// https://pubs.opengroup.org/onlinepubs/9799919799/functions/clock_getres.html
|
||||
//
|
||||
// CLOCK_UPTIME_RAW clock that increments monotonically, in the same man-
|
||||
// ner as CLOCK_MONOTONIC_RAW, but that does not incre-
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ use crate::sys_common::{FromInner, IntoInner};
|
|||
/// [`insecure_time` usercall]: https://edp.fortanix.com/docs/api/fortanix_sgx_abi/struct.Usercalls.html#method.insecure_time
|
||||
/// [timekeeping in SGX]: https://edp.fortanix.com/docs/concepts/rust-std/#codestdtimecode
|
||||
/// [__wasi_clock_time_get]: https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md#clock_time_get
|
||||
/// [clock_gettime]: https://linux.die.net/man/3/clock_gettime
|
||||
/// [clock_gettime]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/clock_getres.html
|
||||
///
|
||||
/// **Disclaimer:** These system calls might change over time.
|
||||
///
|
||||
|
|
@ -234,7 +234,7 @@ pub struct Instant(time::Instant);
|
|||
/// [currently]: crate::io#platform-specific-behavior
|
||||
/// [`insecure_time` usercall]: https://edp.fortanix.com/docs/api/fortanix_sgx_abi/struct.Usercalls.html#method.insecure_time
|
||||
/// [timekeeping in SGX]: https://edp.fortanix.com/docs/concepts/rust-std/#codestdtimecode
|
||||
/// [clock_gettime (Realtime Clock)]: https://linux.die.net/man/3/clock_gettime
|
||||
/// [clock_gettime (Realtime Clock)]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/clock_getres.html
|
||||
/// [__wasi_clock_time_get (Realtime Clock)]: https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md#clock_time_get
|
||||
/// [GetSystemTimePreciseAsFileTime]: https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemtimepreciseasfiletime
|
||||
/// [GetSystemTimeAsFileTime]: https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemtimeasfiletime
|
||||
|
|
|
|||
|
|
@ -2181,6 +2181,10 @@ Please disable assertions with `rust.debug-assertions = false`.
|
|||
}
|
||||
|
||||
if mode == CompiletestMode::Debuginfo {
|
||||
if let Some(debuggers::Cdb { cdb }) = debuggers::discover_cdb(target) {
|
||||
cmd.arg("--cdb").arg(cdb);
|
||||
}
|
||||
|
||||
if let Some(debuggers::Gdb { gdb }) = debuggers::discover_gdb(builder, android.as_ref())
|
||||
{
|
||||
cmd.arg("--gdb").arg(gdb.as_ref());
|
||||
|
|
|
|||
41
src/bootstrap/src/core/debuggers/cdb.rs
Normal file
41
src/bootstrap/src/core/debuggers/cdb.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::core::config::TargetSelection;
|
||||
|
||||
pub(crate) struct Cdb {
|
||||
pub(crate) cdb: PathBuf,
|
||||
}
|
||||
|
||||
/// FIXME: This CDB discovery code was very questionable when it was in
|
||||
/// compiletest, and it's just as questionable now that it's in bootstrap.
|
||||
pub(crate) fn discover_cdb(target: TargetSelection) -> Option<Cdb> {
|
||||
if !cfg!(windows) || !target.ends_with("-pc-windows-msvc") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let pf86 =
|
||||
PathBuf::from(env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?);
|
||||
let cdb_arch = if cfg!(target_arch = "x86") {
|
||||
"x86"
|
||||
} else if cfg!(target_arch = "x86_64") {
|
||||
"x64"
|
||||
} else if cfg!(target_arch = "aarch64") {
|
||||
"arm64"
|
||||
} else if cfg!(target_arch = "arm") {
|
||||
"arm"
|
||||
} else {
|
||||
return None; // No compatible CDB.exe in the Windows 10 SDK
|
||||
};
|
||||
|
||||
let mut path = pf86;
|
||||
path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
|
||||
path.push(cdb_arch);
|
||||
path.push(r"cdb.exe");
|
||||
|
||||
if !path.exists() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Cdb { cdb: path })
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
//! Code for discovering debuggers and debugger-related configuration, so that
|
||||
//! it can be passed to compiletest when running debuginfo tests.
|
||||
|
||||
pub(crate) use self::cdb::{Cdb, discover_cdb};
|
||||
pub(crate) use self::gdb::{Gdb, discover_gdb};
|
||||
pub(crate) use self::lldb::{Lldb, discover_lldb};
|
||||
|
||||
mod cdb;
|
||||
mod gdb;
|
||||
mod lldb;
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ The format of binaries is a linked ELF.
|
|||
|
||||
Binaries must be built with no-std.
|
||||
They can use `core` and `alloc` (`alloc` only if an allocator is supplied).
|
||||
At least one function needs to use the `"gpu-kernel"` calling convention and should be marked with `no_mangle` for simplicity.
|
||||
Functions using the `"gpu-kernel"` calling convention are kernel entrypoints and can be used from the host runtime.
|
||||
At least one function should use the `"gpu-kernel"` calling convention and should be marked with `no_mangle` or `export_name`.
|
||||
Functions using the `"gpu-kernel"` calling convention are kernel entrypoints and can be launched from the host runtime.
|
||||
|
||||
## Building the target
|
||||
|
||||
|
|
@ -34,6 +34,9 @@ The generations are exposed as different target-cpus in the backend.
|
|||
As there are many, Rust does not ship pre-compiled libraries for this target.
|
||||
Therefore, you have to build your own copy of `core` by using `cargo -Zbuild-std=core` or similar.
|
||||
|
||||
An allocator and `println!()` support is provided by the [`amdgpu-device-libs`] crate.
|
||||
Both features rely on the [HIP] runtime.
|
||||
|
||||
To build a binary, create a no-std library:
|
||||
```rust,ignore (platform-specific)
|
||||
// src/lib.rs
|
||||
|
|
@ -65,6 +68,8 @@ lto = true
|
|||
|
||||
The target-cpu must be from the list [supported by LLVM] (or printed with `rustc --target amdgcn-amd-amdhsa --print target-cpus`).
|
||||
The GPU version on the current system can be found e.g. with [`rocminfo`].
|
||||
For a GPU series that has xnack support but the target GPU has not, the `-xnack-support` target-feature needs to be enabled.
|
||||
I.e. if the ISA info as printed with [`rocminfo`] says something about `xnack-`, e.g. `gfx1010:xnack-`, add `-Ctarget-feature=-xnack-support` to the rustflags.
|
||||
|
||||
Example `.cargo/config.toml` file to set the target and GPU generation:
|
||||
```toml
|
||||
|
|
@ -72,6 +77,7 @@ Example `.cargo/config.toml` file to set the target and GPU generation:
|
|||
[build]
|
||||
target = "amdgcn-amd-amdhsa"
|
||||
rustflags = ["-Ctarget-cpu=gfx1100"]
|
||||
# Add "-Ctarget-feature=-xnack-support" for xnack- GPUs (see above)
|
||||
|
||||
[unstable]
|
||||
build-std = ["core"] # Optional: "alloc"
|
||||
|
|
@ -85,8 +91,6 @@ Example code on how to load a compiled binary and run it is available in [ROCm e
|
|||
|
||||
On Linux, binaries can also run through the HSA runtime as implemented in [ROCR-Runtime].
|
||||
|
||||
<!-- Mention an allocator once a suitable one exists for amdgpu -->
|
||||
|
||||
<!--
|
||||
## Testing
|
||||
|
||||
|
|
@ -109,3 +113,4 @@ More information can be found on the [LLVM page for amdgpu].
|
|||
[LLVM page for amdgpu]: https://llvm.org/docs/AMDGPUUsage.html
|
||||
[`rocminfo`]: https://github.com/ROCm/rocminfo
|
||||
[ROCm examples]: https://github.com/ROCm/rocm-examples/tree/ca8ef5b6f1390176616cd1c18fbc98785cbc73f6/HIP-Basic/module_api
|
||||
[`amdgpu-device-libs`]: https://crates.io/crates/amdgpu-device-libs
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::env;
|
|||
use std::process::Command;
|
||||
use std::sync::Arc;
|
||||
|
||||
use camino::{Utf8Path, Utf8PathBuf};
|
||||
use camino::Utf8Path;
|
||||
|
||||
use crate::common::{Config, Debugger};
|
||||
|
||||
|
|
@ -54,51 +54,6 @@ pub(crate) fn configure_lldb(config: &Config) -> Option<Arc<Config>> {
|
|||
Some(Arc::new(Config { debugger: Some(Debugger::Lldb), ..config.clone() }))
|
||||
}
|
||||
|
||||
/// Returns `true` if the given target is a MSVC target for the purposes of CDB testing.
|
||||
fn is_pc_windows_msvc_target(target: &str) -> bool {
|
||||
target.ends_with("-pc-windows-msvc")
|
||||
}
|
||||
|
||||
/// FIXME: this is very questionable...
|
||||
fn find_cdb(target: &str) -> Option<Utf8PathBuf> {
|
||||
if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let pf86 = Utf8PathBuf::from_path_buf(
|
||||
env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?.into(),
|
||||
)
|
||||
.unwrap();
|
||||
let cdb_arch = if cfg!(target_arch = "x86") {
|
||||
"x86"
|
||||
} else if cfg!(target_arch = "x86_64") {
|
||||
"x64"
|
||||
} else if cfg!(target_arch = "aarch64") {
|
||||
"arm64"
|
||||
} else if cfg!(target_arch = "arm") {
|
||||
"arm"
|
||||
} else {
|
||||
return None; // No compatible CDB.exe in the Windows 10 SDK
|
||||
};
|
||||
|
||||
let mut path = pf86;
|
||||
path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
|
||||
path.push(cdb_arch);
|
||||
path.push(r"cdb.exe");
|
||||
|
||||
if !path.exists() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(path)
|
||||
}
|
||||
|
||||
/// Returns Path to CDB
|
||||
pub(crate) fn discover_cdb(cdb: Option<String>, target: &str) -> Option<Utf8PathBuf> {
|
||||
let cdb = cdb.map(Utf8PathBuf::from).or_else(|| find_cdb(target));
|
||||
cdb
|
||||
}
|
||||
|
||||
pub(crate) fn query_cdb_version(cdb: &Utf8Path) -> Option<[u16; 4]> {
|
||||
let mut version = None;
|
||||
if let Ok(output) = Command::new(cdb).arg("/version").output() {
|
||||
|
|
|
|||
|
|
@ -263,7 +263,7 @@ fn parse_config(args: Vec<String>) -> Config {
|
|||
let adb_device_status = target.contains("android") && adb_test_dir.is_some();
|
||||
|
||||
// FIXME: `cdb_version` is *derived* from cdb, but it's *not* technically a config!
|
||||
let cdb = debuggers::discover_cdb(matches.opt_str("cdb"), &target);
|
||||
let cdb = matches.opt_str("cdb").map(Utf8PathBuf::from);
|
||||
let cdb_version = cdb.as_deref().and_then(debuggers::query_cdb_version);
|
||||
// FIXME: `gdb_version` is *derived* from gdb, but it's *not* technically a config!
|
||||
let gdb = matches.opt_str("gdb").map(Utf8PathBuf::from);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{is_arm64ec, is_win7, is_windows, is_windows_msvc, uname};
|
||||
use crate::{is_arm64ec, is_win7, is_windows, is_windows_msvc, target, uname};
|
||||
|
||||
fn get_windows_msvc_libs() -> Vec<&'static str> {
|
||||
let mut libs =
|
||||
|
|
@ -22,14 +22,22 @@ pub fn extra_c_flags() -> Vec<&'static str> {
|
|||
vec!["-lws2_32", "-luserenv", "-lbcrypt", "-lntdll", "-lsynchronization"]
|
||||
}
|
||||
} else {
|
||||
match uname() {
|
||||
n if n.contains("Darwin") => vec!["-lresolv"],
|
||||
n if n.contains("FreeBSD") => vec!["-lm", "-lpthread", "-lgcc_s"],
|
||||
n if n.contains("SunOS") => {
|
||||
vec!["-lm", "-lpthread", "-lposix4", "-lsocket", "-lresolv"]
|
||||
// For cross-compilation targets, we need to check the target, not the host
|
||||
let target_triple = target();
|
||||
if target_triple.contains("hexagon") {
|
||||
// Hexagon targets need unwind support but don't have some Linux libraries
|
||||
vec!["-lunwind", "-lclang_rt.builtins-hexagon"]
|
||||
} else {
|
||||
// For host-based detection, fall back to uname() for non-cross compilation
|
||||
match uname() {
|
||||
n if n.contains("Darwin") => vec!["-lresolv"],
|
||||
n if n.contains("FreeBSD") => vec!["-lm", "-lpthread", "-lgcc_s"],
|
||||
n if n.contains("SunOS") => {
|
||||
vec!["-lm", "-lpthread", "-lposix4", "-lsocket", "-lresolv"]
|
||||
}
|
||||
n if n.contains("OpenBSD") => vec!["-lm", "-lpthread", "-lc++abi"],
|
||||
_ => vec!["-lm", "-lrt", "-ldl", "-lpthread"],
|
||||
}
|
||||
n if n.contains("OpenBSD") => vec!["-lm", "-lpthread", "-lc++abi"],
|
||||
_ => vec!["-lm", "-lrt", "-ldl", "-lpthread"],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ fn main() {
|
|||
"arm-unknown-linux-gnueabi".to_owned(),
|
||||
]);
|
||||
}
|
||||
if llvm_components_contain("amdgpu") {
|
||||
targets.push("amdgcn-amd-amdhsa".to_owned());
|
||||
}
|
||||
let mut x86_archs = Vec::new();
|
||||
if llvm_components_contain("x86") {
|
||||
x86_archs.append(&mut vec!["i686", "x86_64"]);
|
||||
|
|
@ -52,21 +55,25 @@ fn main() {
|
|||
// enabled by-default for i686 and ARM; these features will be invalid
|
||||
// on some platforms, but LLVM just prints a warning so that's fine for
|
||||
// now.
|
||||
let mut cmd = rustc();
|
||||
cmd.target(&target).emit("llvm-ir,asm").input("simd.rs");
|
||||
let target_feature = if target.starts_with("i686") || target.starts_with("x86") {
|
||||
"+sse2"
|
||||
} else if target.starts_with("arm") || target.starts_with("aarch64") {
|
||||
"-soft-float,+neon"
|
||||
} else if target.starts_with("mips") {
|
||||
"+msa,+fp64"
|
||||
} else if target.starts_with("amdgcn") {
|
||||
cmd.arg("-Ctarget-cpu=gfx900");
|
||||
""
|
||||
} else {
|
||||
panic!("missing target_feature case for {target}");
|
||||
};
|
||||
rustc()
|
||||
.target(&target)
|
||||
.emit("llvm-ir,asm")
|
||||
.input("simd.rs")
|
||||
.arg(format!("-Ctarget-feature={target_feature}"))
|
||||
.arg(&format!("-Cextra-filename=-{target}"))
|
||||
.run();
|
||||
|
||||
if !target_feature.is_empty() {
|
||||
cmd.arg(format!("-Ctarget-feature={target_feature}"));
|
||||
}
|
||||
|
||||
cmd.arg(&format!("-Cextra-filename=-{target}")).run();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue