[llvm] [Instrumentor] Improve filtering for flag values (PR #206667)
Ethan Luis McDonough via llvm-commits
llvm-commits at lists.llvm.org
Tue Jun 30 00:22:35 PDT 2026
https://github.com/EthanLuisMcDonough created https://github.com/llvm/llvm-project/pull/206667
This pull request modifies the instrumentor's filter expression parser. It introduces the logical not operator (`!`), flag property access (e.g. `flags.nuw`), and binary literals.
>From 633b0a35dc3fe4b466567d2b7c1b570b0c0f969b Mon Sep 17 00:00:00 2001
From: Ethan Luis McDonough <ethanluismcdonough at gmail.com>
Date: Mon, 29 Jun 2026 19:18:35 -0500
Subject: [PATCH] Add ! operator + flag properties + binary literals to filter
expressions
---
.../llvm/Transforms/IPO/Instrumentor.h | 5 ++
llvm/lib/Transforms/IPO/Instrumentor.cpp | 18 ++++++
llvm/lib/Transforms/IPO/InstrumentorUtils.cpp | 61 +++++++++++++++----
.../Instrumentor/test_filter_flags.ll | 35 +++++++++++
.../test_filter_flags_config.json | 31 ++++++++++
.../Instrumentor/test_filter_not_error.ll | 6 ++
.../test_filter_not_error_config.json | 13 ++++
7 files changed, 156 insertions(+), 13 deletions(-)
create mode 100644 llvm/test/Instrumentation/Instrumentor/test_filter_flags.ll
create mode 100644 llvm/test/Instrumentation/Instrumentor/test_filter_flags_config.json
create mode 100644 llvm/test/Instrumentation/Instrumentor/test_filter_not_error.ll
create mode 100644 llvm/test/Instrumentation/Instrumentor/test_filter_not_error_config.json
diff --git a/llvm/include/llvm/Transforms/IPO/Instrumentor.h b/llvm/include/llvm/Transforms/IPO/Instrumentor.h
index fceb9e58b9791..5440e5efd12f6 100644
--- a/llvm/include/llvm/Transforms/IPO/Instrumentor.h
+++ b/llvm/include/llvm/Transforms/IPO/Instrumentor.h
@@ -473,6 +473,9 @@ struct InstrumentationOpportunity {
/// may be disabled and will not be passed to the function call.
SmallVector<IRTArg> IRTArgs;
+ /// Instruction flag names and their integer bitmask values.
+ DenseMap<StringRef, int32_t> FlagNames;
+
/// Whether the opportunity is enabled.
bool Enabled = true;
@@ -1215,6 +1218,7 @@ struct NumericIO final
LLVM_ABI void init(InstrumentationConfig &IConf,
InstrumentorIRBuilderTy &IIRB,
ConfigTy *UserConfig = nullptr);
+ LLVM_ABI void addFlagNames();
LLVM_ABI static Value *getFlags(Value &V, Type &Ty,
InstrumentationConfig &IConf,
@@ -1259,6 +1263,7 @@ struct CompareIO final
LLVM_ABI void init(InstrumentationConfig &IConf,
InstrumentorIRBuilderTy &IIRB,
ConfigTy *UserConfig = nullptr);
+ LLVM_ABI void addFlagNames();
LLVM_ABI static Value *getOperandTypeId(Value &V, Type &Ty,
InstrumentationConfig &IConf,
diff --git a/llvm/lib/Transforms/IPO/Instrumentor.cpp b/llvm/lib/Transforms/IPO/Instrumentor.cpp
index 3b52e3e1f4605..9558a2dbd536c 100644
--- a/llvm/lib/Transforms/IPO/Instrumentor.cpp
+++ b/llvm/lib/Transforms/IPO/Instrumentor.cpp
@@ -1830,6 +1830,15 @@ Value *NumericIO::getFlags(Value &V, Type &Ty, InstrumentationConfig &IConf,
return getCI(&Ty, Flag);
}
+void NumericIO::addFlagNames() {
+ FlagNames.insert({"nsw", NUMERIC_FLAG_NO_SIGNED_WRAP});
+ FlagNames.insert({"nuw", NUMERIC_FLAG_NO_UNSIGNED_WRAP});
+ FlagNames.insert({"nnan", NUMERIC_FLAG_HAS_NO_NANS});
+ FlagNames.insert({"ninf", NUMERIC_FLAG_HAS_NO_INFS});
+ FlagNames.insert({"nsz", NUMERIC_FLAG_HAS_NO_SIGNED_ZEROS});
+ FlagNames.insert({"exact", NUMERIC_FLAG_IS_EXACT});
+}
+
void NumericIO::init(InstrumentationConfig &IConf,
InstrumentorIRBuilderTy &IIRB, ConfigTy *UserConfig) {
if (UserConfig)
@@ -1873,6 +1882,7 @@ void NumericIO::init(InstrumentationConfig &IConf,
"A bitmask value signaling which instruction flags are present.",
IRTArg::NONE, getFlags));
addCommonArgs(IConf, IIRB.Ctx, Config.has(PassId));
+ addFlagNames();
IConf.addChoice(*this, IIRB.Ctx);
}
@@ -1897,6 +1907,13 @@ Value *CompareIO::getPredicate(Value &V, Type &Ty, InstrumentationConfig &IConf,
return getCI(&Ty, CI->getPredicate());
}
+void CompareIO::addFlagNames() {
+ FlagNames.insert({"samesign", COMPARE_FLAG_SAMESIGN});
+ FlagNames.insert({"nnan", COMPARE_FLAG_HAS_NO_NANS});
+ FlagNames.insert({"ninf", COMPARE_FLAG_HAS_NO_INFS});
+ FlagNames.insert({"nsz", COMPARE_FLAG_HAS_NO_SIGNED_ZEROS});
+}
+
Value *CompareIO::getFlags(Value &V, Type &Ty, InstrumentationConfig &IConf,
InstrumentorIRBuilderTy &IIRB) {
auto &I = cast<Instruction>(V);
@@ -1971,6 +1988,7 @@ void CompareIO::init(InstrumentationConfig &IConf,
IRTArg(IIRB.Int64Ty, "flags",
"A bitmask value signaling which instruction flags are present.",
IRTArg::NONE, getFlags));
+ addFlagNames();
addCommonArgs(IConf, IIRB.Ctx, Config.has(PassId));
IConf.addChoice(*this, IIRB.Ctx);
}
diff --git a/llvm/lib/Transforms/IPO/InstrumentorUtils.cpp b/llvm/lib/Transforms/IPO/InstrumentorUtils.cpp
index 7030002824761..00e379f0074cf 100644
--- a/llvm/lib/Transforms/IPO/InstrumentorUtils.cpp
+++ b/llvm/lib/Transforms/IPO/InstrumentorUtils.cpp
@@ -30,6 +30,7 @@ class FilterEvaluator {
DenseMap<StringRef, StringRef> &StringPropertyValues;
DenseMap<StringRef, Value *> &PointerPropertyValues;
DenseMap<StringRef, PropertyType> &DynamicProperties;
+ DenseMap<StringRef, int32_t> &FlagNameVals;
size_t Pos = 0;
public:
@@ -37,11 +38,12 @@ class FilterEvaluator {
DenseMap<StringRef, int64_t> &IntPropertyValues,
DenseMap<StringRef, StringRef> &StringPropertyValues,
DenseMap<StringRef, Value *> &PointerPropertyValues,
- DenseMap<StringRef, PropertyType> &DynamicProperties)
+ DenseMap<StringRef, PropertyType> &DynamicProperties,
+ DenseMap<StringRef, int32_t> &FlagNameVals)
: Expr(Expr), IntPropertyValues(IntPropertyValues),
StringPropertyValues(StringPropertyValues),
PointerPropertyValues(PointerPropertyValues),
- DynamicProperties(DynamicProperties) {}
+ DynamicProperties(DynamicProperties), FlagNameVals(FlagNameVals) {}
Expected<bool> evaluate() {
if (Expr.empty())
@@ -147,6 +149,13 @@ class FilterEvaluator {
Expected<bool> parseComparison() {
skipWhitespace();
+ // Check for logical not operator.
+ bool LogicalNot = false;
+ if (Pos < Expr.size() && Expr[Pos] == '!') {
+ LogicalNot = true;
+ ++Pos;
+ }
+
// Parse left-hand side (property name).
size_t Start = Pos;
while (Pos < Expr.size() && (std::isalnum(Expr[Pos]) || Expr[Pos] == '_'))
@@ -159,20 +168,34 @@ class FilterEvaluator {
skipWhitespace();
- // Check for .startswith() method call.
+ // Parse property fields and methods.
if (Pos < Expr.size() && Expr[Pos] == '.') {
++Pos;
skipWhitespace();
- // Parse method name.
+ // Parse field name.
Start = Pos;
while (Pos < Expr.size() && std::isalpha(Expr[Pos]))
++Pos;
- StringRef MethodName = Expr.slice(Start, Pos);
+ StringRef FieldName = Expr.slice(Start, Pos);
skipWhitespace();
- if (MethodName == "startswith") {
+ // Handle flag values
+ if (PropName == "flags") {
+ auto FlagValIt = IntPropertyValues.find("flags");
+ if (FlagValIt != IntPropertyValues.end()) {
+ auto FlagNameIt = FlagNameVals.find(FieldName);
+ if (FlagNameIt == FlagNameVals.end())
+ return createStringError("Invalid flag '" + FieldName + "'");
+ return ((static_cast<int32_t>(FlagValIt->second) &
+ FlagNameIt->second) == FlagNameIt->second) ^
+ LogicalNot;
+ }
+ }
+
+ // Check for .startswith() method call.
+ if (FieldName == "startswith") {
// Parse (.
if (Pos >= Expr.size() || Expr[Pos] != '(')
return createStringError(
@@ -199,7 +222,7 @@ class FilterEvaluator {
// Evaluate startswith.
auto StrIt = StringPropertyValues.find(PropName);
if (StrIt != StringPropertyValues.end())
- return StrIt->second.starts_with(*Prefix);
+ return StrIt->second.starts_with(*Prefix) ^ LogicalNot;
// If this is a dynamic string property, assume the filter passes.
if (DynamicProperties.lookup_or(PropName, UNKNOWN) == STRING)
@@ -210,9 +233,11 @@ class FilterEvaluator {
"'");
}
- return createStringError("unknown method '" + MethodName +
+ return createStringError("unknown method '" + FieldName +
"' on property '" + PropName + "'");
- }
+ } else if (LogicalNot)
+ return createStringError("expected boolean value at position " +
+ std::to_string(Start));
// Check if this is an integer property.
auto IntIt = IntPropertyValues.find(PropName);
@@ -264,8 +289,17 @@ class FilterEvaluator {
}
size_t DigitStart = Pos;
- while (Pos < Expr.size() && std::isdigit(Expr[Pos]))
- ++Pos;
+
+ // Parse binary literals.
+ if (Pos + 1 < Expr.size() && Expr[Pos] == '0' && Expr[Pos + 1] == 'b') {
+ Pos += 2;
+ while (Pos < Expr.size() && (Expr[Pos] == '0' || Expr[Pos] == '1'))
+ ++Pos;
+ } else {
+ // Parse decimal literals.
+ while (Pos < Expr.size() && std::isdigit(Expr[Pos]))
+ ++Pos;
+ }
if (Pos == DigitStart)
return createStringError("expected integer value at position " +
@@ -273,7 +307,7 @@ class FilterEvaluator {
StringRef ValueStr = Expr.slice(Start, Pos);
int64_t RHS = 0;
- if (ValueStr.getAsInteger(10, RHS))
+ if (ValueStr.getAsInteger(0, RHS))
return createStringError("invalid integer value '" + ValueStr + "'");
if (Negative)
@@ -459,7 +493,8 @@ bool llvm::instrumentor::evaluateFilter(Value &V, bool &Changed,
}
FilterEvaluator Evaluator(IO.Filter, IntPropertyValues, StringPropertyValues,
- PointerPropertyValues, DynamicProperties);
+ PointerPropertyValues, DynamicProperties,
+ IO.FlagNames);
Expected<bool> Result = Evaluator.evaluate();
if (!Result) {
diff --git a/llvm/test/Instrumentation/Instrumentor/test_filter_flags.ll b/llvm/test/Instrumentation/Instrumentor/test_filter_flags.ll
new file mode 100644
index 0000000000000..279520d77184b
--- /dev/null
+++ b/llvm/test/Instrumentation/Instrumentor/test_filter_flags.ll
@@ -0,0 +1,35 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=instrumentor -instrumentor-read-config-files=%S/test_filter_flags_config.json -S | FileCheck %s
+
+define float @test_float(float %p1, float %p2) {
+; CHECK-LABEL: define float @test_float(
+; CHECK-SAME: float [[P1:%.*]], float [[P2:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = bitcast float [[P1]] to i32
+; CHECK-NEXT: [[TMP1:%.*]] = zext i32 [[TMP0]] to i64
+; CHECK-NEXT: [[TMP2:%.*]] = bitcast float [[P2]] to i32
+; CHECK-NEXT: [[TMP3:%.*]] = zext i32 [[TMP2]] to i64
+; CHECK-NEXT: call void @__instrumentor_pre_numeric(i32 2, i32 4, i32 15, i64 [[TMP1]], i64 [[TMP3]], i64 4, i32 1) #[[ATTR0:[0-9]+]]
+; CHECK-NEXT: [[A1:%.*]] = fadd nnan float [[P1]], [[P2]]
+; CHECK-NEXT: [[A2:%.*]] = fmul float [[P1]], [[A1]]
+; CHECK-NEXT: [[A3:%.*]] = fdiv nnan ninf float [[A2]], [[P2]]
+; CHECK-NEXT: [[A4:%.*]] = fsub fast float [[A3]], [[A1]]
+; CHECK-NEXT: [[TMP4:%.*]] = bitcast float [[A3]] to i32
+; CHECK-NEXT: [[TMP5:%.*]] = zext i32 [[TMP4]] to i64
+; CHECK-NEXT: [[TMP6:%.*]] = bitcast float [[A1]] to i32
+; CHECK-NEXT: [[TMP7:%.*]] = zext i32 [[TMP6]] to i64
+; CHECK-NEXT: call void @__instrumentor_post_numeric(i32 2, i32 4, i32 17, i64 [[TMP5]], i64 [[TMP7]], i64 28, i32 -4) #[[ATTR0]]
+; CHECK-NEXT: [[TMP8:%.*]] = bitcast float [[A4]] to i32
+; CHECK-NEXT: [[TMP9:%.*]] = zext i32 [[TMP8]] to i64
+; CHECK-NEXT: call void @__instrumentor_pre_numeric(i32 2, i32 4, i32 13, i64 [[TMP9]], i64 poison, i64 16, i32 5) #[[ATTR0]]
+; CHECK-NEXT: [[A5:%.*]] = fneg nsz float [[A4]]
+; CHECK-NEXT: ret float [[A5]]
+;
+entry:
+ %a1 = fadd nnan float %p1, %p2
+ %a2 = fmul float %p1, %a1
+ %a3 = fdiv nnan ninf float %a2, %p2
+ %a4 = fsub fast float %a3, %a1
+ %a5 = fneg nsz float %a4
+ ret float %a5
+}
diff --git a/llvm/test/Instrumentation/Instrumentor/test_filter_flags_config.json b/llvm/test/Instrumentation/Instrumentor/test_filter_flags_config.json
new file mode 100644
index 0000000000000..11b7d28b94b59
--- /dev/null
+++ b/llvm/test/Instrumentation/Instrumentor/test_filter_flags_config.json
@@ -0,0 +1,31 @@
+{
+ "configuration": {
+ "runtime_prefix": "__instrumentor_"
+ },
+ "instruction_pre": {
+ "numeric": {
+ "enabled": true,
+ "filter": "(flags.nnan || flags.nsz) && !flags.ninf",
+ "type_id": true,
+ "size": true,
+ "opcode": true,
+ "left": true,
+ "right": true,
+ "flags": true,
+ "id": true
+ }
+ },
+ "instruction_post": {
+ "numeric": {
+ "enabled": true,
+ "filter": "flags == 0b11100",
+ "type_id": true,
+ "size": true,
+ "opcode": true,
+ "left": true,
+ "right": true,
+ "flags": true,
+ "id": true
+ }
+ }
+}
diff --git a/llvm/test/Instrumentation/Instrumentor/test_filter_not_error.ll b/llvm/test/Instrumentation/Instrumentor/test_filter_not_error.ll
new file mode 100644
index 0000000000000..49e69c03dfb63
--- /dev/null
+++ b/llvm/test/Instrumentation/Instrumentor/test_filter_not_error.ll
@@ -0,0 +1,6 @@
+; RUN: not opt < %s -passes=instrumentor -instrumentor-read-config-files=%S/test_filter_not_error_config.json -S 2>&1 | FileCheck %s
+
+ at X = dso_local global i32 0, align 4
+
+; CHECK: error: malformed filter expression for instrumentation opportunity 'global': expected boolean value at position 1
+; CHECK-NEXT: Filter: !name
diff --git a/llvm/test/Instrumentation/Instrumentor/test_filter_not_error_config.json b/llvm/test/Instrumentation/Instrumentor/test_filter_not_error_config.json
new file mode 100644
index 0000000000000..805f52ba2b82e
--- /dev/null
+++ b/llvm/test/Instrumentation/Instrumentor/test_filter_not_error_config.json
@@ -0,0 +1,13 @@
+{
+ "configuration": {
+ "runtime_prefix": "__instrumentor_"
+ },
+ "global_pre": {
+ "global": {
+ "enabled": true,
+ "name": true,
+ "filter": "!name",
+ "id": true
+ }
+ }
+}
More information about the llvm-commits
mailing list