[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