Skip to content

Commit 06d79e4

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 bc8ce1b commit 06d79e4

File tree

5 files changed

+842
-1
lines changed

5 files changed

+842
-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+
DIExpression *foldConstantMath();
31243129
};
31253130

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

llvm/lib/IR/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ add_llvm_component_library(LLVMCore
1717
DataLayout.cpp
1818
DebugInfo.cpp
1919
DebugInfoMetadata.cpp
20+
DIExpressionOptimizer.cpp
2021
DebugProgramInstruction.cpp
2122
DebugLoc.cpp
2223
DiagnosticHandler.cpp

llvm/lib/IR/DIExpressionOptimizer.cpp

Lines changed: 379 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,379 @@
1+
//===- DIExpressionOptimizer.cpp - Constant folding of DIExpressions ------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file implements functions to constant fold DIExpressions. Which were
10+
// declared in DIExpressionOptimizer.h
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#include "llvm/BinaryFormat/Dwarf.h"
15+
#include "llvm/IR/DebugInfoMetadata.h"
16+
17+
using namespace llvm;
18+
19+
/// Returns true if the Op is a DW_OP_constu.
20+
static std::optional<uint64_t> isConstantVal(DIExpression::ExprOperand Op) {
21+
if (Op.getOp() == dwarf::DW_OP_constu)
22+
return Op.getArg(0);
23+
return std::nullopt;
24+
}
25+
26+
/// Returns true if an operation and operand result in a No Op.
27+
static bool isNeutralElement(uint64_t Op, uint64_t Val) {
28+
switch (Op) {
29+
case dwarf::DW_OP_plus:
30+
case dwarf::DW_OP_minus:
31+
case dwarf::DW_OP_shl:
32+
case dwarf::DW_OP_shr:
33+
return Val == 0;
34+
case dwarf::DW_OP_mul:
35+
case dwarf::DW_OP_div:
36+
return Val == 1;
37+
default:
38+
return false;
39+
}
40+
}
41+
42+
/// Try to fold \p Const1 and \p Const2 by applying \p Operator and returning
43+
/// the result, if there is an overflow, return a std::nullopt.
44+
static std::optional<uint64_t>
45+
foldOperationIfPossible(uint64_t Const1, uint64_t Const2,
46+
dwarf::LocationAtom Operator) {
47+
48+
bool ResultOverflowed;
49+
switch (Operator) {
50+
case dwarf::DW_OP_plus: {
51+
auto Result = SaturatingAdd(Const1, Const2, &ResultOverflowed);
52+
if (ResultOverflowed)
53+
return std::nullopt;
54+
return Result;
55+
}
56+
case dwarf::DW_OP_minus: {
57+
if (Const1 < Const2)
58+
return std::nullopt;
59+
return Const1 - Const2;
60+
}
61+
case dwarf::DW_OP_shl: {
62+
if ((uint64_t)countl_zero(Const1) < Const2)
63+
return std::nullopt;
64+
return Const1 << Const2;
65+
}
66+
case dwarf::DW_OP_shr: {
67+
if ((uint64_t)countr_zero(Const1) < Const2)
68+
return std::nullopt;
69+
return Const1 >> Const2;
70+
}
71+
case dwarf::DW_OP_mul: {
72+
auto Result = SaturatingMultiply(Const1, Const2, &ResultOverflowed);
73+
if (ResultOverflowed)
74+
return std::nullopt;
75+
return Result;
76+
}
77+
case dwarf::DW_OP_div: {
78+
if (Const2)
79+
return Const1 / Const2;
80+
return std::nullopt;
81+
}
82+
default:
83+
return std::nullopt;
84+
}
85+
}
86+
87+
/// Returns true if the two operations \p Operator1 and \p Operator2 are
88+
/// commutative and can be folded.
89+
static bool operationsAreFoldableAndCommutative(dwarf::LocationAtom Operator1,
90+
dwarf::LocationAtom Operator2) {
91+
return Operator1 == Operator2 &&
92+
(Operator1 == dwarf::DW_OP_plus || Operator1 == dwarf::DW_OP_mul);
93+
}
94+
95+
/// Consume one operator and its operand(s).
96+
static void consumeOneOperator(DIExpressionCursor &Cursor, uint64_t &Loc,
97+
const DIExpression::ExprOperand &Op) {
98+
Cursor.consume(1);
99+
Loc = Loc + Op.getSize();
100+
}
101+
102+
/// Reset the Cursor to the beginning of the WorkingOps.
103+
void startFromBeginning(uint64_t &Loc, DIExpressionCursor &Cursor,
104+
ArrayRef<uint64_t> WorkingOps) {
105+
Cursor.assignNewExpr(WorkingOps);
106+
Loc = 0;
107+
}
108+
109+
/// This function will canonicalize:
110+
/// 1. DW_OP_plus_uconst to DW_OP_constu <const-val> DW_OP_plus
111+
/// 2. DW_OP_lit<n> to DW_OP_constu <n>
112+
static SmallVector<uint64_t>
113+
canonicalizeDwarfOperations(ArrayRef<uint64_t> WorkingOps) {
114+
DIExpressionCursor Cursor(WorkingOps);
115+
uint64_t Loc = 0;
116+
SmallVector<uint64_t> ResultOps;
117+
while (Loc < WorkingOps.size()) {
118+
auto Op = Cursor.peek();
119+
/// Expression has no operations, break.
120+
if (!Op)
121+
break;
122+
auto OpRaw = Op->getOp();
123+
124+
if (OpRaw >= dwarf::DW_OP_lit0 && OpRaw <= dwarf::DW_OP_lit31) {
125+
ResultOps.push_back(dwarf::DW_OP_constu);
126+
ResultOps.push_back(OpRaw - dwarf::DW_OP_lit0);
127+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
128+
continue;
129+
}
130+
if (OpRaw == dwarf::DW_OP_plus_uconst) {
131+
ResultOps.push_back(dwarf::DW_OP_constu);
132+
ResultOps.push_back(Op->getArg(0));
133+
ResultOps.push_back(dwarf::DW_OP_plus);
134+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
135+
continue;
136+
}
137+
uint64_t PrevLoc = Loc;
138+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
139+
ResultOps.append(WorkingOps.begin() + PrevLoc, WorkingOps.begin() + Loc);
140+
}
141+
return ResultOps;
142+
}
143+
144+
/// This function will convert:
145+
/// 1. DW_OP_constu <const-val> DW_OP_plus to DW_OP_plus_uconst
146+
/// 2. DW_OP_constu, 0 to DW_OP_lit0
147+
static SmallVector<uint64_t>
148+
optimizeDwarfOperations(ArrayRef<uint64_t> WorkingOps) {
149+
DIExpressionCursor Cursor(WorkingOps);
150+
uint64_t Loc = 0;
151+
SmallVector<uint64_t> ResultOps;
152+
while (Loc < WorkingOps.size()) {
153+
auto Op1 = Cursor.peek();
154+
/// Expression has no operations, exit.
155+
if (!Op1)
156+
break;
157+
auto Op1Raw = Op1->getOp();
158+
159+
if (Op1Raw == dwarf::DW_OP_constu && Op1->getArg(0) == 0) {
160+
ResultOps.push_back(dwarf::DW_OP_lit0);
161+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
162+
continue;
163+
}
164+
165+
auto Op2 = Cursor.peekNext();
166+
/// Expression has no more operations, copy into ResultOps and exit.
167+
if (!Op2) {
168+
uint64_t PrevLoc = Loc;
169+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
170+
ResultOps.append(WorkingOps.begin() + PrevLoc, WorkingOps.begin() + Loc);
171+
break;
172+
}
173+
auto Op2Raw = Op2->getOp();
174+
175+
if (Op1Raw == dwarf::DW_OP_constu && Op2Raw == dwarf::DW_OP_plus) {
176+
ResultOps.push_back(dwarf::DW_OP_plus_uconst);
177+
ResultOps.push_back(Op1->getArg(0));
178+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
179+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
180+
continue;
181+
}
182+
uint64_t PrevLoc = Loc;
183+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
184+
ResultOps.append(WorkingOps.begin() + PrevLoc, WorkingOps.begin() + Loc);
185+
}
186+
return ResultOps;
187+
}
188+
189+
/// {DW_OP_constu, 0, DW_OP_[plus, minus, shl, shr]} -> {}
190+
/// {DW_OP_constu, 1, DW_OP_[mul, div]} -> {}
191+
static bool tryFoldNoOpMath(uint64_t Const1,
192+
ArrayRef<DIExpression::ExprOperand> Ops,
193+
uint64_t &Loc, DIExpressionCursor &Cursor,
194+
SmallVectorImpl<uint64_t> &WorkingOps) {
195+
196+
if (isNeutralElement(Ops[1].getOp(), Const1)) {
197+
WorkingOps.erase(WorkingOps.begin() + Loc, WorkingOps.begin() + Loc + 3);
198+
startFromBeginning(Loc, Cursor, WorkingOps);
199+
return true;
200+
}
201+
return false;
202+
}
203+
204+
/// {DW_OP_constu, Const1, DW_OP_constu, Const2, DW_OP_[plus,
205+
/// minus, mul, div, shl, shr] -> {DW_OP_constu, Const1 [+, -, *, /, <<, >>]
206+
/// Const2}
207+
static bool tryFoldConstants(uint64_t Const1,
208+
ArrayRef<DIExpression::ExprOperand> Ops,
209+
uint64_t &Loc, DIExpressionCursor &Cursor,
210+
SmallVectorImpl<uint64_t> &WorkingOps) {
211+
212+
auto Const2 = isConstantVal(Ops[1]);
213+
if (!Const2)
214+
return false;
215+
216+
auto Result = foldOperationIfPossible(
217+
Const1, *Const2, static_cast<dwarf::LocationAtom>(Ops[2].getOp()));
218+
if (!Result) {
219+
consumeOneOperator(Cursor, Loc, Ops[0]);
220+
return true;
221+
}
222+
WorkingOps.erase(WorkingOps.begin() + Loc + 2,
223+
WorkingOps.begin() + Loc + 5);
224+
WorkingOps[Loc] = dwarf::DW_OP_constu;
225+
WorkingOps[Loc + 1] = *Result;
226+
startFromBeginning(Loc, Cursor, WorkingOps);
227+
return true;
228+
}
229+
230+
/// {DW_OP_constu, Const1, DW_OP_[plus, mul], DW_OP_constu, Const2,
231+
/// DW_OP_[plus, mul]} -> {DW_OP_constu, Const1 [+, *] Const2, DW_OP_[plus,
232+
/// mul]}
233+
static bool tryFoldCommutativeMath(uint64_t Const1,
234+
ArrayRef<DIExpression::ExprOperand> Ops,
235+
uint64_t &Loc, DIExpressionCursor &Cursor,
236+
SmallVectorImpl<uint64_t> &WorkingOps) {
237+
238+
auto Const2 = isConstantVal(Ops[2]);
239+
auto Operand1 = static_cast<dwarf::LocationAtom>(Ops[1].getOp());
240+
auto Operand2 = static_cast<dwarf::LocationAtom>(Ops[3].getOp());
241+
242+
if (!Const2 || !operationsAreFoldableAndCommutative(Operand1, Operand2))
243+
return false;
244+
245+
auto Result = foldOperationIfPossible(Const1, *Const2, Operand1);
246+
if (!Result) {
247+
consumeOneOperator(Cursor, Loc, Ops[0]);
248+
return true;
249+
}
250+
WorkingOps.erase(WorkingOps.begin() + Loc + 3, WorkingOps.begin() + Loc + 6);
251+
WorkingOps[Loc] = dwarf::DW_OP_constu;
252+
WorkingOps[Loc + 1] = *Result;
253+
startFromBeginning(Loc, Cursor, WorkingOps);
254+
return true;
255+
}
256+
257+
/// {DW_OP_constu, Const1, DW_OP_[plus, mul], DW_OP_LLVM_arg, Arg1,
258+
/// DW_OP_[plus, mul], DW_OP_constu, Const2, DW_OP_[plus, mul]} ->
259+
/// {DW_OP_constu, Const1 [+, *] Const2, DW_OP_[plus, mul], DW_OP_LLVM_arg,
260+
/// Arg1, DW_OP_[plus, mul]}
261+
static bool tryFoldCommutativeMathWithArgInBetween(
262+
uint64_t Const1, ArrayRef<DIExpression::ExprOperand> Ops, uint64_t &Loc,
263+
DIExpressionCursor &Cursor, SmallVectorImpl<uint64_t> &WorkingOps) {
264+
265+
auto Const2 = isConstantVal(Ops[4]);
266+
auto Operand1 = static_cast<dwarf::LocationAtom>(Ops[1].getOp());
267+
auto Operand2 = static_cast<dwarf::LocationAtom>(Ops[3].getOp());
268+
auto Operand3 = static_cast<dwarf::LocationAtom>(Ops[5].getOp());
269+
270+
if (!Const2 || Ops[2].getOp() != dwarf::DW_OP_LLVM_arg ||
271+
!operationsAreFoldableAndCommutative(Operand1, Operand2) ||
272+
!operationsAreFoldableAndCommutative(Operand2, Operand3))
273+
return false;
274+
275+
auto Result = foldOperationIfPossible(Const1, *Const2, Operand1);
276+
if (!Result) {
277+
consumeOneOperator(Cursor, Loc, Ops[0]);
278+
return true;
279+
}
280+
WorkingOps.erase(WorkingOps.begin() + Loc + 6, WorkingOps.begin() + Loc + 9);
281+
WorkingOps[Loc] = dwarf::DW_OP_constu;
282+
WorkingOps[Loc + 1] = *Result;
283+
startFromBeginning(Loc, Cursor, WorkingOps);
284+
return true;
285+
}
286+
287+
DIExpression *DIExpression::foldConstantMath() {
288+
289+
SmallVector<uint64_t, 8> WorkingOps(Elements.begin(), Elements.end());
290+
uint64_t Loc = 0;
291+
SmallVector<uint64_t> ResultOps = canonicalizeDwarfOperations(WorkingOps);
292+
DIExpressionCursor Cursor(ResultOps);
293+
SmallVector<DIExpression::ExprOperand, 8> Ops;
294+
295+
// Iterate over all Operations in a DIExpression to match the smallest pattern
296+
// that can be folded.
297+
while (Loc < ResultOps.size()) {
298+
Ops.clear();
299+
300+
auto Op = Cursor.peek();
301+
// Expression has no operations, exit.
302+
if (!Op)
303+
break;
304+
305+
auto Const1 = isConstantVal(*Op);
306+
307+
if (!Const1) {
308+
// Early exit, all of the following patterns start with a constant value.
309+
consumeOneOperator(Cursor, Loc, *Op);
310+
continue;
311+
}
312+
313+
Ops.push_back(*Op);
314+
315+
Op = Cursor.peekNext();
316+
// All following patterns require at least 2 Operations, exit.
317+
if (!Op)
318+
break;
319+
320+
Ops.push_back(*Op);
321+
322+
// Try to fold a constant no-op, such as {+ 0}
323+
if (tryFoldNoOpMath(*Const1, Ops, Loc, Cursor, ResultOps))
324+
continue;
325+
326+
Op = Cursor.peekNextN(2);
327+
// Op[1] could still match a pattern, skip iteration.
328+
if (!Op) {
329+
consumeOneOperator(Cursor, Loc, Ops[0]);
330+
continue;
331+
}
332+
333+
Ops.push_back(*Op);
334+
335+
// Try to fold a pattern of two constants such as {C1 + C2}.
336+
if (tryFoldConstants(*Const1, Ops, Loc, Cursor, ResultOps))
337+
continue;
338+
339+
Op = Cursor.peekNextN(3);
340+
// Op[1] and Op[2] could still match a pattern, skip iteration.
341+
if (!Op) {
342+
consumeOneOperator(Cursor, Loc, Ops[0]);
343+
continue;
344+
}
345+
346+
Ops.push_back(*Op);
347+
348+
// Try to fold commutative constant math, such as {C1 + C2 +}.
349+
if (tryFoldCommutativeMath(*Const1, Ops, Loc, Cursor, ResultOps))
350+
continue;
351+
352+
Op = Cursor.peekNextN(4);
353+
if (!Op) {
354+
consumeOneOperator(Cursor, Loc, Ops[0]);
355+
continue;
356+
}
357+
358+
Ops.push_back(*Op);
359+
Op = Cursor.peekNextN(5);
360+
if (!Op) {
361+
consumeOneOperator(Cursor, Loc, Ops[0]);
362+
continue;
363+
}
364+
365+
Ops.push_back(*Op);
366+
367+
// Try to fold commutative constant math with an LLVM_Arg in between, such
368+
// as {C1 + Arg + C2 +}.
369+
if (tryFoldCommutativeMathWithArgInBetween(*Const1, Ops, Loc, Cursor,
370+
ResultOps))
371+
continue;
372+
373+
consumeOneOperator(Cursor, Loc, Ops[0]);
374+
}
375+
ResultOps = optimizeDwarfOperations(ResultOps);
376+
auto *Result = DIExpression::get(getContext(), ResultOps);
377+
assert(Result->isValid() && "concatenated expression is not valid");
378+
return Result;
379+
}

0 commit comments

Comments
 (0)