Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
1696f53
fix: rust-lang/rust#47446
sassman Oct 9, 2024
529aae6
wasi/fs: Improve stopping condition for <ReadDir as Iterator>::next
osiewicz Nov 18, 2024
ec5f41a
Run TLS destructors for wasm32-wasip1-threads
surban Nov 23, 2024
692c19a
Refactor ReadDir into a state machine
osiewicz Nov 26, 2024
f4ab982
chore: Improve doc comments
osiewicz Nov 26, 2024
9142cae
add `LinkageInfo` to keep track of how we figured out the linkage
folkertdev Aug 8, 2024
bdf64e1
squashed changes to inlining and const eval
folkertdev Jul 19, 2024
d67a5c0
test the new global asm output of naked functions
folkertdev Jul 19, 2024
a93192c
add `InstructionSetAttr::as_str` and make the type `Copy`
folkertdev Jul 20, 2024
49a297a
squashed changes to tests
folkertdev Jul 20, 2024
589ebb8
make naked functions always have external linkage *in LLVM*. If we do…
folkertdev Jul 26, 2024
2e24cdb
squashed changes:
folkertdev Aug 11, 2024
64420b6
skip `predefine_fn` for naked functions
folkertdev Nov 30, 2024
cf738f6
Revert "add `LinkageInfo` to keep track of how we figured out the lin…
folkertdev Nov 30, 2024
868668e
Run `cargo update` and update licenses
clubby789 Nov 25, 2024
4fe15b0
Use UNIX thread_local implementation for WASI.
surban Dec 3, 2024
30847eb
use vendor sources by default on dist tarballs
onur-ozkan Dec 4, 2024
a6aaef1
update `build.vendor` documentation
onur-ozkan Dec 4, 2024
9ca9b41
add a change entry for new default on `build.vendor`
onur-ozkan Dec 4, 2024
e4092bd
Fix compilation for wasm32-wasip1 (without threads).
surban Dec 5, 2024
4f16640
Add libc funcitons only for wasm32-wasip1-threads.
surban Dec 5, 2024
ac815ff
coverage: Prefer to visit nodes whose predecessors have been visited
Zalathar Dec 5, 2024
f3f7c20
coverage: Move `CoverageIdsInfo` into `mir::coverage`
Zalathar Dec 6, 2024
2022ef7
coverage: Use a query to find counters/expressions that must be zero
Zalathar Dec 6, 2024
4d2bfec
coverage: Remove FunctionCoverageCollector
Zalathar Dec 6, 2024
3a35fb6
coverage: Unused functions don't need to store `CoverageIdsInfo`
Zalathar Dec 8, 2024
9eb35a0
fix the `naked-asan` test
folkertdev Dec 1, 2024
2d8a871
Downgrade cc
clubby789 Dec 8, 2024
4f14890
Configure renovatebot
Kobzol Dec 9, 2024
ed8ee39
fix ICE on type error in promoted
RalfJung Dec 7, 2024
a9fc2e2
Add feature gate, not working yet
compiler-errors Nov 16, 2024
7951d19
Apply suggestions from code review
sassman Dec 9, 2024
3511a46
Implement projection and shim for AFIDT
compiler-errors Nov 16, 2024
b02fcc5
Rollup merge of #128004 - folkertdev:naked-fn-asm, r=Amanieu
fmease Dec 9, 2024
35fc15b
Rollup merge of #131558 - sassman:feat/warnin-for-no-mangle-together-…
fmease Dec 9, 2024
d0f329a
Rollup merge of #133122 - compiler-errors:afidt, r=oli-obk
fmease Dec 9, 2024
00a7584
Rollup merge of #133184 - osiewicz:wasm-fix-infinite-loop-in-remove-d…
fmease Dec 9, 2024
18419d0
Rollup merge of #133456 - clubby789:cargo-update, r=ChrisDenton
fmease Dec 9, 2024
2b252ba
Rollup merge of #133472 - rust-wasi-web:master, r=joboet
fmease Dec 9, 2024
0c87c3c
Rollup merge of #133853 - onur-ozkan:use-vendor-directory-on-dist-bui…
fmease Dec 9, 2024
69889fa
Rollup merge of #133946 - Zalathar:ready-first, r=oli-obk
fmease Dec 9, 2024
65e8897
Rollup merge of #134010 - RalfJung:promoted-type-error-ice, r=oli-obk
fmease Dec 9, 2024
ce27319
Rollup merge of #134029 - Zalathar:zero, r=oli-obk
fmease Dec 9, 2024
d5b0698
Rollup merge of #134071 - Kobzol:ci-renovatebot, r=MarcoIeni
fmease Dec 9, 2024
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
coverage: Use a query to find counters/expressions that must be zero
This query (`coverage_ids_info`) already determines which counter/expression IDs are unused, so it only takes a little extra effort to also determine which counters/expressions must have a value of zero.
  • Loading branch information
Zalathar committed Dec 8, 2024
commit 2022ef7f12fe6ebc810ee5731557d23ba763cc96
116 changes: 4 additions & 112 deletions compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use rustc_data_structures::captures::Captures;
use rustc_data_structures::fx::FxIndexSet;
use rustc_index::bit_set::BitSet;
use rustc_middle::mir::coverage::{
CounterId, CovTerm, CoverageIdsInfo, Expression, ExpressionId, FunctionCoverageInfo, Mapping,
MappingKind, Op, SourceRegion,
CovTerm, CoverageIdsInfo, Expression, FunctionCoverageInfo, Mapping, MappingKind, Op,
SourceRegion,
};
use rustc_middle::ty::Instance;
use tracing::debug;
Expand Down Expand Up @@ -55,92 +53,17 @@ impl<'tcx> FunctionCoverageCollector<'tcx> {
Self { function_coverage_info, ids_info, is_used }
}

/// Identify expressions that will always have a value of zero, and note
/// their IDs in [`ZeroExpressions`]. Mappings that refer to a zero expression
/// can instead become mappings to a constant zero value.
///
/// This method mainly exists to preserve the simplifications that were
/// already being performed by the Rust-side expression renumbering, so that
/// the resulting coverage mappings don't get worse.
fn identify_zero_expressions(&self) -> ZeroExpressions {
// The set of expressions that either were optimized out entirely, or
// have zero as both of their operands, and will therefore always have
// a value of zero. Other expressions that refer to these as operands
// can have those operands replaced with `CovTerm::Zero`.
let mut zero_expressions = ZeroExpressions::default();

// Simplify a copy of each expression based on lower-numbered expressions,
// and then update the set of always-zero expressions if necessary.
// (By construction, expressions can only refer to other expressions
// that have lower IDs, so one pass is sufficient.)
for (id, expression) in self.function_coverage_info.expressions.iter_enumerated() {
if !self.is_used || !self.ids_info.expressions_seen.contains(id) {
// If an expression was not seen, it must have been optimized away,
// so any operand that refers to it can be replaced with zero.
zero_expressions.insert(id);
continue;
}

// We don't need to simplify the actual expression data in the
// expressions list; we can just simplify a temporary copy and then
// use that to update the set of always-zero expressions.
let Expression { mut lhs, op, mut rhs } = *expression;

// If an expression has an operand that is also an expression, the
// operand's ID must be strictly lower. This is what lets us find
// all zero expressions in one pass.
let assert_operand_expression_is_lower = |operand_id: ExpressionId| {
assert!(
operand_id < id,
"Operand {operand_id:?} should be less than {id:?} in {expression:?}",
)
};

// If an operand refers to a counter or expression that is always
// zero, then that operand can be replaced with `CovTerm::Zero`.
let maybe_set_operand_to_zero = |operand: &mut CovTerm| {
if let CovTerm::Expression(id) = *operand {
assert_operand_expression_is_lower(id);
}

if is_zero_term(&self.ids_info.counters_seen, &zero_expressions, *operand) {
*operand = CovTerm::Zero;
}
};
maybe_set_operand_to_zero(&mut lhs);
maybe_set_operand_to_zero(&mut rhs);

// Coverage counter values cannot be negative, so if an expression
// involves subtraction from zero, assume that its RHS must also be zero.
// (Do this after simplifications that could set the LHS to zero.)
if lhs == CovTerm::Zero && op == Op::Subtract {
rhs = CovTerm::Zero;
}

// After the above simplifications, if both operands are zero, then
// we know that this expression is always zero too.
if lhs == CovTerm::Zero && rhs == CovTerm::Zero {
zero_expressions.insert(id);
}
}

zero_expressions
}

pub(crate) fn into_finished(self) -> FunctionCoverage<'tcx> {
let zero_expressions = self.identify_zero_expressions();
let FunctionCoverageCollector { function_coverage_info, ids_info, is_used, .. } = self;

FunctionCoverage { function_coverage_info, ids_info, is_used, zero_expressions }
FunctionCoverage { function_coverage_info, ids_info, is_used }
}
}

pub(crate) struct FunctionCoverage<'tcx> {
pub(crate) function_coverage_info: &'tcx FunctionCoverageInfo,
ids_info: &'tcx CoverageIdsInfo,
is_used: bool,

zero_expressions: ZeroExpressions,
}

impl<'tcx> FunctionCoverage<'tcx> {
Expand Down Expand Up @@ -195,37 +118,6 @@ impl<'tcx> FunctionCoverage<'tcx> {
}

fn is_zero_term(&self, term: CovTerm) -> bool {
!self.is_used || is_zero_term(&self.ids_info.counters_seen, &self.zero_expressions, term)
}
}

/// Set of expression IDs that are known to always evaluate to zero.
/// Any mapping or expression operand that refers to these expressions can have
/// that reference replaced with a constant zero value.
#[derive(Default)]
struct ZeroExpressions(FxIndexSet<ExpressionId>);

impl ZeroExpressions {
fn insert(&mut self, id: ExpressionId) {
self.0.insert(id);
}

fn contains(&self, id: ExpressionId) -> bool {
self.0.contains(&id)
}
}

/// Returns `true` if the given term is known to have a value of zero, taking
/// into account knowledge of which counters are unused and which expressions
/// are always zero.
fn is_zero_term(
counters_seen: &BitSet<CounterId>,
zero_expressions: &ZeroExpressions,
term: CovTerm,
) -> bool {
match term {
CovTerm::Zero => true,
CovTerm::Counter(id) => !counters_seen.contains(id),
CovTerm::Expression(id) => zero_expressions.contains(id),
!self.is_used || self.ids_info.is_zero_term(term)
}
}
13 changes: 12 additions & 1 deletion compiler/rustc_middle/src/mir/coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ pub struct MCDCDecisionSpan {
#[derive(Clone, TyEncodable, TyDecodable, Debug, HashStable)]
pub struct CoverageIdsInfo {
pub counters_seen: BitSet<CounterId>,
pub expressions_seen: BitSet<ExpressionId>,
pub zero_expressions: BitSet<ExpressionId>,
}

impl CoverageIdsInfo {
Expand All @@ -337,4 +337,15 @@ impl CoverageIdsInfo {
// used. Fixing this would require adding a renumbering step somewhere.
self.counters_seen.last_set_in(..).map_or(0, |max| max.as_u32() + 1)
}

/// Returns `true` if the given term is known to have a value of zero, taking
/// into account knowledge of which counters are unused and which expressions
/// are always zero.
pub fn is_zero_term(&self, term: CovTerm) -> bool {
match term {
CovTerm::Zero => true,
CovTerm::Counter(id) => !self.counters_seen.contains(id),
CovTerm::Expression(id) => self.zero_expressions.contains(id),
}
}
}
105 changes: 101 additions & 4 deletions compiler/rustc_mir_transform/src/coverage/query.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use rustc_data_structures::captures::Captures;
use rustc_index::bit_set::BitSet;
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
use rustc_middle::mir::coverage::{CovTerm, CoverageIdsInfo, CoverageKind, MappingKind};
use rustc_middle::mir::coverage::{
CounterId, CovTerm, CoverageIdsInfo, CoverageKind, Expression, ExpressionId,
FunctionCoverageInfo, MappingKind, Op,
};
use rustc_middle::mir::{Body, Statement, StatementKind};
use rustc_middle::query::TyCtxtAt;
use rustc_middle::ty::{self, TyCtxt};
Expand Down Expand Up @@ -87,10 +90,10 @@ fn coverage_ids_info<'tcx>(
) -> CoverageIdsInfo {
let mir_body = tcx.instance_mir(instance_def);

let Some(fn_cov_info) = mir_body.function_coverage_info.as_ref() else {
let Some(fn_cov_info) = mir_body.function_coverage_info.as_deref() else {
return CoverageIdsInfo {
counters_seen: BitSet::new_empty(0),
expressions_seen: BitSet::new_empty(0),
zero_expressions: BitSet::new_empty(0),
};
};

Expand Down Expand Up @@ -123,7 +126,10 @@ fn coverage_ids_info<'tcx>(
}
}

CoverageIdsInfo { counters_seen, expressions_seen }
let zero_expressions =
identify_zero_expressions(fn_cov_info, &counters_seen, &expressions_seen);

CoverageIdsInfo { counters_seen, zero_expressions }
}

fn all_coverage_in_mir_body<'a, 'tcx>(
Expand All @@ -141,3 +147,94 @@ fn is_inlined(body: &Body<'_>, statement: &Statement<'_>) -> bool {
let scope_data = &body.source_scopes[statement.source_info.scope];
scope_data.inlined.is_some() || scope_data.inlined_parent_scope.is_some()
}

/// Identify expressions that will always have a value of zero, and note
/// their IDs in a `BitSet`. Mappings that refer to a zero expression
/// can instead become mappings to a constant zero value.
///
/// This function mainly exists to preserve the simplifications that were
/// already being performed by the Rust-side expression renumbering, so that
/// the resulting coverage mappings don't get worse.
fn identify_zero_expressions(
fn_cov_info: &FunctionCoverageInfo,
counters_seen: &BitSet<CounterId>,
expressions_seen: &BitSet<ExpressionId>,
) -> BitSet<ExpressionId> {
// The set of expressions that either were optimized out entirely, or
// have zero as both of their operands, and will therefore always have
// a value of zero. Other expressions that refer to these as operands
// can have those operands replaced with `CovTerm::Zero`.
let mut zero_expressions = BitSet::new_empty(fn_cov_info.expressions.len());

// Simplify a copy of each expression based on lower-numbered expressions,
// and then update the set of always-zero expressions if necessary.
// (By construction, expressions can only refer to other expressions
// that have lower IDs, so one pass is sufficient.)
for (id, expression) in fn_cov_info.expressions.iter_enumerated() {
if !expressions_seen.contains(id) {
// If an expression was not seen, it must have been optimized away,
// so any operand that refers to it can be replaced with zero.
zero_expressions.insert(id);
continue;
}

// We don't need to simplify the actual expression data in the
// expressions list; we can just simplify a temporary copy and then
// use that to update the set of always-zero expressions.
let Expression { mut lhs, op, mut rhs } = *expression;

// If an expression has an operand that is also an expression, the
// operand's ID must be strictly lower. This is what lets us find
// all zero expressions in one pass.
let assert_operand_expression_is_lower = |operand_id: ExpressionId| {
assert!(
operand_id < id,
"Operand {operand_id:?} should be less than {id:?} in {expression:?}",
)
};

// If an operand refers to a counter or expression that is always
// zero, then that operand can be replaced with `CovTerm::Zero`.
let maybe_set_operand_to_zero = |operand: &mut CovTerm| {
if let CovTerm::Expression(id) = *operand {
assert_operand_expression_is_lower(id);
}

if is_zero_term(&counters_seen, &zero_expressions, *operand) {
*operand = CovTerm::Zero;
}
};
maybe_set_operand_to_zero(&mut lhs);
maybe_set_operand_to_zero(&mut rhs);

// Coverage counter values cannot be negative, so if an expression
// involves subtraction from zero, assume that its RHS must also be zero.
// (Do this after simplifications that could set the LHS to zero.)
if lhs == CovTerm::Zero && op == Op::Subtract {
rhs = CovTerm::Zero;
}

// After the above simplifications, if both operands are zero, then
// we know that this expression is always zero too.
if lhs == CovTerm::Zero && rhs == CovTerm::Zero {
zero_expressions.insert(id);
}
}

zero_expressions
}

/// Returns `true` if the given term is known to have a value of zero, taking
/// into account knowledge of which counters are unused and which expressions
/// are always zero.
fn is_zero_term(
counters_seen: &BitSet<CounterId>,
zero_expressions: &BitSet<ExpressionId>,
term: CovTerm,
) -> bool {
match term {
CovTerm::Zero => true,
CovTerm::Counter(id) => !counters_seen.contains(id),
CovTerm::Expression(id) => zero_expressions.contains(id),
}
}