CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
480 lines
15 KiB
C++
480 lines
15 KiB
C++
//===-- InstrumentorUtils.cpp - Highly configurable instrumentation pass --===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "llvm/Transforms/IPO/InstrumentorUtils.h"
|
|
#include "llvm/Transforms/IPO/Instrumentor.h"
|
|
|
|
#include "llvm/ADT/DenseMap.h"
|
|
#include "llvm/IR/DiagnosticInfo.h"
|
|
|
|
using namespace llvm;
|
|
using namespace llvm::instrumentor;
|
|
|
|
namespace {
|
|
enum PropertyType { INT, STRING, POINTER, UNKNOWN };
|
|
|
|
/// Simple filter expression evaluator for instrumentation opportunities.
|
|
/// Supports integer comparisons (==, !=, <, >, <=, >=), string comparisons
|
|
/// (==, !=), pointer comparisons (==, !=) against null, string prefix checks
|
|
/// (startswith), and logical operators (&&, ||).
|
|
class FilterEvaluator {
|
|
StringRef Expr;
|
|
DenseMap<StringRef, int64_t> &IntPropertyValues;
|
|
DenseMap<StringRef, StringRef> &StringPropertyValues;
|
|
DenseMap<StringRef, Value *> &PointerPropertyValues;
|
|
DenseMap<StringRef, PropertyType> &DynamicProperties;
|
|
size_t Pos = 0;
|
|
|
|
public:
|
|
FilterEvaluator(StringRef Expr,
|
|
DenseMap<StringRef, int64_t> &IntPropertyValues,
|
|
DenseMap<StringRef, StringRef> &StringPropertyValues,
|
|
DenseMap<StringRef, Value *> &PointerPropertyValues,
|
|
DenseMap<StringRef, PropertyType> &DynamicProperties)
|
|
: Expr(Expr), IntPropertyValues(IntPropertyValues),
|
|
StringPropertyValues(StringPropertyValues),
|
|
PointerPropertyValues(PointerPropertyValues),
|
|
DynamicProperties(DynamicProperties) {}
|
|
|
|
Expected<bool> evaluate() {
|
|
if (Expr.empty())
|
|
return true;
|
|
|
|
Expected<bool> Result = parseOrExpr();
|
|
|
|
// Check if we consumed the entire expression.
|
|
skipWhitespace();
|
|
if (Pos < Expr.size() && Result)
|
|
return createStringError(
|
|
"unexpected characters at position " + std::to_string(Pos) + ": '" +
|
|
Expr.substr(Pos, std::min<size_t>(10, Expr.size() - Pos)).str() +
|
|
"'");
|
|
|
|
return Result;
|
|
}
|
|
|
|
private:
|
|
void skipWhitespace() {
|
|
while (Pos < Expr.size() && std::isspace(Expr[Pos]))
|
|
++Pos;
|
|
}
|
|
|
|
Expected<bool> parseOrExpr() {
|
|
Expected<bool> Result = parseAndExpr();
|
|
while (Result) {
|
|
skipWhitespace();
|
|
if (Pos + 1 < Expr.size() && Expr[Pos] == '|' && Expr[Pos + 1] == '|') {
|
|
Pos += 2;
|
|
Expected<bool> NextResult = parseAndExpr();
|
|
if (!NextResult)
|
|
return NextResult;
|
|
*Result |= *NextResult;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
Expected<bool> parseAndExpr() {
|
|
Expected<bool> Result = parsePrimary();
|
|
while (Result) {
|
|
skipWhitespace();
|
|
if (Pos + 1 < Expr.size() && Expr[Pos] == '&' && Expr[Pos + 1] == '&') {
|
|
Pos += 2;
|
|
Expected<bool> NextResult = parsePrimary();
|
|
if (!NextResult)
|
|
return NextResult;
|
|
*Result &= *NextResult;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
Expected<bool> parsePrimary() {
|
|
skipWhitespace();
|
|
|
|
// Check for opening parenthesis.
|
|
if (Pos < Expr.size() && Expr[Pos] == '(') {
|
|
++Pos; // Skip '('
|
|
Expected<bool> Result = parseOrExpr();
|
|
|
|
skipWhitespace();
|
|
if (Result && (Pos >= Expr.size() || Expr[Pos] != ')'))
|
|
return createStringError("expected ')' at position " +
|
|
std::to_string(Pos));
|
|
|
|
// Skip ')'.
|
|
++Pos;
|
|
return Result;
|
|
}
|
|
|
|
// Otherwise parse a comparison.
|
|
return parseComparison();
|
|
}
|
|
|
|
// Parse a quoted string literal.
|
|
Expected<StringRef> parseStringLiteral() {
|
|
skipWhitespace();
|
|
if (Pos >= Expr.size() || Expr[Pos] != '"')
|
|
return createStringError("expected string literal at position " +
|
|
std::to_string(Pos));
|
|
|
|
// Skip opening quote.
|
|
++Pos;
|
|
size_t Start = Pos;
|
|
while (Pos < Expr.size() && Expr[Pos] != '"')
|
|
++Pos;
|
|
|
|
if (Pos >= Expr.size())
|
|
return createStringError("unclosed string literal starting at position " +
|
|
std::to_string(Start - 1));
|
|
|
|
StringRef Result = Expr.slice(Start, Pos);
|
|
// Skip closing quote.
|
|
++Pos;
|
|
return Result;
|
|
}
|
|
|
|
Expected<bool> parseComparison() {
|
|
skipWhitespace();
|
|
|
|
// Parse left-hand side (property name).
|
|
size_t Start = Pos;
|
|
while (Pos < Expr.size() && (std::isalnum(Expr[Pos]) || Expr[Pos] == '_'))
|
|
++Pos;
|
|
|
|
StringRef PropName = Expr.slice(Start, Pos);
|
|
if (PropName.empty())
|
|
return createStringError("expected property name at position " +
|
|
std::to_string(Pos));
|
|
|
|
skipWhitespace();
|
|
|
|
// Check for .startswith() method call.
|
|
if (Pos < Expr.size() && Expr[Pos] == '.') {
|
|
++Pos;
|
|
skipWhitespace();
|
|
|
|
// Parse method name.
|
|
Start = Pos;
|
|
while (Pos < Expr.size() && std::isalpha(Expr[Pos]))
|
|
++Pos;
|
|
|
|
StringRef MethodName = Expr.slice(Start, Pos);
|
|
skipWhitespace();
|
|
|
|
if (MethodName == "startswith") {
|
|
// Parse (.
|
|
if (Pos >= Expr.size() || Expr[Pos] != '(')
|
|
return createStringError(
|
|
"expected '(' after 'startswith' at position " +
|
|
std::to_string(Pos));
|
|
|
|
++Pos;
|
|
|
|
// Parse string argument.
|
|
auto Prefix = parseStringLiteral();
|
|
if (!Prefix)
|
|
return Prefix.takeError();
|
|
|
|
skipWhitespace();
|
|
|
|
// Parse )
|
|
if (Pos >= Expr.size() || Expr[Pos] != ')')
|
|
return createStringError(
|
|
"expected ')' to close 'startswith' call at position " +
|
|
std::to_string(Pos));
|
|
|
|
++Pos;
|
|
|
|
// Evaluate startswith.
|
|
auto StrIt = StringPropertyValues.find(PropName);
|
|
if (StrIt != StringPropertyValues.end())
|
|
return StrIt->second.starts_with(*Prefix);
|
|
|
|
// If this is a dynamic string property, assume the filter passes.
|
|
if (DynamicProperties.lookup_or(PropName, UNKNOWN) == STRING)
|
|
return true;
|
|
|
|
return createStringError(
|
|
"startswith is only valid on string properties not '" + PropName +
|
|
"'");
|
|
}
|
|
|
|
return createStringError("unknown method '" + MethodName.str() +
|
|
"' on property '" + PropName.str() + "'");
|
|
}
|
|
|
|
// Check if this is an integer property.
|
|
auto IntIt = IntPropertyValues.find(PropName);
|
|
if (IntIt != IntPropertyValues.end()) {
|
|
int64_t LHS = IntIt->second;
|
|
|
|
// Parse operator.
|
|
enum OpKind { EQ, NE, LT, GT, LE, GE } Op;
|
|
if (Pos < Expr.size()) {
|
|
if (Expr[Pos] == '=' && Pos + 1 < Expr.size() && Expr[Pos + 1] == '=') {
|
|
Op = EQ;
|
|
Pos += 2;
|
|
} else if (Expr[Pos] == '!' && Pos + 1 < Expr.size() &&
|
|
Expr[Pos + 1] == '=') {
|
|
Op = NE;
|
|
Pos += 2;
|
|
} else if (Expr[Pos] == '<' && Pos + 1 < Expr.size() &&
|
|
Expr[Pos + 1] == '=') {
|
|
Op = LE;
|
|
Pos += 2;
|
|
} else if (Expr[Pos] == '>' && Pos + 1 < Expr.size() &&
|
|
Expr[Pos + 1] == '=') {
|
|
Op = GE;
|
|
Pos += 2;
|
|
} else if (Expr[Pos] == '<') {
|
|
Op = LT;
|
|
Pos += 1;
|
|
} else if (Expr[Pos] == '>') {
|
|
Op = GT;
|
|
Pos += 1;
|
|
} else {
|
|
return createStringError("expected comparison operator (==, !=, <, "
|
|
">, <=, >=) at position " +
|
|
std::to_string(Pos));
|
|
}
|
|
} else {
|
|
return createStringError(
|
|
"expected comparison operator after property '" + PropName.str() +
|
|
"'");
|
|
}
|
|
|
|
skipWhitespace();
|
|
|
|
// Parse right-hand side (constant value).
|
|
Start = Pos;
|
|
bool Negative = false;
|
|
if (Pos < Expr.size() && Expr[Pos] == '-') {
|
|
Negative = true;
|
|
++Pos;
|
|
}
|
|
|
|
size_t DigitStart = Pos;
|
|
while (Pos < Expr.size() && std::isdigit(Expr[Pos]))
|
|
++Pos;
|
|
|
|
if (Pos == DigitStart)
|
|
return createStringError("expected integer value at position " +
|
|
std::to_string(Pos));
|
|
|
|
StringRef ValueStr = Expr.slice(Start, Pos);
|
|
int64_t RHS = 0;
|
|
if (ValueStr.getAsInteger(10, RHS))
|
|
return createStringError("invalid integer value '" + ValueStr.str() +
|
|
"'");
|
|
|
|
if (Negative)
|
|
RHS = -RHS;
|
|
|
|
// Evaluate comparison.
|
|
switch (Op) {
|
|
case EQ:
|
|
return LHS == RHS;
|
|
case NE:
|
|
return LHS != RHS;
|
|
case LT:
|
|
return LHS < RHS;
|
|
case GT:
|
|
return LHS > RHS;
|
|
case LE:
|
|
return LHS <= RHS;
|
|
case GE:
|
|
return LHS >= RHS;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Check if this is a string property.
|
|
auto StrIt = StringPropertyValues.find(PropName);
|
|
if (StrIt != StringPropertyValues.end()) {
|
|
StringRef LHS = StrIt->second;
|
|
|
|
// Parse operator (only == and != for strings).
|
|
enum OpKind { EQ, NE } Op;
|
|
if (Pos < Expr.size()) {
|
|
if (Expr[Pos] == '=' && Pos + 1 < Expr.size() && Expr[Pos + 1] == '=') {
|
|
Op = EQ;
|
|
Pos += 2;
|
|
} else if (Expr[Pos] == '!' && Pos + 1 < Expr.size() &&
|
|
Expr[Pos + 1] == '=') {
|
|
Op = NE;
|
|
Pos += 2;
|
|
} else {
|
|
return createStringError("string property '" + PropName.str() +
|
|
"' only supports == and != operators");
|
|
}
|
|
} else {
|
|
return createStringError(
|
|
"expected comparison operator after string property '" +
|
|
PropName.str() + "'");
|
|
}
|
|
|
|
skipWhitespace();
|
|
|
|
// Parse right-hand side (string literal).
|
|
auto RHS = parseStringLiteral();
|
|
if (!RHS)
|
|
return RHS.takeError();
|
|
|
|
// Evaluate comparison.
|
|
switch (Op) {
|
|
case EQ:
|
|
return LHS == *RHS;
|
|
case NE:
|
|
return LHS != *RHS;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Check if this is a pointer property.
|
|
auto PtrIt = PointerPropertyValues.find(PropName);
|
|
if (PtrIt != PointerPropertyValues.end()) {
|
|
Value *LHS = PtrIt->second;
|
|
|
|
// Parse operator (only == and != for pointers).
|
|
enum OpKind { EQ, NE } Op;
|
|
if (Pos < Expr.size()) {
|
|
if (Expr[Pos] == '=' && Pos + 1 < Expr.size() && Expr[Pos + 1] == '=') {
|
|
Op = EQ;
|
|
Pos += 2;
|
|
} else if (Expr[Pos] == '!' && Pos + 1 < Expr.size() &&
|
|
Expr[Pos + 1] == '=') {
|
|
Op = NE;
|
|
Pos += 2;
|
|
} else {
|
|
return createStringError("pointer property '" + PropName.str() +
|
|
"' only supports == and != operators");
|
|
}
|
|
} else {
|
|
return createStringError(
|
|
"expected comparison operator after pointer property '" +
|
|
PropName.str() + "'");
|
|
}
|
|
|
|
skipWhitespace();
|
|
|
|
// Parse right-hand side (must be "null").
|
|
Start = Pos;
|
|
while (Pos < Expr.size() && std::isalpha(Expr[Pos]))
|
|
++Pos;
|
|
|
|
StringRef RHS = Expr.slice(Start, Pos);
|
|
if (RHS != "null")
|
|
return createStringError("pointer comparisons only support 'null' as "
|
|
"right-hand side, got '" +
|
|
RHS.str() + "'");
|
|
|
|
// Check if the pointer is a constant null.
|
|
bool IsNull = false;
|
|
if (auto *C = dyn_cast<Constant>(LHS)) {
|
|
IsNull = C->isNullValue();
|
|
} else {
|
|
// Non-constant pointer - assume filter passes (conservative)
|
|
return true;
|
|
}
|
|
|
|
// Evaluate comparison
|
|
switch (Op) {
|
|
case EQ:
|
|
return IsNull;
|
|
case NE:
|
|
return !IsNull;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Dynamic property value, assume filter passes.
|
|
if (DynamicProperties.count(PropName))
|
|
return true;
|
|
|
|
// Unknown property, record an error.
|
|
return createStringError("expected enabled property name, got '" +
|
|
PropName.str() + "'");
|
|
}
|
|
};
|
|
} // anonymous namespace
|
|
|
|
bool llvm::instrumentor::evaluateFilter(Value &V, bool &Changed,
|
|
InstrumentationOpportunity &IO,
|
|
InstrumentationConfig &IConf,
|
|
InstrumentorIRBuilderTy &IIRB) {
|
|
if (IO.Filter.empty())
|
|
return true;
|
|
|
|
// Collect constant property values for filter evaluation.
|
|
DenseMap<StringRef, int64_t> IntPropertyValues;
|
|
DenseMap<StringRef, StringRef> StringPropertyValues;
|
|
DenseMap<StringRef, Value *> PointerPropertyValues;
|
|
DenseMap<StringRef, PropertyType> DynamicProperties;
|
|
|
|
for (auto &Arg : IO.IRTArgs) {
|
|
if (!Arg.Enabled)
|
|
continue;
|
|
|
|
// Get the value for this argument.
|
|
Value *ArgValue = Arg.GetterCB(V, *Arg.Ty, IConf, IIRB);
|
|
if (!ArgValue)
|
|
continue;
|
|
|
|
// TODO: This is likely too broad and we might want GetterCB to indicate
|
|
// changes.
|
|
Changed = true;
|
|
|
|
if (auto *CI = dyn_cast<ConstantInt>(ArgValue)) {
|
|
// Check for constant integer values.
|
|
IntPropertyValues[Arg.Name] = CI->getSExtValue();
|
|
} else if ((Arg.Flags & IRTArg::STRING) && isa<Constant>(ArgValue)) {
|
|
// Check for constant string values (marked with STRING flag).
|
|
if (auto *GV = dyn_cast<GlobalVariable>(ArgValue))
|
|
if (GV->isConstant() && GV->hasInitializer())
|
|
if (auto *CDA = dyn_cast<ConstantDataArray>(GV->getInitializer()))
|
|
if (CDA->isCString())
|
|
StringPropertyValues[Arg.Name] = CDA->getAsCString();
|
|
} else if (ArgValue->getType()->isPointerTy()) {
|
|
// Check for pointer values (for null comparisons), after the strings.
|
|
PointerPropertyValues[Arg.Name] = ArgValue;
|
|
} else {
|
|
// If the value is not constant, we skip it - the filter will pass
|
|
// for dynamic values - but we still want to report broken filters.
|
|
DynamicProperties[Arg.Name] =
|
|
Arg.Ty->isIntegerTy()
|
|
? INT
|
|
: (Arg.Flags & IRTArg::STRING
|
|
? STRING
|
|
: (Arg.Ty->isPointerTy() ? POINTER : UNKNOWN));
|
|
}
|
|
}
|
|
|
|
FilterEvaluator Evaluator(IO.Filter, IntPropertyValues, StringPropertyValues,
|
|
PointerPropertyValues, DynamicProperties);
|
|
|
|
Expected<bool> Result = Evaluator.evaluate();
|
|
if (!Result) {
|
|
// Emit an error if the filter is malformed.
|
|
IIRB.Ctx.diagnose(DiagnosticInfoInstrumentation(
|
|
Twine("malformed filter expression for instrumentation opportunity '") +
|
|
IO.getName() + Twine("': ") + toString(Result.takeError()) +
|
|
Twine("\nFilter: ") + IO.Filter,
|
|
DS_Error));
|
|
return false;
|
|
}
|
|
|
|
return Result.get();
|
|
}
|