Skip to content

Commit dfba61c

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 bf17548 commit dfba61c

File tree

3 files changed

+599
-1
lines changed

3 files changed

+599
-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: 315 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,321 @@ DIExpression::constantFold(const ConstantInt *CI) {
20112010
ConstantInt::get(getContext(), NewInt)};
20122011
}
20132012

2013+
static bool isConstantVal(uint64_t Op) {
2014+
return Op == dwarf::DW_OP_constu || Op == dwarf::DW_OP_consts;
2015+
}
2016+
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+
static std::optional<uint64_t>
2033+
foldOperationIfPossible(uint64_t Op, uint64_t Operand1, uint64_t Operand2) {
2034+
switch (Op) {
2035+
case dwarf::DW_OP_plus:
2036+
return Operand1 + Operand2;
2037+
case dwarf::DW_OP_minus:
2038+
return Operand1 - Operand2;
2039+
case dwarf::DW_OP_shl:
2040+
return Operand1 << Operand2;
2041+
case dwarf::DW_OP_shr:
2042+
return Operand1 >> Operand2;
2043+
case dwarf::DW_OP_mul:
2044+
return Operand1 * Operand2;
2045+
case dwarf::DW_OP_div:
2046+
return Operand1 / Operand2;
2047+
default:
2048+
return std::nullopt;
2049+
}
2050+
}
2051+
2052+
static bool operationsAreFoldableAndCommutative(uint64_t Op1, uint64_t Op2) {
2053+
if (Op1 != Op2)
2054+
return false;
2055+
switch (Op1) {
2056+
case dwarf::DW_OP_plus:
2057+
case dwarf::DW_OP_mul:
2058+
return true;
2059+
default:
2060+
return false;
2061+
}
2062+
}
2063+
2064+
static void consumeOneOperator(DIExpressionCursor &Cursor, uint64_t &Loc,
2065+
const DIExpression::ExprOperand &Op) {
2066+
Cursor.consume(1);
2067+
Loc = Loc + Op.getSize();
2068+
}
2069+
2070+
static void startFromBeginning(uint64_t &Loc, DIExpressionCursor &Cursor,
2071+
ArrayRef<uint64_t> WorkingOps) {
2072+
Cursor.assignNewExpr(WorkingOps);
2073+
Loc = 0;
2074+
}
2075+
2076+
static void moveCursorToCurrentOp(uint64_t &Loc, DIExpressionCursor &Cursor,
2077+
ArrayRef<uint64_t> WorkingOps,
2078+
uint32_t NumOfOpsConsumed) {
2079+
Cursor.assignNewExpr(WorkingOps);
2080+
Cursor.consume(NumOfOpsConsumed);
2081+
assert(Cursor.peek()->getOp() == WorkingOps[Loc] &&
2082+
"Cursor position does not match position of iterator 'Loc' to "
2083+
"WorkingOps");
2084+
}
2085+
2086+
// This function will canonicalize:
2087+
// 1. DW_OP_plus_uconst to DW_OP_constu <const-val> DW_OP_plus
2088+
// 2. DW_OP_lit<n> to DW_OP_constu <n>
2089+
static void canonicalizeDwarfOperations(SmallVectorImpl<uint64_t> &WorkingOps) {
2090+
DIExpressionCursor Cursor(WorkingOps);
2091+
uint64_t Loc = 0;
2092+
uint32_t NumOfOpsConsumed = 0;
2093+
while (Loc < WorkingOps.size()) {
2094+
auto Op = Cursor.peek();
2095+
// Expression has no operations, break.
2096+
if (!Op)
2097+
break;
2098+
auto OpRaw = Op->getOp();
2099+
auto OpArg = Op->getArg(0);
2100+
2101+
if (OpRaw >= dwarf::DW_OP_lit0 && OpRaw <= dwarf::DW_OP_lit31) {
2102+
WorkingOps[Loc] = dwarf::DW_OP_constu;
2103+
WorkingOps.insert(WorkingOps.begin() + Loc + 1,
2104+
OpRaw - dwarf::DW_OP_lit0);
2105+
} else if (OpRaw == dwarf::DW_OP_plus_uconst) {
2106+
WorkingOps[Loc] = dwarf::DW_OP_constu;
2107+
WorkingOps[Loc + 1] = OpArg;
2108+
WorkingOps.insert(WorkingOps.begin() + Loc + 2, dwarf::DW_OP_plus);
2109+
moveCursorToCurrentOp(Loc, Cursor, WorkingOps, NumOfOpsConsumed);
2110+
}
2111+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
2112+
NumOfOpsConsumed++;
2113+
}
2114+
}
2115+
2116+
// This function will convert:
2117+
// DW_OP_constu <const-val> DW_OP_plus to DW_OP_plus_uconst
2118+
static void optimizeDwarfOperations(SmallVectorImpl<uint64_t> &WorkingOps) {
2119+
DIExpressionCursor Cursor(WorkingOps);
2120+
uint64_t Loc = 0;
2121+
uint32_t NumOfOpsConsumed = 0;
2122+
while (Loc < WorkingOps.size()) {
2123+
auto Op1 = Cursor.peek();
2124+
// Expression has no operations, exit.
2125+
if (!Op1)
2126+
break;
2127+
auto Op1Raw = Op1->getOp();
2128+
auto Op1Arg = Op1->getArg(0);
2129+
2130+
if (Op1Raw == dwarf::DW_OP_constu && Op1Arg == 0) {
2131+
WorkingOps[Loc] = dwarf::DW_OP_lit0;
2132+
WorkingOps.erase(WorkingOps.begin() + Loc + 1);
2133+
moveCursorToCurrentOp(Loc, Cursor, WorkingOps, NumOfOpsConsumed);
2134+
continue;
2135+
}
2136+
2137+
auto Op2 = Cursor.peekNext();
2138+
// Expression has no more operations, exit.
2139+
if (!Op2)
2140+
break;
2141+
auto Op2Raw = Op2->getOp();
2142+
2143+
if (Op1Raw == dwarf::DW_OP_constu && Op2Raw == dwarf::DW_OP_plus) {
2144+
WorkingOps.erase(WorkingOps.begin() + Loc + 2);
2145+
WorkingOps[Loc] = dwarf::DW_OP_plus_uconst;
2146+
WorkingOps[Loc + 1] = Op1Arg;
2147+
moveCursorToCurrentOp(Loc, Cursor, WorkingOps, NumOfOpsConsumed);
2148+
}
2149+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
2150+
NumOfOpsConsumed++;
2151+
}
2152+
}
2153+
2154+
// {DW_OP_const[u, s], 0, DW_OP_[plus, minus, shl, shr]} -> {}
2155+
// {DW_OP_const[u, s], 1, DW_OP_[mul, div]} -> {}
2156+
static bool tryFoldNoOpMath(uint64_t Op1Raw, uint64_t Op1Arg, uint64_t Op2Raw,
2157+
uint64_t &Loc, DIExpressionCursor &Cursor,
2158+
SmallVectorImpl<uint64_t> &WorkingOps) {
2159+
if (isConstantVal(Op1Raw) && isNeutralElement(Op2Raw, Op1Arg)) {
2160+
WorkingOps.erase(WorkingOps.begin() + Loc, WorkingOps.begin() + Loc + 3);
2161+
startFromBeginning(Loc, Cursor, WorkingOps);
2162+
return true;
2163+
}
2164+
return false;
2165+
}
2166+
2167+
// {DW_OP_const[u, s], Const1, DW_OP_const[u, s], Const2, DW_OP_[plus,
2168+
// minus, mul, div, shl, shr] -> {DW_OP_constu, Const1 [+, -, *, /, <<, >>]
2169+
// Const2}
2170+
static bool tryFoldConstants(std::optional<DIExpression::ExprOperand> Op1,
2171+
uint64_t Op1Raw, uint64_t Op1Arg, uint64_t Op2Raw,
2172+
uint64_t Op2Arg, uint64_t Op3Raw, uint64_t &Loc,
2173+
DIExpressionCursor &Cursor,
2174+
SmallVectorImpl<uint64_t> &WorkingOps) {
2175+
if (isConstantVal(Op1Raw) && isConstantVal(Op2Raw)) {
2176+
auto Result = foldOperationIfPossible(Op3Raw, Op1Arg, Op2Arg);
2177+
if (!Result) {
2178+
consumeOneOperator(Cursor, Loc, *Op1);
2179+
return true;
2180+
}
2181+
WorkingOps.erase(WorkingOps.begin() + Loc + 2,
2182+
WorkingOps.begin() + Loc + 5);
2183+
WorkingOps[Loc] = dwarf::DW_OP_constu;
2184+
WorkingOps[Loc + 1] = *Result;
2185+
startFromBeginning(Loc, Cursor, WorkingOps);
2186+
return true;
2187+
}
2188+
return false;
2189+
}
2190+
2191+
// {DW_OP_const[u, s], Const1, DW_OP_[plus, mul], DW_OP_const[u, s], Const2,
2192+
// DW_OP_[plus, mul]} -> {DW_OP_constu, Const1 [+, *] Const2, DW_OP_[plus, mul]}
2193+
static bool tryFoldCommutativeMath(uint64_t Op1Raw, uint64_t Op1Arg,
2194+
uint64_t Op2Raw, uint64_t Op2Arg,
2195+
uint64_t Op3Raw, uint64_t Op3Arg,
2196+
uint64_t Op4Raw, uint64_t &Loc,
2197+
DIExpressionCursor &Cursor,
2198+
SmallVectorImpl<uint64_t> &WorkingOps) {
2199+
2200+
if (isConstantVal(Op1Raw) && isConstantVal(Op3Raw) &&
2201+
operationsAreFoldableAndCommutative(Op2Raw, Op4Raw)) {
2202+
auto Result = foldOperationIfPossible(Op2Raw, Op1Arg, Op3Arg);
2203+
if (!Result)
2204+
llvm_unreachable(
2205+
"Something went very wrong here! This should always work");
2206+
WorkingOps.erase(WorkingOps.begin() + Loc + 3,
2207+
WorkingOps.begin() + Loc + 6);
2208+
WorkingOps[Loc] = dwarf::DW_OP_constu;
2209+
WorkingOps[Loc + 1] = *Result;
2210+
startFromBeginning(Loc, Cursor, WorkingOps);
2211+
return true;
2212+
}
2213+
return false;
2214+
}
2215+
2216+
// {DW_OP_const[u, s], Const1, DW_OP_[plus, mul], DW_OP_LLVM_arg, Arg1,
2217+
// DW_OP_[plus, mul], DW_OP_const[u, s], Const2, DW_OP_[plus, mul]} ->
2218+
// {DW_OP_constu, Const1 [+, *] Const2, DW_OP_[plus, mul], DW_OP_LLVM_arg,
2219+
// Arg1, DW_OP_[plus, mul]}
2220+
static bool tryFoldCommutativeMathWithArgInBetween(
2221+
uint64_t Op1Raw, uint64_t Op1Arg, uint64_t Op2Raw, uint64_t Op3Raw,
2222+
uint64_t Op4Raw, uint64_t Op5Raw, uint64_t Op5Arg, uint64_t Op6Raw,
2223+
uint64_t &Loc, DIExpressionCursor &Cursor,
2224+
SmallVectorImpl<uint64_t> &WorkingOps) {
2225+
if (isConstantVal(Op1Raw) && Op3Raw == dwarf::DW_OP_LLVM_arg &&
2226+
isConstantVal(Op5Raw) &&
2227+
operationsAreFoldableAndCommutative(Op2Raw, Op4Raw) &&
2228+
operationsAreFoldableAndCommutative(Op4Raw, Op6Raw)) {
2229+
auto Result = foldOperationIfPossible(Op2Raw, Op1Arg, Op5Arg);
2230+
if (!Result)
2231+
llvm_unreachable(
2232+
"Something went very wrong here! This should always work");
2233+
WorkingOps.erase(WorkingOps.begin() + Loc + 6,
2234+
WorkingOps.begin() + Loc + 9);
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+
DIExpression *DIExpression::foldConstantMath() {
2244+
2245+
SmallVector<uint64_t, 8> WorkingOps(Elements.begin(), Elements.end());
2246+
uint64_t Loc = 0;
2247+
canonicalizeDwarfOperations(WorkingOps);
2248+
DIExpressionCursor Cursor(WorkingOps);
2249+
2250+
while (Loc < WorkingOps.size()) {
2251+
2252+
auto Op1 = Cursor.peek();
2253+
// Expression has no operations, exit.
2254+
if (!Op1)
2255+
break;
2256+
auto Op1Raw = Op1->getOp();
2257+
auto Op1Arg = Op1->getArg(0);
2258+
2259+
if (!isConstantVal(Op1Raw)) {
2260+
// Early exit, all of the following patterns start with a constant value.
2261+
consumeOneOperator(Cursor, Loc, *Op1);
2262+
continue;
2263+
}
2264+
2265+
auto Op2 = Cursor.peekNext();
2266+
// All following patterns require at least 2 Operations, exit.
2267+
if (!Op2)
2268+
break;
2269+
auto Op2Raw = Op2->getOp();
2270+
2271+
if (tryFoldNoOpMath(Op1Raw, Op1Arg, Op2Raw, Loc, Cursor, WorkingOps))
2272+
continue;
2273+
2274+
auto Op2Arg = Op2->getArg(0);
2275+
2276+
auto Op3 = Cursor.peekNextN(2);
2277+
// Op2 could still match a pattern, skip iteration.
2278+
if (!Op3) {
2279+
consumeOneOperator(Cursor, Loc, *Op1);
2280+
continue;
2281+
}
2282+
auto Op3Raw = Op3->getOp();
2283+
2284+
if (tryFoldConstants(Op1, Op1Raw, Op1Arg, Op2Raw, Op2Arg, Op3Raw, Loc,
2285+
Cursor, WorkingOps))
2286+
continue;
2287+
2288+
auto Op3Arg = Op3->getArg(0);
2289+
2290+
auto Op4 = Cursor.peekNextN(3);
2291+
// Op2 and Op3 could still match a pattern, skip iteration.
2292+
if (!Op4) {
2293+
consumeOneOperator(Cursor, Loc, *Op1);
2294+
continue;
2295+
}
2296+
auto Op4Raw = Op4->getOp();
2297+
2298+
if (tryFoldCommutativeMath(Op1Raw, Op1Arg, Op2Raw, Op2Arg, Op3Raw, Op3Arg,
2299+
Op4Raw, Loc, Cursor, WorkingOps))
2300+
continue;
2301+
2302+
auto Op5 = Cursor.peekNextN(4);
2303+
if (!Op5) {
2304+
consumeOneOperator(Cursor, Loc, *Op1);
2305+
continue;
2306+
}
2307+
auto Op5Raw = Op5->getOp();
2308+
auto Op5Arg = Op5->getArg(0);
2309+
auto Op6 = Cursor.peekNextN(5);
2310+
if (!Op6) {
2311+
consumeOneOperator(Cursor, Loc, *Op1);
2312+
continue;
2313+
}
2314+
auto Op6Raw = Op6->getOp();
2315+
if (tryFoldCommutativeMathWithArgInBetween(Op1Raw, Op1Arg, Op2Raw, Op3Raw,
2316+
Op4Raw, Op5Raw, Op5Arg, Op6Raw,
2317+
Loc, Cursor, WorkingOps))
2318+
continue;
2319+
2320+
consumeOneOperator(Cursor, Loc, *Op1);
2321+
}
2322+
optimizeDwarfOperations(WorkingOps);
2323+
auto *Result = DIExpression::get(getContext(), WorkingOps);
2324+
assert(Result->isValid() && "concatenated expression is not valid");
2325+
return Result;
2326+
}
2327+
20142328
uint64_t DIExpression::getNumLocationOperands() const {
20152329
uint64_t Result = 0;
20162330
for (auto ExprOp : expr_ops())

0 commit comments

Comments
 (0)