Skip to content

Commit 884ded7

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 5cfe70a commit 884ded7

File tree

5 files changed

+813
-1
lines changed

5 files changed

+813
-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: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
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+
static std::optional<uint64_t> isConstantVal(DIExpression::ExprOperand Op) {
20+
if (Op.getOp() == dwarf::DW_OP_constu)
21+
return Op.getArg(0);
22+
return std::nullopt;
23+
}
24+
25+
static bool isNeutralElement(uint64_t Op, uint64_t Val) {
26+
switch (Op) {
27+
case dwarf::DW_OP_plus:
28+
case dwarf::DW_OP_minus:
29+
case dwarf::DW_OP_shl:
30+
case dwarf::DW_OP_shr:
31+
return Val == 0;
32+
case dwarf::DW_OP_mul:
33+
case dwarf::DW_OP_div:
34+
return Val == 1;
35+
default:
36+
return false;
37+
}
38+
}
39+
40+
static std::optional<uint64_t> foldOperationIfPossible(
41+
DIExpression::ExprOperand Op1, DIExpression::ExprOperand Op2,
42+
DIExpression::ExprOperand Op3, bool &ConstantValCheckFailed) {
43+
44+
auto Operand1 = isConstantVal(Op1);
45+
auto Operand2 = isConstantVal(Op2);
46+
47+
if (!Operand1 || !Operand2) {
48+
ConstantValCheckFailed = true;
49+
return std::nullopt;
50+
}
51+
52+
auto Oper1 = *Operand1;
53+
auto Oper2 = *Operand2;
54+
55+
bool ResultOverflowed;
56+
switch (Op3.getOp()) {
57+
case dwarf::DW_OP_plus: {
58+
auto Result = SaturatingAdd(Oper1, Oper2, &ResultOverflowed);
59+
if (ResultOverflowed)
60+
return std::nullopt;
61+
return Result;
62+
}
63+
case dwarf::DW_OP_minus: {
64+
if (Oper1 < Oper2)
65+
return std::nullopt;
66+
return Oper1 - Oper2;
67+
}
68+
case dwarf::DW_OP_shl: {
69+
if ((uint64_t)countl_zero(Oper1) < Oper2)
70+
return std::nullopt;
71+
return Oper1 << Oper2;
72+
}
73+
case dwarf::DW_OP_shr: {
74+
if ((uint64_t)countr_zero(Oper1) < Oper2)
75+
return std::nullopt;
76+
return Oper1 >> Oper2;
77+
}
78+
case dwarf::DW_OP_mul: {
79+
auto Result = SaturatingMultiply(Oper1, Oper2, &ResultOverflowed);
80+
if (ResultOverflowed)
81+
return std::nullopt;
82+
return Result;
83+
}
84+
case dwarf::DW_OP_div: {
85+
if (Oper2)
86+
return Oper1 / Oper2;
87+
return std::nullopt;
88+
}
89+
default:
90+
return std::nullopt;
91+
}
92+
}
93+
94+
static bool operationsAreFoldableAndCommutative(uint64_t Op1, uint64_t Op2) {
95+
return Op1 == Op2 && (Op1 == dwarf::DW_OP_plus || Op1 == dwarf::DW_OP_mul);
96+
}
97+
98+
static void consumeOneOperator(DIExpressionCursor &Cursor, uint64_t &Loc,
99+
const DIExpression::ExprOperand &Op) {
100+
Cursor.consume(1);
101+
Loc = Loc + Op.getSize();
102+
}
103+
104+
void startFromBeginning(uint64_t &Loc, DIExpressionCursor &Cursor,
105+
ArrayRef<uint64_t> WorkingOps) {
106+
Cursor.assignNewExpr(WorkingOps);
107+
Loc = 0;
108+
}
109+
110+
static SmallVector<uint64_t>
111+
canonicalizeDwarfOperations(ArrayRef<uint64_t> WorkingOps) {
112+
DIExpressionCursor Cursor(WorkingOps);
113+
uint64_t Loc = 0;
114+
SmallVector<uint64_t> ResultOps;
115+
while (Loc < WorkingOps.size()) {
116+
auto Op = Cursor.peek();
117+
/// Expression has no operations, break.
118+
if (!Op)
119+
break;
120+
auto OpRaw = Op->getOp();
121+
auto OpArg = Op->getArg(0);
122+
123+
if (OpRaw >= dwarf::DW_OP_lit0 && OpRaw <= dwarf::DW_OP_lit31) {
124+
ResultOps.push_back(dwarf::DW_OP_constu);
125+
ResultOps.push_back(OpRaw - dwarf::DW_OP_lit0);
126+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
127+
continue;
128+
}
129+
if (OpRaw == dwarf::DW_OP_plus_uconst) {
130+
ResultOps.push_back(dwarf::DW_OP_constu);
131+
ResultOps.push_back(OpArg);
132+
ResultOps.push_back(dwarf::DW_OP_plus);
133+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
134+
continue;
135+
}
136+
uint64_t PrevLoc = Loc;
137+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
138+
ResultOps.append(WorkingOps.begin() + PrevLoc, WorkingOps.begin() + Loc);
139+
}
140+
return ResultOps;
141+
}
142+
143+
static SmallVector<uint64_t>
144+
optimizeDwarfOperations(ArrayRef<uint64_t> WorkingOps) {
145+
DIExpressionCursor Cursor(WorkingOps);
146+
uint64_t Loc = 0;
147+
SmallVector<uint64_t> ResultOps;
148+
while (Loc < WorkingOps.size()) {
149+
auto Op1 = Cursor.peek();
150+
/// Expression has no operations, exit.
151+
if (!Op1)
152+
break;
153+
auto Op1Raw = Op1->getOp();
154+
auto Op1Arg = Op1->getArg(0);
155+
156+
if (Op1Raw == dwarf::DW_OP_constu && Op1Arg == 0) {
157+
ResultOps.push_back(dwarf::DW_OP_lit0);
158+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
159+
continue;
160+
}
161+
162+
auto Op2 = Cursor.peekNext();
163+
/// Expression has no more operations, copy into ResultOps and exit.
164+
if (!Op2) {
165+
uint64_t PrevLoc = Loc;
166+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
167+
ResultOps.append(WorkingOps.begin() + PrevLoc, WorkingOps.begin() + Loc);
168+
break;
169+
}
170+
auto Op2Raw = Op2->getOp();
171+
172+
if (Op1Raw == dwarf::DW_OP_constu && Op2Raw == dwarf::DW_OP_plus) {
173+
ResultOps.push_back(dwarf::DW_OP_plus_uconst);
174+
ResultOps.push_back(Op1Arg);
175+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
176+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
177+
continue;
178+
}
179+
uint64_t PrevLoc = Loc;
180+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
181+
ResultOps.append(WorkingOps.begin() + PrevLoc, WorkingOps.begin() + Loc);
182+
}
183+
return ResultOps;
184+
}
185+
186+
static bool tryFoldNoOpMath(ArrayRef<DIExpression::ExprOperand> Ops,
187+
uint64_t &Loc, DIExpressionCursor &Cursor,
188+
SmallVectorImpl<uint64_t> &WorkingOps) {
189+
190+
if (isConstantVal(Ops[0]) &&
191+
isNeutralElement(Ops[1].getOp(), Ops[0].getArg(0))) {
192+
WorkingOps.erase(WorkingOps.begin() + Loc, WorkingOps.begin() + Loc + 3);
193+
startFromBeginning(Loc, Cursor, WorkingOps);
194+
return true;
195+
}
196+
return false;
197+
}
198+
199+
static bool tryFoldConstants(ArrayRef<DIExpression::ExprOperand> Ops,
200+
uint64_t &Loc, DIExpressionCursor &Cursor,
201+
SmallVectorImpl<uint64_t> &WorkingOps) {
202+
203+
bool ConstantValCheckFailed = false;
204+
auto Result =
205+
foldOperationIfPossible(Ops[0], Ops[1], Ops[2], ConstantValCheckFailed);
206+
if (ConstantValCheckFailed)
207+
return false;
208+
if (!Result) {
209+
consumeOneOperator(Cursor, Loc, Ops[0]);
210+
return true;
211+
}
212+
WorkingOps.erase(WorkingOps.begin() + Loc + 2,
213+
WorkingOps.begin() + Loc + 5);
214+
WorkingOps[Loc] = dwarf::DW_OP_constu;
215+
WorkingOps[Loc + 1] = *Result;
216+
startFromBeginning(Loc, Cursor, WorkingOps);
217+
return true;
218+
}
219+
220+
static bool tryFoldCommutativeMath(ArrayRef<DIExpression::ExprOperand> Ops,
221+
uint64_t &Loc, DIExpressionCursor &Cursor,
222+
SmallVectorImpl<uint64_t> &WorkingOps) {
223+
224+
bool ConstantValCheckFailed = false;
225+
if (operationsAreFoldableAndCommutative(Ops[1].getOp(), Ops[3].getOp())) {
226+
auto Result =
227+
foldOperationIfPossible(Ops[0], Ops[2], Ops[1], ConstantValCheckFailed);
228+
if (ConstantValCheckFailed)
229+
return false;
230+
if (!Result) {
231+
consumeOneOperator(Cursor, Loc, Ops[0]);
232+
return true;
233+
}
234+
WorkingOps.erase(WorkingOps.begin() + Loc + 3,
235+
WorkingOps.begin() + Loc + 6);
236+
WorkingOps[Loc] = dwarf::DW_OP_constu;
237+
WorkingOps[Loc + 1] = *Result;
238+
startFromBeginning(Loc, Cursor, WorkingOps);
239+
return true;
240+
}
241+
return false;
242+
}
243+
244+
static bool tryFoldCommutativeMathWithArgInBetween(
245+
ArrayRef<DIExpression::ExprOperand> Ops, uint64_t &Loc,
246+
DIExpressionCursor &Cursor, SmallVectorImpl<uint64_t> &WorkingOps) {
247+
248+
bool ConstantValCheckFailed = false;
249+
if (Ops[2].getOp() == dwarf::DW_OP_LLVM_arg &&
250+
operationsAreFoldableAndCommutative(Ops[1].getOp(), Ops[3].getOp()) &&
251+
operationsAreFoldableAndCommutative(Ops[3].getOp(), Ops[5].getOp())) {
252+
auto Result =
253+
foldOperationIfPossible(Ops[0], Ops[4], Ops[1], ConstantValCheckFailed);
254+
if (ConstantValCheckFailed)
255+
return false;
256+
if (!Result) {
257+
consumeOneOperator(Cursor, Loc, Ops[0]);
258+
return true;
259+
}
260+
WorkingOps.erase(WorkingOps.begin() + Loc + 6,
261+
WorkingOps.begin() + Loc + 9);
262+
WorkingOps[Loc] = dwarf::DW_OP_constu;
263+
WorkingOps[Loc + 1] = *Result;
264+
startFromBeginning(Loc, Cursor, WorkingOps);
265+
return true;
266+
}
267+
return false;
268+
}
269+
270+
DIExpression *DIExpression::foldConstantMath() {
271+
272+
SmallVector<uint64_t, 8> WorkingOps(Elements.begin(), Elements.end());
273+
uint64_t Loc = 0;
274+
SmallVector<uint64_t> ResultOps = canonicalizeDwarfOperations(WorkingOps);
275+
DIExpressionCursor Cursor(ResultOps);
276+
SmallVector<DIExpression::ExprOperand, 8> Ops;
277+
278+
while (Loc < ResultOps.size()) {
279+
Ops.clear();
280+
281+
auto Op = Cursor.peek();
282+
// Expression has no operations, exit.
283+
if (!Op)
284+
break;
285+
286+
Ops.push_back(*Op);
287+
288+
if (!isConstantVal(Ops[0])) {
289+
// Early exit, all of the following patterns start with a constant value.
290+
consumeOneOperator(Cursor, Loc, *Op);
291+
continue;
292+
}
293+
294+
Op = Cursor.peekNext();
295+
// All following patterns require at least 2 Operations, exit.
296+
if (!Op)
297+
break;
298+
299+
Ops.push_back(*Op);
300+
301+
if (tryFoldNoOpMath(Ops, Loc, Cursor, ResultOps))
302+
continue;
303+
304+
Op = Cursor.peekNextN(2);
305+
// Op[1] could still match a pattern, skip iteration.
306+
if (!Op) {
307+
consumeOneOperator(Cursor, Loc, Ops[0]);
308+
continue;
309+
}
310+
311+
Ops.push_back(*Op);
312+
if (tryFoldConstants(Ops, Loc, Cursor, ResultOps))
313+
continue;
314+
315+
Op = Cursor.peekNextN(3);
316+
// Op[1] and Op[2] could still match a pattern, skip iteration.
317+
if (!Op) {
318+
consumeOneOperator(Cursor, Loc, Ops[0]);
319+
continue;
320+
}
321+
322+
Ops.push_back(*Op);
323+
if (tryFoldCommutativeMath(Ops, Loc, Cursor, ResultOps))
324+
continue;
325+
326+
Op = Cursor.peekNextN(4);
327+
if (!Op) {
328+
consumeOneOperator(Cursor, Loc, Ops[0]);
329+
continue;
330+
}
331+
332+
Ops.push_back(*Op);
333+
Op = Cursor.peekNextN(5);
334+
if (!Op) {
335+
consumeOneOperator(Cursor, Loc, Ops[0]);
336+
continue;
337+
}
338+
339+
Ops.push_back(*Op);
340+
if (tryFoldCommutativeMathWithArgInBetween(Ops, Loc, Cursor, ResultOps))
341+
continue;
342+
343+
consumeOneOperator(Cursor, Loc, Ops[0]);
344+
}
345+
ResultOps = optimizeDwarfOperations(ResultOps);
346+
auto *Result = DIExpression::get(getContext(), ResultOps);
347+
assert(Result->isValid() && "concatenated expression is not valid");
348+
return Result;
349+
}

llvm/lib/IR/DebugInfoMetadata.cpp

Lines changed: 1 addition & 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");

0 commit comments

Comments
 (0)