Skip to content

Commit 3b40e56

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 088382b commit 3b40e56

File tree

3 files changed

+833
-1
lines changed

3 files changed

+833
-1
lines changed

llvm/include/llvm/IR/DebugInfoMetadata.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3121,6 +3121,11 @@ class DIExpression : public MDNode {
31213121
/// expression and constant on failure.
31223122
std::pair<DIExpression *, const ConstantInt *>
31233123
constantFold(const ConstantInt *CI);
3124+
3125+
/// Try to shorten an expression with constant math operations that can be
3126+
/// evaluated at compile time. Returns a new expression on success, or the old
3127+
/// expression if there is nothing to be reduced.
3128+
SmallVector<uint64_t> foldConstantMath();
31243129
};
31253130

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

llvm/lib/IR/DebugInfoMetadata.cpp

Lines changed: 342 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "llvm/IR/Type.h"
2323
#include "llvm/IR/Value.h"
2424

25+
#include <bit>
2526
#include <numeric>
2627
#include <optional>
2728

@@ -1880,7 +1881,6 @@ DIExpression *DIExpression::append(const DIExpression *Expr,
18801881
}
18811882
Op.appendToVector(NewOps);
18821883
}
1883-
18841884
NewOps.append(Ops.begin(), Ops.end());
18851885
auto *result = DIExpression::get(Expr->getContext(), NewOps);
18861886
assert(result->isValid() && "concatenated expression is not valid");
@@ -2022,6 +2022,347 @@ DIExpression::constantFold(const ConstantInt *CI) {
20222022
ConstantInt::get(getContext(), NewInt)};
20232023
}
20242024

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

0 commit comments

Comments
 (0)