Skip to content

Commit 68b157e

Browse files
committed
Add support for instrumentation rules
1 parent 02bfb5f commit 68b157e

File tree

6 files changed

+235
-28
lines changed

6 files changed

+235
-28
lines changed

compiler/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ glob = "0.3.1"
2929
config = { workspace = true }
3030
serde = { workspace = true }
3131
ron = "0.8"
32+
regex-lite = "0.1"
3233
bitflags = "2.6.0"
3334
tracing-subscriber = { workspace = true }
3435

compiler/src/config.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ pub(crate) struct LeafCompilerConfig {
1515
pub codegen_all_mir: bool,
1616
#[serde(default = "default_marker_cfg_name")]
1717
pub marker_cfg_name: String,
18+
#[serde(default)]
19+
pub rules: InstrumentationRules,
1820
}
1921

2022
fn default_override_sysroot() -> bool {
@@ -92,6 +94,83 @@ fn default_runtime_shim_crate_name() -> String {
9294
"leaf".to_string()
9395
}
9496

97+
/* NOTE: How is serde's structure is defined?
98+
* We want to make the rules easy and intuitive to define in TOML.
99+
* - The default enum representation in serde uses the variant name as the key.
100+
* - The untagged representation selects the variant based on unique fields matched.
101+
* We mostly utilize these two and flattening.
102+
* For example, a `LogicFormula` can be represented as any of the following:
103+
* ```toml
104+
* [[f]]
105+
* crate = { is_external = true }
106+
* [[f]]
107+
* not = { crate = { name = "std" } }
108+
* [[f]]
109+
* any = [{ crate = { name = "std" } }, { crate = { name = "core" } }]
110+
* [[f]]
111+
* all = [{ crate = { is_external = true } }, { crate = { name = "core" } }]
112+
* ```
113+
*/
114+
115+
#[derive(Debug, Clone, Deserialize, Default)]
116+
pub(crate) struct InstrumentationRules {
117+
#[serde(default)]
118+
pub(crate) include: Vec<EntityFilter>,
119+
#[serde(default)]
120+
pub(crate) exclude: Vec<EntityFilter>,
121+
}
122+
123+
#[derive(Debug, Clone, Deserialize)]
124+
#[serde(untagged)]
125+
pub(crate) enum EntityFilter {
126+
WholeBody(LogicFormula<EntityLocationFilter>),
127+
}
128+
129+
#[derive(Debug, Clone, Deserialize)]
130+
#[serde(untagged)]
131+
pub(crate) enum LogicFormula<T> {
132+
Not(NotFormula<T>),
133+
Any(AnyFormula<T>),
134+
All(AllFormula<T>),
135+
Atom(T),
136+
// NOTE: This variant helps with parsing empty tables by preventing the infinite search over the name of fields.
137+
None {},
138+
}
139+
140+
#[derive(Debug, Clone, Deserialize)]
141+
pub(crate) struct NotFormula<T> {
142+
#[serde(rename = "not")]
143+
pub(crate) of: Box<LogicFormula<T>>,
144+
}
145+
146+
#[derive(Debug, Clone, Deserialize)]
147+
pub(crate) struct AnyFormula<T> {
148+
#[serde(rename = "any")]
149+
pub(crate) of: Vec<LogicFormula<T>>,
150+
}
151+
152+
#[derive(Debug, Clone, Deserialize)]
153+
pub(crate) struct AllFormula<T> {
154+
#[serde(rename = "all")]
155+
pub(crate) of: Vec<LogicFormula<T>>,
156+
}
157+
158+
#[derive(Debug, Clone, Deserialize)]
159+
#[serde(rename_all = "snake_case")]
160+
pub(crate) enum EntityLocationFilter {
161+
#[serde(alias = "def_path")]
162+
DefPathMatch(String),
163+
Crate(CrateFilter),
164+
}
165+
166+
#[derive(Debug, Clone, Deserialize)]
167+
#[serde(rename_all = "snake_case")]
168+
pub(crate) enum CrateFilter {
169+
#[serde(alias = "is_external")]
170+
Externality(bool),
171+
Name(String),
172+
}
173+
95174
const CONFIG_FILENAME: &str = "leafc_config";
96175

97176
pub(super) fn load_config() -> LeafCompilerConfig {

compiler/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ mod driver_callbacks {
203203
<LeafToolAdder>,
204204
<TypeExporter>,
205205
nctfe_pass,
206-
Instrumentor::new(true, None /* FIXME */),
206+
Instrumentor::new(true, None /* FIXME */, config.rules.clone()),
207207
);
208208

209209
if config.codegen_all_mir {

compiler/src/passes/instr/decision.rs

Lines changed: 126 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,44 @@ use rustc_middle::{
77
};
88
use rustc_span::Symbol;
99

10-
use common::{log_info, log_warn};
10+
use core::ops::Deref;
1111

12-
use crate::utils::mir::TyCtxtExt;
12+
use common::{log_debug, log_info, log_warn};
1313

14-
pub(super) const TAG_INSTR_DECISION: &str = concatcp!(super::TAG_INSTRUMENTATION, "::skip");
14+
use crate::{config::InstrumentationRules, utils::mir::TyCtxtExt};
15+
16+
pub(super) const TAG_INSTR_DECISION: &str = concatcp!(super::TAG_INSTRUMENTATION, "::decision");
1517

1618
const TOOL_NAME: &str = crate::constants::TOOL_LEAF;
1719
const ATTR_NAME: &str = "instrument";
20+
pub(super) const KEY_RULES: &str = "instr_rules";
21+
const KEY_BAKED_RULES: &str = "instr_rules_baked";
1822

19-
pub(super) fn should_instrument<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> bool {
23+
pub(super) fn should_instrument<'tcx>(
24+
tcx: TyCtxt<'tcx>,
25+
body: &mut Body<'tcx>,
26+
storage: &mut dyn Storage,
27+
) -> bool {
2028
let def_id = body.source.def_id();
2129

2230
if !decide_instance_kind(&body.source.instance) {
2331
return false;
2432
}
2533

26-
if let Some((explicit, item)) = opt_instrument_attr_inheritable(tcx, def_id) {
27-
log_info!(
34+
let rules = storage.get_or_insert_with_acc(KEY_BAKED_RULES.to_owned(), |storage| {
35+
let rules = storage.get_or_default::<InstrumentationRules>(KEY_RULES.to_owned());
36+
rules.to_baked()
37+
});
38+
39+
if let Some((decision, item)) = find_inheritable_first_filtered(tcx, def_id, rules.deref()) {
40+
log_debug!(
2841
target: TAG_INSTR_DECISION,
29-
"Found explicit instrumentation attribute for {:?} on {:?} with value: {}",
42+
"Found a rule for instrumentation of {:?} on {:?} with decision: {}",
3043
def_id,
3144
item,
32-
explicit
45+
decision
3346
);
34-
return explicit;
47+
return decision;
3548
}
3649

3750
if is_lang_start_item(tcx, def_id) {
@@ -81,16 +94,27 @@ fn decide_instance_kind(kind: &InstanceKind) -> bool {
8194
}
8295
}
8396

84-
/// Returns the value of the `instrument` attribute if it is placed on the item or one of its ancestors.
85-
fn opt_instrument_attr_inheritable<'tcx>(
97+
fn find_inheritable_first_filtered<'tcx>(
8698
tcx: TyCtxt<'tcx>,
8799
def_id: DefId,
100+
rules: &dyn Fn(TyCtxt<'tcx>, DefId) -> Option<bool>,
88101
) -> Option<(bool, DefId)> {
89102
let mut current = def_id;
90103
loop {
91-
let attr = opt_instrument_attr(tcx, current);
92-
if attr.is_some() {
93-
return attr.map(|v| (v, current));
104+
// Attributes take precedence over filters.
105+
if let Some(explicit) = opt_instrument_attr(tcx, current) {
106+
log_info!(
107+
target: TAG_INSTR_DECISION,
108+
"Found explicit instrumentation attribute for {:?} on {:?} with value: {}",
109+
def_id,
110+
current,
111+
explicit
112+
);
113+
return Some((explicit, current));
114+
}
115+
116+
if let Some(include) = rules(tcx, current) {
117+
return Some((include, current));
94118
}
95119

96120
let parent = tcx.opt_parent(current);
@@ -817,3 +841,91 @@ mod intrinsics {
817841
}
818842
}
819843
pub(super) use intrinsics::{decide_intrinsic_call, AtomicIntrinsicKind, IntrinsicDecision};
844+
845+
use super::{Storage, StorageExt};
846+
847+
mod rules {
848+
use super::*;
849+
850+
use crate::config::{
851+
AllFormula, AnyFormula, CrateFilter, EntityFilter, EntityLocationFilter,
852+
InstrumentationRules, LogicFormula, NotFormula,
853+
};
854+
855+
type Predicate = Box<dyn Fn(TyCtxt<'_>, DefId) -> bool>;
856+
857+
trait ToPredicate {
858+
fn to_predicate<'tcx>(&self) -> Predicate;
859+
}
860+
861+
impl InstrumentationRules {
862+
pub(super) fn to_baked<'tcx>(&self) -> impl Fn(TyCtxt<'_>, DefId) -> Option<bool> {
863+
fn to_predicate(filters: &[EntityFilter]) -> Predicate {
864+
let preds = filters
865+
.iter()
866+
.filter_map(|f| match &f {
867+
EntityFilter::WholeBody(formula) => Some(formula),
868+
})
869+
.map(|f| f.to_predicate())
870+
.collect::<Vec<_>>();
871+
Box::new(move |tcx, def_id| preds.iter().any(|p| p(tcx, def_id)))
872+
}
873+
874+
let include = to_predicate(&self.include);
875+
let exclude = to_predicate(&self.exclude);
876+
877+
move |tcx, def_id| {
878+
if include(tcx, def_id) {
879+
Some(true)
880+
} else if exclude(tcx, def_id) {
881+
Some(false)
882+
} else {
883+
None
884+
}
885+
}
886+
}
887+
}
888+
889+
impl<T: ToPredicate> ToPredicate for LogicFormula<T> {
890+
fn to_predicate<'tcx>(&self) -> Predicate {
891+
match self {
892+
LogicFormula::None {} => Box::new(|_, _| false),
893+
LogicFormula::Atom(f) => f.to_predicate(),
894+
LogicFormula::Not(NotFormula { of }) => {
895+
let pred = of.to_predicate();
896+
Box::new(move |tcx, def_id| !pred(tcx, def_id))
897+
}
898+
LogicFormula::Any(AnyFormula { of }) => {
899+
let preds = of.iter().map(|f| f.to_predicate()).collect::<Vec<_>>();
900+
Box::new(move |tcx, def_id| preds.iter().any(|p| p(tcx, def_id)))
901+
}
902+
LogicFormula::All(AllFormula { of }) => {
903+
let preds = of.iter().map(|f| f.to_predicate()).collect::<Vec<_>>();
904+
Box::new(move |tcx, def_id| preds.iter().all(|p| p(tcx, def_id)))
905+
}
906+
}
907+
}
908+
}
909+
910+
impl ToPredicate for EntityLocationFilter {
911+
fn to_predicate<'tcx>(&self) -> Predicate {
912+
match self {
913+
EntityLocationFilter::Crate(crate_filter) => match crate_filter.clone() {
914+
CrateFilter::Externality(is_external) => {
915+
Box::new(move |_, def_id| def_id.is_local() != is_external)
916+
}
917+
CrateFilter::Name(name) => {
918+
Box::new(move |tcx, def_id| tcx.crate_name(def_id.krate).as_str() == name)
919+
}
920+
},
921+
EntityLocationFilter::DefPathMatch(pattern) => {
922+
let regex = regex_lite::Regex::new(&pattern).expect("Invalid regex pattern");
923+
Box::new(move |tcx, def_id| {
924+
let def_path = tcx.def_path_str(def_id);
925+
regex.is_match(&def_path)
926+
})
927+
}
928+
}
929+
}
930+
}
931+
}

compiler/src/passes/instr/mod.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use common::{log_debug, log_info, log_warn};
1919
use std::{collections::HashSet, num::NonZeroUsize, sync::atomic};
2020

2121
use crate::{
22+
config::InstrumentationRules,
2223
mir_transform::{self, BodyInstrumentationUnit, JumpTargetModifier},
2324
passes::{instr::call::context::PriItems, StorageExt},
2425
utils::mir::TyCtxtExt,
@@ -50,13 +51,19 @@ const KEY_TOTAL_COUNT: &str = "total_body_count";
5051
pub(crate) struct Instrumentor {
5152
enabled: bool,
5253
total_body_count: Option<NonZeroUsize>,
54+
rules: Option<InstrumentationRules>,
5355
}
5456

5557
impl Instrumentor {
56-
pub(crate) fn new(enabled: bool, total_body_count: Option<NonZeroUsize>) -> Self {
58+
pub(crate) fn new(
59+
enabled: bool,
60+
total_body_count: Option<NonZeroUsize>,
61+
filters: InstrumentationRules,
62+
) -> Self {
5763
Self {
5864
enabled,
5965
total_body_count,
66+
rules: Some(filters),
6067
}
6168
}
6269
}
@@ -76,6 +83,9 @@ impl CompilationPass for Instrumentor {
7683
// As early as possible, we use transform_ast to set the enabled flag.
7784
storage.get_or_insert_with(KEY_ENABLED.to_owned(), || self.enabled);
7885
storage.get_or_insert_with(KEY_TOTAL_COUNT.to_owned(), || self.total_body_count);
86+
storage.get_or_insert_with(decision::KEY_RULES.to_owned(), || {
87+
self.rules.take().unwrap()
88+
});
7989
rustc_driver::Compilation::Continue
8090
}
8191

@@ -97,7 +107,7 @@ fn transform<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, storage: &mut dyn S
97107

98108
let def_id = body.source.def_id();
99109

100-
if !decision::should_instrument(tcx, body) {
110+
if !decision::should_instrument(tcx, body, storage) {
101111
log_info!(
102112
target: decision::TAG_INSTR_DECISION,
103113
"Skipping instrumentation for {:#?}",

0 commit comments

Comments
 (0)