Skip to content

Commit 056a04d

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 e4558e7 commit 056a04d

File tree

3 files changed

+803
-1
lines changed

3 files changed

+803
-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+
/// evaulated at compile time. Returns a new expression on success, or the old
3070+
/// expression if there is nothing to be reduced.
3071+
DIExpression *foldConstantMath();
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 (Operand2 > 64)
2050+
return std::nullopt;
2051+
return Operand1 << Operand2;
2052+
}
2053+
case dwarf::DW_OP_shr: {
2054+
if (Operand2 > 64)
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+
// Move the Cursor to the position of an operation by starting from the
2102+
// beginning and consuming the number of operations that came before.
2103+
static void moveCursorToCurrentOp(uint64_t &Loc, DIExpressionCursor &Cursor,
2104+
ArrayRef<uint64_t> WorkingOps,
2105+
uint32_t NumOfOpsConsumed) {
2106+
Cursor.assignNewExpr(WorkingOps);
2107+
Cursor.consume(NumOfOpsConsumed);
2108+
assert(Cursor.peek()->getOp() == WorkingOps[Loc] &&
2109+
"Cursor position does not match position of iterator 'Loc' to "
2110+
"WorkingOps");
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 void canonicalizeDwarfOperations(SmallVectorImpl<uint64_t> &WorkingOps) {
2117+
DIExpressionCursor Cursor(WorkingOps);
2118+
uint64_t Loc = 0;
2119+
uint32_t NumOfOpsConsumed = 0;
2120+
while (Loc < WorkingOps.size()) {
2121+
auto Op = Cursor.peek();
2122+
// Expression has no operations, break.
2123+
if (!Op)
2124+
break;
2125+
auto OpRaw = Op->getOp();
2126+
auto OpArg = Op->getArg(0);
2127+
2128+
if (OpRaw >= dwarf::DW_OP_lit0 && OpRaw <= dwarf::DW_OP_lit31) {
2129+
WorkingOps[Loc] = dwarf::DW_OP_constu;
2130+
WorkingOps.insert(WorkingOps.begin() + Loc + 1,
2131+
OpRaw - dwarf::DW_OP_lit0);
2132+
} else if (OpRaw == dwarf::DW_OP_plus_uconst) {
2133+
WorkingOps[Loc] = dwarf::DW_OP_constu;
2134+
WorkingOps[Loc + 1] = OpArg;
2135+
WorkingOps.insert(WorkingOps.begin() + Loc + 2, dwarf::DW_OP_plus);
2136+
moveCursorToCurrentOp(Loc, Cursor, WorkingOps, NumOfOpsConsumed);
2137+
}
2138+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
2139+
NumOfOpsConsumed++;
2140+
}
2141+
}
2142+
2143+
// This function will convert:
2144+
// 1. DW_OP_constu <const-val> DW_OP_plus to DW_OP_plus_uconst
2145+
// 2. DW_OP_constu, 0 to DW_OP_lit0
2146+
static void optimizeDwarfOperations(SmallVectorImpl<uint64_t> &WorkingOps) {
2147+
DIExpressionCursor Cursor(WorkingOps);
2148+
uint64_t Loc = 0;
2149+
uint32_t NumOfOpsConsumed = 0;
2150+
while (Loc < WorkingOps.size()) {
2151+
auto Op1 = Cursor.peek();
2152+
// Expression has no operations, exit.
2153+
if (!Op1)
2154+
break;
2155+
auto Op1Raw = Op1->getOp();
2156+
auto Op1Arg = Op1->getArg(0);
2157+
2158+
if (Op1Raw == dwarf::DW_OP_constu && Op1Arg == 0) {
2159+
WorkingOps[Loc] = dwarf::DW_OP_lit0;
2160+
WorkingOps.erase(WorkingOps.begin() + Loc + 1);
2161+
moveCursorToCurrentOp(Loc, Cursor, WorkingOps, NumOfOpsConsumed);
2162+
continue;
2163+
}
2164+
2165+
auto Op2 = Cursor.peekNext();
2166+
// Expression has no more operations, exit.
2167+
if (!Op2)
2168+
break;
2169+
auto Op2Raw = Op2->getOp();
2170+
2171+
if (Op1Raw == dwarf::DW_OP_constu && Op2Raw == dwarf::DW_OP_plus) {
2172+
WorkingOps.erase(WorkingOps.begin() + Loc + 2);
2173+
WorkingOps[Loc] = dwarf::DW_OP_plus_uconst;
2174+
WorkingOps[Loc + 1] = Op1Arg;
2175+
moveCursorToCurrentOp(Loc, Cursor, WorkingOps, NumOfOpsConsumed);
2176+
}
2177+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
2178+
NumOfOpsConsumed++;
2179+
}
2180+
}
2181+
2182+
// {DW_OP_constu, 0, DW_OP_[plus, minus, shl, shr]} -> {}
2183+
// {DW_OP_constu, 1, DW_OP_[mul, div]} -> {}
2184+
static bool tryFoldNoOpMath(uint64_t Op1Raw, uint64_t Op1Arg, uint64_t Op2Raw,
2185+
uint64_t &Loc, DIExpressionCursor &Cursor,
2186+
SmallVectorImpl<uint64_t> &WorkingOps) {
2187+
if (isConstantVal(Op1Raw) && isNeutralElement(Op2Raw, Op1Arg)) {
2188+
WorkingOps.erase(WorkingOps.begin() + Loc, WorkingOps.begin() + Loc + 3);
2189+
startFromBeginning(Loc, Cursor, WorkingOps);
2190+
return true;
2191+
}
2192+
return false;
2193+
}
2194+
2195+
// {DW_OP_constu, Const1, DW_OP_constu, Const2, DW_OP_[plus,
2196+
// minus, mul, div, shl, shr] -> {DW_OP_constu, Const1 [+, -, *, /, <<, >>]
2197+
// Const2}
2198+
static bool tryFoldConstants(std::optional<DIExpression::ExprOperand> Op1,
2199+
uint64_t Op1Raw, uint64_t Op1Arg, uint64_t Op2Raw,
2200+
uint64_t Op2Arg, uint64_t Op3Raw, uint64_t &Loc,
2201+
DIExpressionCursor &Cursor,
2202+
SmallVectorImpl<uint64_t> &WorkingOps) {
2203+
if (isConstantVal(Op1Raw) && isConstantVal(Op2Raw)) {
2204+
auto Result = foldOperationIfPossible(Op3Raw, Op1Arg, Op2Arg);
2205+
if (!Result) {
2206+
consumeOneOperator(Cursor, Loc, *Op1);
2207+
return true;
2208+
}
2209+
WorkingOps.erase(WorkingOps.begin() + Loc + 2,
2210+
WorkingOps.begin() + Loc + 5);
2211+
WorkingOps[Loc] = dwarf::DW_OP_constu;
2212+
WorkingOps[Loc + 1] = *Result;
2213+
startFromBeginning(Loc, Cursor, WorkingOps);
2214+
return true;
2215+
}
2216+
return false;
2217+
}
2218+
2219+
// {DW_OP_constu, Const1, DW_OP_[plus, mul], DW_OP_constu, Const2,
2220+
// DW_OP_[plus, mul]} -> {DW_OP_constu, Const1 [+, *] Const2, DW_OP_[plus, mul]}
2221+
static bool tryFoldCommutativeMath(uint64_t Op1Raw, uint64_t Op1Arg,
2222+
uint64_t Op2Raw, uint64_t Op2Arg,
2223+
uint64_t Op3Raw, uint64_t Op3Arg,
2224+
uint64_t Op4Raw, uint64_t &Loc,
2225+
DIExpressionCursor &Cursor,
2226+
SmallVectorImpl<uint64_t> &WorkingOps) {
2227+
2228+
if (isConstantVal(Op1Raw) && isConstantVal(Op3Raw) &&
2229+
operationsAreFoldableAndCommutative(Op2Raw, Op4Raw)) {
2230+
auto Result = foldOperationIfPossible(Op2Raw, Op1Arg, Op3Arg);
2231+
if (!Result)
2232+
return false;
2233+
WorkingOps.erase(WorkingOps.begin() + Loc + 3,
2234+
WorkingOps.begin() + Loc + 6);
2235+
WorkingOps[Loc] = dwarf::DW_OP_constu;
2236+
WorkingOps[Loc + 1] = *Result;
2237+
startFromBeginning(Loc, Cursor, WorkingOps);
2238+
return true;
2239+
}
2240+
return false;
2241+
}
2242+
2243+
// {DW_OP_constu, Const1, DW_OP_[plus, mul], DW_OP_LLVM_arg, Arg1,
2244+
// DW_OP_[plus, mul], DW_OP_constu, Const2, DW_OP_[plus, mul]} ->
2245+
// {DW_OP_constu, Const1 [+, *] Const2, DW_OP_[plus, mul], DW_OP_LLVM_arg,
2246+
// Arg1, DW_OP_[plus, mul]}
2247+
static bool tryFoldCommutativeMathWithArgInBetween(
2248+
uint64_t Op1Raw, uint64_t Op1Arg, uint64_t Op2Raw, uint64_t Op3Raw,
2249+
uint64_t Op4Raw, uint64_t Op5Raw, uint64_t Op5Arg, uint64_t Op6Raw,
2250+
uint64_t &Loc, DIExpressionCursor &Cursor,
2251+
SmallVectorImpl<uint64_t> &WorkingOps) {
2252+
if (isConstantVal(Op1Raw) && Op3Raw == dwarf::DW_OP_LLVM_arg &&
2253+
isConstantVal(Op5Raw) &&
2254+
operationsAreFoldableAndCommutative(Op2Raw, Op4Raw) &&
2255+
operationsAreFoldableAndCommutative(Op4Raw, Op6Raw)) {
2256+
auto Result = foldOperationIfPossible(Op2Raw, Op1Arg, Op5Arg);
2257+
if (!Result)
2258+
return false;
2259+
WorkingOps.erase(WorkingOps.begin() + Loc + 6,
2260+
WorkingOps.begin() + Loc + 9);
2261+
WorkingOps[Loc] = dwarf::DW_OP_constu;
2262+
WorkingOps[Loc + 1] = *Result;
2263+
startFromBeginning(Loc, Cursor, WorkingOps);
2264+
return true;
2265+
}
2266+
return false;
2267+
}
2268+
2269+
DIExpression *DIExpression::foldConstantMath() {
2270+
2271+
SmallVector<uint64_t, 8> WorkingOps(Elements.begin(), Elements.end());
2272+
uint64_t Loc = 0;
2273+
canonicalizeDwarfOperations(WorkingOps);
2274+
DIExpressionCursor Cursor(WorkingOps);
2275+
2276+
while (Loc < WorkingOps.size()) {
2277+
2278+
auto Op1 = Cursor.peek();
2279+
// Expression has no operations, exit.
2280+
if (!Op1)
2281+
break;
2282+
auto Op1Raw = Op1->getOp();
2283+
auto Op1Arg = Op1->getArg(0);
2284+
2285+
if (!isConstantVal(Op1Raw)) {
2286+
// Early exit, all of the following patterns start with a constant value.
2287+
consumeOneOperator(Cursor, Loc, *Op1);
2288+
continue;
2289+
}
2290+
2291+
auto Op2 = Cursor.peekNext();
2292+
// All following patterns require at least 2 Operations, exit.
2293+
if (!Op2)
2294+
break;
2295+
auto Op2Raw = Op2->getOp();
2296+
2297+
if (tryFoldNoOpMath(Op1Raw, Op1Arg, Op2Raw, Loc, Cursor, WorkingOps))
2298+
continue;
2299+
2300+
auto Op2Arg = Op2->getArg(0);
2301+
2302+
auto Op3 = Cursor.peekNextN(2);
2303+
// Op2 could still match a pattern, skip iteration.
2304+
if (!Op3) {
2305+
consumeOneOperator(Cursor, Loc, *Op1);
2306+
continue;
2307+
}
2308+
auto Op3Raw = Op3->getOp();
2309+
2310+
if (tryFoldConstants(Op1, Op1Raw, Op1Arg, Op2Raw, Op2Arg, Op3Raw, Loc,
2311+
Cursor, WorkingOps))
2312+
continue;
2313+
2314+
auto Op3Arg = Op3->getArg(0);
2315+
2316+
auto Op4 = Cursor.peekNextN(3);
2317+
// Op2 and Op3 could still match a pattern, skip iteration.
2318+
if (!Op4) {
2319+
consumeOneOperator(Cursor, Loc, *Op1);
2320+
continue;
2321+
}
2322+
auto Op4Raw = Op4->getOp();
2323+
2324+
if (tryFoldCommutativeMath(Op1Raw, Op1Arg, Op2Raw, Op2Arg, Op3Raw, Op3Arg,
2325+
Op4Raw, Loc, Cursor, WorkingOps))
2326+
continue;
2327+
2328+
auto Op5 = Cursor.peekNextN(4);
2329+
if (!Op5) {
2330+
consumeOneOperator(Cursor, Loc, *Op1);
2331+
continue;
2332+
}
2333+
auto Op5Raw = Op5->getOp();
2334+
auto Op5Arg = Op5->getArg(0);
2335+
auto Op6 = Cursor.peekNextN(5);
2336+
if (!Op6) {
2337+
consumeOneOperator(Cursor, Loc, *Op1);
2338+
continue;
2339+
}
2340+
auto Op6Raw = Op6->getOp();
2341+
if (tryFoldCommutativeMathWithArgInBetween(Op1Raw, Op1Arg, Op2Raw, Op3Raw,
2342+
Op4Raw, Op5Raw, Op5Arg, Op6Raw,
2343+
Loc, Cursor, WorkingOps))
2344+
continue;
2345+
2346+
consumeOneOperator(Cursor, Loc, *Op1);
2347+
}
2348+
optimizeDwarfOperations(WorkingOps);
2349+
auto *Result = DIExpression::get(getContext(), WorkingOps);
2350+
assert(Result->isValid() && "concatenated expression is not valid");
2351+
return Result;
2352+
}
2353+
20142354
uint64_t DIExpression::getNumLocationOperands() const {
20152355
uint64_t Result = 0;
20162356
for (auto ExprOp : expr_ops())

0 commit comments

Comments
 (0)