Skip to content

Commit dcf20f1

Browse files
Introduce DIExpression::foldConstantMath()
DIExpressions can get very long and have a lot of redundant operations. This function uses simple pattern matching to fold constant math that can be evaluated at compile time.
1 parent 1f9b481 commit dcf20f1

File tree

3 files changed

+832
-1
lines changed

3 files changed

+832
-1
lines changed

llvm/include/llvm/IR/DebugInfoMetadata.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3064,6 +3064,11 @@ class DIExpression : public MDNode {
30643064
/// expression and constant on failure.
30653065
std::pair<DIExpression *, const ConstantInt *>
30663066
constantFold(const ConstantInt *CI);
3067+
3068+
/// Try to shorten an expression with constant math operations that can be
3069+
/// evaluated at compile time. Returns a new expression on success, or the old
3070+
/// expression if there is nothing to be reduced.
3071+
SmallVector<uint64_t> foldConstantMath(ArrayRef<uint64_t> WorkingOps);
30673072
};
30683073

30693074
inline bool operator==(const DIExpression::FragmentInfo &A,

llvm/lib/IR/DebugInfoMetadata.cpp

Lines changed: 341 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1870,7 +1870,6 @@ DIExpression *DIExpression::append(const DIExpression *Expr,
18701870
}
18711871
Op.appendToVector(NewOps);
18721872
}
1873-
18741873
NewOps.append(Ops.begin(), Ops.end());
18751874
auto *result = DIExpression::get(Expr->getContext(), NewOps);
18761875
assert(result->isValid() && "concatenated expression is not valid");
@@ -2011,6 +2010,347 @@ DIExpression::constantFold(const ConstantInt *CI) {
20112010
ConstantInt::get(getContext(), NewInt)};
20122011
}
20132012

2013+
/// Returns true if the Op is a DW_OP_constu.
2014+
static bool isConstantVal(uint64_t Op) { return Op == dwarf::DW_OP_constu; }
2015+
2016+
/// Returns true if an operation and operand result in a No Op.
2017+
static bool isNeutralElement(uint64_t Op, uint64_t Val) {
2018+
switch (Op) {
2019+
case dwarf::DW_OP_plus:
2020+
case dwarf::DW_OP_minus:
2021+
case dwarf::DW_OP_shl:
2022+
case dwarf::DW_OP_shr:
2023+
return Val == 0;
2024+
case dwarf::DW_OP_mul:
2025+
case dwarf::DW_OP_div:
2026+
return Val == 1;
2027+
default:
2028+
return false;
2029+
}
2030+
}
2031+
2032+
/// Try to fold constant math operations and return the result if possible.
2033+
static std::optional<uint64_t>
2034+
foldOperationIfPossible(uint64_t Op, uint64_t Operand1, uint64_t Operand2) {
2035+
bool ResultOverflowed;
2036+
switch (Op) {
2037+
case dwarf::DW_OP_plus: {
2038+
auto Result = SaturatingAdd(Operand1, Operand2, &ResultOverflowed);
2039+
if (ResultOverflowed)
2040+
return std::nullopt;
2041+
return Result;
2042+
}
2043+
case dwarf::DW_OP_minus: {
2044+
if (Operand1 < Operand2)
2045+
return std::nullopt;
2046+
return Operand1 - Operand2;
2047+
}
2048+
case dwarf::DW_OP_shl: {
2049+
if ((uint64_t)__builtin_clz(Operand1) < Operand2)
2050+
return std::nullopt;
2051+
return Operand1 << Operand2;
2052+
}
2053+
case dwarf::DW_OP_shr: {
2054+
if ((uint64_t)__builtin_ctz(Operand1) < Operand2)
2055+
return std::nullopt;
2056+
return Operand1 >> Operand2;
2057+
}
2058+
case dwarf::DW_OP_mul: {
2059+
auto Result = SaturatingMultiply(Operand1, Operand2, &ResultOverflowed);
2060+
if (ResultOverflowed)
2061+
return std::nullopt;
2062+
return Result;
2063+
}
2064+
case dwarf::DW_OP_div: {
2065+
if (Operand2)
2066+
return Operand1 / Operand2;
2067+
return std::nullopt;
2068+
}
2069+
default:
2070+
return std::nullopt;
2071+
}
2072+
}
2073+
2074+
/// Returns true if the two operations are commutative and can be folded.
2075+
static bool operationsAreFoldableAndCommutative(uint64_t Op1, uint64_t Op2) {
2076+
if (Op1 != Op2)
2077+
return false;
2078+
switch (Op1) {
2079+
case dwarf::DW_OP_plus:
2080+
case dwarf::DW_OP_mul:
2081+
return true;
2082+
default:
2083+
return false;
2084+
}
2085+
}
2086+
2087+
/// Consume one operator and its operand(s).
2088+
static void consumeOneOperator(DIExpressionCursor &Cursor, uint64_t &Loc,
2089+
const DIExpression::ExprOperand &Op) {
2090+
Cursor.consume(1);
2091+
Loc = Loc + Op.getSize();
2092+
}
2093+
2094+
/// Reset the Cursor to the beginning of the WorkingOps.
2095+
static void startFromBeginning(uint64_t &Loc, DIExpressionCursor &Cursor,
2096+
ArrayRef<uint64_t> WorkingOps) {
2097+
Cursor.assignNewExpr(WorkingOps);
2098+
Loc = 0;
2099+
}
2100+
2101+
/// This function will canonicalize:
2102+
/// 1. DW_OP_plus_uconst to DW_OP_constu <const-val> DW_OP_plus
2103+
/// 2. DW_OP_lit<n> to DW_OP_constu <n>
2104+
static SmallVector<uint64_t>
2105+
canonicalizeDwarfOperations(ArrayRef<uint64_t> WorkingOps) {
2106+
DIExpressionCursor Cursor(WorkingOps);
2107+
uint64_t Loc = 0;
2108+
SmallVector<uint64_t> ResultOps;
2109+
while (Loc < WorkingOps.size()) {
2110+
auto Op = Cursor.peek();
2111+
/// Expression has no operations, break.
2112+
if (!Op)
2113+
break;
2114+
auto OpRaw = Op->getOp();
2115+
auto OpArg = Op->getArg(0);
2116+
2117+
if (OpRaw >= dwarf::DW_OP_lit0 && OpRaw <= dwarf::DW_OP_lit31) {
2118+
ResultOps.push_back(dwarf::DW_OP_constu);
2119+
ResultOps.push_back(OpRaw - dwarf::DW_OP_lit0);
2120+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
2121+
continue;
2122+
}
2123+
if (OpRaw == dwarf::DW_OP_plus_uconst) {
2124+
ResultOps.push_back(dwarf::DW_OP_constu);
2125+
ResultOps.push_back(OpArg);
2126+
ResultOps.push_back(dwarf::DW_OP_plus);
2127+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
2128+
continue;
2129+
}
2130+
uint64_t PrevLoc = Loc;
2131+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
2132+
ResultOps.append(WorkingOps.begin() + PrevLoc, WorkingOps.begin() + Loc);
2133+
}
2134+
return ResultOps;
2135+
}
2136+
2137+
/// This function will convert:
2138+
/// 1. DW_OP_constu <const-val> DW_OP_plus to DW_OP_plus_uconst
2139+
/// 2. DW_OP_constu, 0 to DW_OP_lit0
2140+
static SmallVector<uint64_t>
2141+
optimizeDwarfOperations(ArrayRef<uint64_t> WorkingOps) {
2142+
DIExpressionCursor Cursor(WorkingOps);
2143+
uint64_t Loc = 0;
2144+
SmallVector<uint64_t> ResultOps;
2145+
while (Loc < WorkingOps.size()) {
2146+
auto Op1 = Cursor.peek();
2147+
/// Expression has no operations, exit.
2148+
if (!Op1)
2149+
break;
2150+
auto Op1Raw = Op1->getOp();
2151+
auto Op1Arg = Op1->getArg(0);
2152+
2153+
if (Op1Raw == dwarf::DW_OP_constu && Op1Arg == 0) {
2154+
ResultOps.push_back(dwarf::DW_OP_lit0);
2155+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
2156+
continue;
2157+
}
2158+
2159+
auto Op2 = Cursor.peekNext();
2160+
/// Expression has no more operations, copy into ResultOps and exit.
2161+
if (!Op2) {
2162+
uint64_t PrevLoc = Loc;
2163+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
2164+
ResultOps.append(WorkingOps.begin() + PrevLoc, WorkingOps.begin() + Loc);
2165+
break;
2166+
}
2167+
auto Op2Raw = Op2->getOp();
2168+
2169+
if (Op1Raw == dwarf::DW_OP_constu && Op2Raw == dwarf::DW_OP_plus) {
2170+
ResultOps.push_back(dwarf::DW_OP_plus_uconst);
2171+
ResultOps.push_back(Op1Arg);
2172+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
2173+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
2174+
continue;
2175+
}
2176+
uint64_t PrevLoc = Loc;
2177+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
2178+
ResultOps.append(WorkingOps.begin() + PrevLoc, WorkingOps.begin() + Loc);
2179+
}
2180+
return ResultOps;
2181+
}
2182+
2183+
/// {DW_OP_constu, 0, DW_OP_[plus, minus, shl, shr]} -> {}
2184+
/// {DW_OP_constu, 1, DW_OP_[mul, div]} -> {}
2185+
static bool tryFoldNoOpMath(uint64_t Op1Raw, uint64_t Op1Arg, uint64_t Op2Raw,
2186+
uint64_t &Loc, DIExpressionCursor &Cursor,
2187+
SmallVectorImpl<uint64_t> &WorkingOps) {
2188+
if (isConstantVal(Op1Raw) && isNeutralElement(Op2Raw, Op1Arg)) {
2189+
WorkingOps.erase(WorkingOps.begin() + Loc, WorkingOps.begin() + Loc + 3);
2190+
startFromBeginning(Loc, Cursor, WorkingOps);
2191+
return true;
2192+
}
2193+
return false;
2194+
}
2195+
2196+
/// {DW_OP_constu, Const1, DW_OP_constu, Const2, DW_OP_[plus,
2197+
/// minus, mul, div, shl, shr] -> {DW_OP_constu, Const1 [+, -, *, /, <<, >>]
2198+
/// Const2}
2199+
static bool tryFoldConstants(std::optional<DIExpression::ExprOperand> Op1,
2200+
uint64_t Op1Raw, uint64_t Op1Arg, uint64_t Op2Raw,
2201+
uint64_t Op2Arg, uint64_t Op3Raw, uint64_t &Loc,
2202+
DIExpressionCursor &Cursor,
2203+
SmallVectorImpl<uint64_t> &WorkingOps) {
2204+
if (isConstantVal(Op1Raw) && isConstantVal(Op2Raw)) {
2205+
auto Result = foldOperationIfPossible(Op3Raw, Op1Arg, Op2Arg);
2206+
if (!Result) {
2207+
consumeOneOperator(Cursor, Loc, *Op1);
2208+
return true;
2209+
}
2210+
WorkingOps.erase(WorkingOps.begin() + Loc + 2,
2211+
WorkingOps.begin() + Loc + 5);
2212+
WorkingOps[Loc] = dwarf::DW_OP_constu;
2213+
WorkingOps[Loc + 1] = *Result;
2214+
startFromBeginning(Loc, Cursor, WorkingOps);
2215+
return true;
2216+
}
2217+
return false;
2218+
}
2219+
2220+
/// {DW_OP_constu, Const1, DW_OP_[plus, mul], DW_OP_constu, Const2,
2221+
/// DW_OP_[plus, mul]} -> {DW_OP_constu, Const1 [+, *] Const2, DW_OP_[plus,
2222+
/// mul]}
2223+
static bool tryFoldCommutativeMath(uint64_t Op1Raw, uint64_t Op1Arg,
2224+
uint64_t Op2Raw, uint64_t Op2Arg,
2225+
uint64_t Op3Raw, uint64_t Op3Arg,
2226+
uint64_t Op4Raw, uint64_t &Loc,
2227+
DIExpressionCursor &Cursor,
2228+
SmallVectorImpl<uint64_t> &WorkingOps) {
2229+
2230+
if (isConstantVal(Op1Raw) && isConstantVal(Op3Raw) &&
2231+
operationsAreFoldableAndCommutative(Op2Raw, Op4Raw)) {
2232+
auto Result = foldOperationIfPossible(Op2Raw, Op1Arg, Op3Arg);
2233+
if (!Result)
2234+
return false;
2235+
WorkingOps.erase(WorkingOps.begin() + Loc + 3,
2236+
WorkingOps.begin() + Loc + 6);
2237+
WorkingOps[Loc] = dwarf::DW_OP_constu;
2238+
WorkingOps[Loc + 1] = *Result;
2239+
startFromBeginning(Loc, Cursor, WorkingOps);
2240+
return true;
2241+
}
2242+
return false;
2243+
}
2244+
2245+
/// {DW_OP_constu, Const1, DW_OP_[plus, mul], DW_OP_LLVM_arg, Arg1,
2246+
/// DW_OP_[plus, mul], DW_OP_constu, Const2, DW_OP_[plus, mul]} ->
2247+
/// {DW_OP_constu, Const1 [+, *] Const2, DW_OP_[plus, mul], DW_OP_LLVM_arg,
2248+
/// Arg1, DW_OP_[plus, mul]}
2249+
static bool tryFoldCommutativeMathWithArgInBetween(
2250+
uint64_t Op1Raw, uint64_t Op1Arg, uint64_t Op2Raw, uint64_t Op3Raw,
2251+
uint64_t Op4Raw, uint64_t Op5Raw, uint64_t Op5Arg, uint64_t Op6Raw,
2252+
uint64_t &Loc, DIExpressionCursor &Cursor,
2253+
SmallVectorImpl<uint64_t> &WorkingOps) {
2254+
if (isConstantVal(Op1Raw) && Op3Raw == dwarf::DW_OP_LLVM_arg &&
2255+
isConstantVal(Op5Raw) &&
2256+
operationsAreFoldableAndCommutative(Op2Raw, Op4Raw) &&
2257+
operationsAreFoldableAndCommutative(Op4Raw, Op6Raw)) {
2258+
auto Result = foldOperationIfPossible(Op2Raw, Op1Arg, Op5Arg);
2259+
if (!Result)
2260+
return false;
2261+
WorkingOps.erase(WorkingOps.begin() + Loc + 6,
2262+
WorkingOps.begin() + Loc + 9);
2263+
WorkingOps[Loc] = dwarf::DW_OP_constu;
2264+
WorkingOps[Loc + 1] = *Result;
2265+
startFromBeginning(Loc, Cursor, WorkingOps);
2266+
return true;
2267+
}
2268+
return false;
2269+
}
2270+
2271+
SmallVector<uint64_t>
2272+
DIExpression::foldConstantMath(ArrayRef<uint64_t> WorkingOps) {
2273+
2274+
uint64_t Loc = 0;
2275+
SmallVector<uint64_t> ResultOps = canonicalizeDwarfOperations(WorkingOps);
2276+
DIExpressionCursor Cursor(ResultOps);
2277+
2278+
while (Loc < ResultOps.size()) {
2279+
2280+
auto Op1 = Cursor.peek();
2281+
// Expression has no operations, exit.
2282+
if (!Op1)
2283+
break;
2284+
auto Op1Raw = Op1->getOp();
2285+
auto Op1Arg = Op1->getArg(0);
2286+
2287+
if (!isConstantVal(Op1Raw)) {
2288+
// Early exit, all of the following patterns start with a constant value.
2289+
consumeOneOperator(Cursor, Loc, *Op1);
2290+
continue;
2291+
}
2292+
2293+
auto Op2 = Cursor.peekNext();
2294+
// All following patterns require at least 2 Operations, exit.
2295+
if (!Op2)
2296+
break;
2297+
auto Op2Raw = Op2->getOp();
2298+
2299+
if (tryFoldNoOpMath(Op1Raw, Op1Arg, Op2Raw, Loc, Cursor, ResultOps))
2300+
continue;
2301+
2302+
auto Op2Arg = Op2->getArg(0);
2303+
2304+
auto Op3 = Cursor.peekNextN(2);
2305+
// Op2 could still match a pattern, skip iteration.
2306+
if (!Op3) {
2307+
consumeOneOperator(Cursor, Loc, *Op1);
2308+
continue;
2309+
}
2310+
auto Op3Raw = Op3->getOp();
2311+
2312+
if (tryFoldConstants(Op1, Op1Raw, Op1Arg, Op2Raw, Op2Arg, Op3Raw, Loc,
2313+
Cursor, ResultOps))
2314+
continue;
2315+
2316+
auto Op3Arg = Op3->getArg(0);
2317+
2318+
auto Op4 = Cursor.peekNextN(3);
2319+
// Op2 and Op3 could still match a pattern, skip iteration.
2320+
if (!Op4) {
2321+
consumeOneOperator(Cursor, Loc, *Op1);
2322+
continue;
2323+
}
2324+
auto Op4Raw = Op4->getOp();
2325+
2326+
if (tryFoldCommutativeMath(Op1Raw, Op1Arg, Op2Raw, Op2Arg, Op3Raw, Op3Arg,
2327+
Op4Raw, Loc, Cursor, ResultOps))
2328+
continue;
2329+
2330+
auto Op5 = Cursor.peekNextN(4);
2331+
if (!Op5) {
2332+
consumeOneOperator(Cursor, Loc, *Op1);
2333+
continue;
2334+
}
2335+
auto Op5Raw = Op5->getOp();
2336+
auto Op5Arg = Op5->getArg(0);
2337+
auto Op6 = Cursor.peekNextN(5);
2338+
if (!Op6) {
2339+
consumeOneOperator(Cursor, Loc, *Op1);
2340+
continue;
2341+
}
2342+
auto Op6Raw = Op6->getOp();
2343+
if (tryFoldCommutativeMathWithArgInBetween(Op1Raw, Op1Arg, Op2Raw, Op3Raw,
2344+
Op4Raw, Op5Raw, Op5Arg, Op6Raw,
2345+
Loc, Cursor, ResultOps))
2346+
continue;
2347+
2348+
consumeOneOperator(Cursor, Loc, *Op1);
2349+
}
2350+
ResultOps = optimizeDwarfOperations(ResultOps);
2351+
return ResultOps;
2352+
}
2353+
20142354
uint64_t DIExpression::getNumLocationOperands() const {
20152355
uint64_t Result = 0;
20162356
for (auto ExprOp : expr_ops())

0 commit comments

Comments
 (0)