Skip to content

Commit 04bb1b4

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 6bb96a0 commit 04bb1b4

File tree

6 files changed

+908
-1
lines changed

6 files changed

+908
-1
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//===- llvm/IR/DIExpressionOptimizer.h - Constant folding of DIExpressions --*-
2+
//C++ -*-===//
3+
//
4+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5+
// See https://llvm.org/LICENSE.txt for license information.
6+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
//
8+
//===----------------------------------------------------------------------===//
9+
//
10+
// Declarations for functions to constant fold DIExpressions.
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#ifndef LLVM_IR_DIEXPRESSIONOPTIMIZER_H
15+
#define LLVM_IR_DIEXPRESSIONOPTIMIZER_H
16+
17+
#include "llvm/IR/DebugInfoMetadata.h"
18+
19+
using namespace llvm;
20+
21+
/// Returns true if the Op is a DW_OP_constu.
22+
bool isConstantVal(uint64_t Op);
23+
24+
/// Returns true if an operation and operand result in a No Op.
25+
bool isNeutralElement(uint64_t Op, uint64_t Val);
26+
27+
/// Try to fold constant math operations and return the result if possible.
28+
std::optional<uint64_t> foldOperationIfPossible(uint64_t Op, uint64_t Operand1,
29+
uint64_t Operand2);
30+
31+
/// Returns true if the two operations are commutative and can be folded.
32+
bool operationsAreFoldableAndCommutative(uint64_t Op1, uint64_t Op2);
33+
34+
/// Consume one operator and its operand(s).
35+
void consumeOneOperator(DIExpressionCursor &Cursor, uint64_t &Loc,
36+
const DIExpression::ExprOperand &Op);
37+
38+
/// Reset the Cursor to the beginning of the WorkingOps.
39+
void startFromBeginning(uint64_t &Loc, DIExpressionCursor &Cursor,
40+
ArrayRef<uint64_t> WorkingOps);
41+
42+
/// This function will canonicalize:
43+
/// 1. DW_OP_plus_uconst to DW_OP_constu <const-val> DW_OP_plus
44+
/// 2. DW_OP_lit<n> to DW_OP_constu <n>
45+
SmallVector<uint64_t>
46+
canonicalizeDwarfOperations(ArrayRef<uint64_t> WorkingOps);
47+
48+
/// This function will convert:
49+
/// 1. DW_OP_constu <const-val> DW_OP_plus to DW_OP_plus_uconst
50+
/// 2. DW_OP_constu, 0 to DW_OP_lit0
51+
SmallVector<uint64_t> optimizeDwarfOperations(ArrayRef<uint64_t> WorkingOps);
52+
53+
/// {DW_OP_constu, 0, DW_OP_[plus, minus, shl, shr]} -> {}
54+
/// {DW_OP_constu, 1, DW_OP_[mul, div]} -> {}
55+
bool tryFoldNoOpMath(ArrayRef<DIExpression::ExprOperand> Ops, uint64_t &Loc,
56+
DIExpressionCursor &Cursor,
57+
SmallVectorImpl<uint64_t> &WorkingOps);
58+
59+
/// {DW_OP_constu, Const1, DW_OP_constu, Const2, DW_OP_[plus,
60+
/// minus, mul, div, shl, shr] -> {DW_OP_constu, Const1 [+, -, *, /, <<, >>]
61+
/// Const2}
62+
bool tryFoldConstants(ArrayRef<DIExpression::ExprOperand> Ops, uint64_t &Loc,
63+
DIExpressionCursor &Cursor,
64+
SmallVectorImpl<uint64_t> &WorkingOps);
65+
66+
/// {DW_OP_constu, Const1, DW_OP_[plus, mul], DW_OP_constu, Const2,
67+
/// DW_OP_[plus, mul]} -> {DW_OP_constu, Const1 [+, *] Const2, DW_OP_[plus,
68+
/// mul]}
69+
bool tryFoldCommutativeMath(ArrayRef<DIExpression::ExprOperand> Ops,
70+
uint64_t &Loc, DIExpressionCursor &Cursor,
71+
SmallVectorImpl<uint64_t> &WorkingOps);
72+
73+
/// {DW_OP_constu, Const1, DW_OP_[plus, mul], DW_OP_LLVM_arg, Arg1,
74+
/// DW_OP_[plus, mul], DW_OP_constu, Const2, DW_OP_[plus, mul]} ->
75+
/// {DW_OP_constu, Const1 [+, *] Const2, DW_OP_[plus, mul], DW_OP_LLVM_arg,
76+
/// Arg1, DW_OP_[plus, mul]}
77+
bool tryFoldCommutativeMathWithArgInBetween(
78+
ArrayRef<DIExpression::ExprOperand> Ops, uint64_t &Loc,
79+
DIExpressionCursor &Cursor, SmallVectorImpl<uint64_t> &WorkingOps);
80+
81+
#endif // LLVM_IR_DIEXPRESSIONOPTIMIZER_H

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: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
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.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "llvm/IR/DIExpressionOptimizer.h"
14+
#include "llvm/BinaryFormat/Dwarf.h"
15+
16+
bool isConstantVal(uint64_t Op) { return Op == dwarf::DW_OP_constu; }
17+
18+
bool isNeutralElement(uint64_t Op, uint64_t Val) {
19+
switch (Op) {
20+
case dwarf::DW_OP_plus:
21+
case dwarf::DW_OP_minus:
22+
case dwarf::DW_OP_shl:
23+
case dwarf::DW_OP_shr:
24+
return Val == 0;
25+
case dwarf::DW_OP_mul:
26+
case dwarf::DW_OP_div:
27+
return Val == 1;
28+
default:
29+
return false;
30+
}
31+
}
32+
33+
std::optional<uint64_t> foldOperationIfPossible(uint64_t Op, uint64_t Operand1,
34+
uint64_t Operand2) {
35+
bool ResultOverflowed;
36+
switch (Op) {
37+
case dwarf::DW_OP_plus: {
38+
auto Result = SaturatingAdd(Operand1, Operand2, &ResultOverflowed);
39+
if (ResultOverflowed)
40+
return std::nullopt;
41+
return Result;
42+
}
43+
case dwarf::DW_OP_minus: {
44+
if (Operand1 < Operand2)
45+
return std::nullopt;
46+
return Operand1 - Operand2;
47+
}
48+
case dwarf::DW_OP_shl: {
49+
if ((uint64_t)countl_zero(Operand1) < Operand2)
50+
return std::nullopt;
51+
return Operand1 << Operand2;
52+
}
53+
case dwarf::DW_OP_shr: {
54+
if ((uint64_t)countr_zero(Operand1) < Operand2)
55+
return std::nullopt;
56+
return Operand1 >> Operand2;
57+
}
58+
case dwarf::DW_OP_mul: {
59+
auto Result = SaturatingMultiply(Operand1, Operand2, &ResultOverflowed);
60+
if (ResultOverflowed)
61+
return std::nullopt;
62+
return Result;
63+
}
64+
case dwarf::DW_OP_div: {
65+
if (Operand2)
66+
return Operand1 / Operand2;
67+
return std::nullopt;
68+
}
69+
default:
70+
return std::nullopt;
71+
}
72+
}
73+
74+
bool operationsAreFoldableAndCommutative(uint64_t Op1, uint64_t Op2) {
75+
if (Op1 != Op2)
76+
return false;
77+
switch (Op1) {
78+
case dwarf::DW_OP_plus:
79+
case dwarf::DW_OP_mul:
80+
return true;
81+
default:
82+
return false;
83+
}
84+
}
85+
86+
void consumeOneOperator(DIExpressionCursor &Cursor, uint64_t &Loc,
87+
const DIExpression::ExprOperand &Op) {
88+
Cursor.consume(1);
89+
Loc = Loc + Op.getSize();
90+
}
91+
92+
void startFromBeginning(uint64_t &Loc, DIExpressionCursor &Cursor,
93+
ArrayRef<uint64_t> WorkingOps) {
94+
Cursor.assignNewExpr(WorkingOps);
95+
Loc = 0;
96+
}
97+
98+
SmallVector<uint64_t>
99+
canonicalizeDwarfOperations(ArrayRef<uint64_t> WorkingOps) {
100+
DIExpressionCursor Cursor(WorkingOps);
101+
uint64_t Loc = 0;
102+
SmallVector<uint64_t> ResultOps;
103+
while (Loc < WorkingOps.size()) {
104+
auto Op = Cursor.peek();
105+
/// Expression has no operations, break.
106+
if (!Op)
107+
break;
108+
auto OpRaw = Op->getOp();
109+
auto OpArg = Op->getArg(0);
110+
111+
if (OpRaw >= dwarf::DW_OP_lit0 && OpRaw <= dwarf::DW_OP_lit31) {
112+
ResultOps.push_back(dwarf::DW_OP_constu);
113+
ResultOps.push_back(OpRaw - dwarf::DW_OP_lit0);
114+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
115+
continue;
116+
}
117+
if (OpRaw == dwarf::DW_OP_plus_uconst) {
118+
ResultOps.push_back(dwarf::DW_OP_constu);
119+
ResultOps.push_back(OpArg);
120+
ResultOps.push_back(dwarf::DW_OP_plus);
121+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
122+
continue;
123+
}
124+
uint64_t PrevLoc = Loc;
125+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
126+
ResultOps.append(WorkingOps.begin() + PrevLoc, WorkingOps.begin() + Loc);
127+
}
128+
return ResultOps;
129+
}
130+
131+
SmallVector<uint64_t> optimizeDwarfOperations(ArrayRef<uint64_t> WorkingOps) {
132+
DIExpressionCursor Cursor(WorkingOps);
133+
uint64_t Loc = 0;
134+
SmallVector<uint64_t> ResultOps;
135+
while (Loc < WorkingOps.size()) {
136+
auto Op1 = Cursor.peek();
137+
/// Expression has no operations, exit.
138+
if (!Op1)
139+
break;
140+
auto Op1Raw = Op1->getOp();
141+
auto Op1Arg = Op1->getArg(0);
142+
143+
if (Op1Raw == dwarf::DW_OP_constu && Op1Arg == 0) {
144+
ResultOps.push_back(dwarf::DW_OP_lit0);
145+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
146+
continue;
147+
}
148+
149+
auto Op2 = Cursor.peekNext();
150+
/// Expression has no more operations, copy into ResultOps and exit.
151+
if (!Op2) {
152+
uint64_t PrevLoc = Loc;
153+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
154+
ResultOps.append(WorkingOps.begin() + PrevLoc, WorkingOps.begin() + Loc);
155+
break;
156+
}
157+
auto Op2Raw = Op2->getOp();
158+
159+
if (Op1Raw == dwarf::DW_OP_constu && Op2Raw == dwarf::DW_OP_plus) {
160+
ResultOps.push_back(dwarf::DW_OP_plus_uconst);
161+
ResultOps.push_back(Op1Arg);
162+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
163+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
164+
continue;
165+
}
166+
uint64_t PrevLoc = Loc;
167+
consumeOneOperator(Cursor, Loc, *Cursor.peek());
168+
ResultOps.append(WorkingOps.begin() + PrevLoc, WorkingOps.begin() + Loc);
169+
}
170+
return ResultOps;
171+
}
172+
173+
bool tryFoldNoOpMath(ArrayRef<DIExpression::ExprOperand> Ops, uint64_t &Loc,
174+
DIExpressionCursor &Cursor,
175+
SmallVectorImpl<uint64_t> &WorkingOps) {
176+
auto Op1 = Ops[0];
177+
auto Op2 = Ops[1];
178+
auto Op1Raw = Op1.getOp();
179+
auto Op1Arg = Op1.getArg(0);
180+
auto Op2Raw = Op2.getOp();
181+
182+
if (isConstantVal(Op1Raw) && isNeutralElement(Op2Raw, Op1Arg)) {
183+
WorkingOps.erase(WorkingOps.begin() + Loc, WorkingOps.begin() + Loc + 3);
184+
startFromBeginning(Loc, Cursor, WorkingOps);
185+
return true;
186+
}
187+
return false;
188+
}
189+
190+
bool tryFoldConstants(ArrayRef<DIExpression::ExprOperand> Ops, uint64_t &Loc,
191+
DIExpressionCursor &Cursor,
192+
SmallVectorImpl<uint64_t> &WorkingOps) {
193+
auto Op1 = Ops[0];
194+
auto Op2 = Ops[1];
195+
auto Op3 = Ops[2];
196+
auto Op1Raw = Op1.getOp();
197+
auto Op1Arg = Op1.getArg(0);
198+
auto Op2Raw = Op2.getOp();
199+
auto Op2Arg = Op2.getArg(0);
200+
auto Op3Raw = Op3.getOp();
201+
202+
if (isConstantVal(Op1Raw) && isConstantVal(Op2Raw)) {
203+
auto Result = foldOperationIfPossible(Op3Raw, Op1Arg, Op2Arg);
204+
if (!Result) {
205+
consumeOneOperator(Cursor, Loc, Op1);
206+
return true;
207+
}
208+
WorkingOps.erase(WorkingOps.begin() + Loc + 2,
209+
WorkingOps.begin() + Loc + 5);
210+
WorkingOps[Loc] = dwarf::DW_OP_constu;
211+
WorkingOps[Loc + 1] = *Result;
212+
startFromBeginning(Loc, Cursor, WorkingOps);
213+
return true;
214+
}
215+
return false;
216+
}
217+
218+
bool tryFoldCommutativeMath(ArrayRef<DIExpression::ExprOperand> Ops,
219+
uint64_t &Loc, DIExpressionCursor &Cursor,
220+
SmallVectorImpl<uint64_t> &WorkingOps) {
221+
222+
auto Op1 = Ops[0];
223+
auto Op2 = Ops[1];
224+
auto Op3 = Ops[2];
225+
auto Op4 = Ops[3];
226+
auto Op1Raw = Op1.getOp();
227+
auto Op1Arg = Op1.getArg(0);
228+
auto Op2Raw = Op2.getOp();
229+
auto Op3Raw = Op3.getOp();
230+
auto Op3Arg = Op3.getArg(0);
231+
auto Op4Raw = Op4.getOp();
232+
233+
if (isConstantVal(Op1Raw) && isConstantVal(Op3Raw) &&
234+
operationsAreFoldableAndCommutative(Op2Raw, Op4Raw)) {
235+
auto Result = foldOperationIfPossible(Op2Raw, Op1Arg, Op3Arg);
236+
if (!Result)
237+
return false;
238+
WorkingOps.erase(WorkingOps.begin() + Loc + 3,
239+
WorkingOps.begin() + Loc + 6);
240+
WorkingOps[Loc] = dwarf::DW_OP_constu;
241+
WorkingOps[Loc + 1] = *Result;
242+
startFromBeginning(Loc, Cursor, WorkingOps);
243+
return true;
244+
}
245+
return false;
246+
}
247+
248+
bool tryFoldCommutativeMathWithArgInBetween(
249+
ArrayRef<DIExpression::ExprOperand> Ops, uint64_t &Loc,
250+
DIExpressionCursor &Cursor, SmallVectorImpl<uint64_t> &WorkingOps) {
251+
auto Op1 = Ops[0];
252+
auto Op2 = Ops[1];
253+
auto Op3 = Ops[2];
254+
auto Op4 = Ops[3];
255+
auto Op5 = Ops[4];
256+
auto Op6 = Ops[5];
257+
auto Op1Raw = Op1.getOp();
258+
auto Op1Arg = Op1.getArg(0);
259+
auto Op2Raw = Op2.getOp();
260+
auto Op3Raw = Op3.getOp();
261+
auto Op4Raw = Op4.getOp();
262+
auto Op5Raw = Op5.getOp();
263+
auto Op5Arg = Op5.getArg(0);
264+
auto Op6Raw = Op6.getOp();
265+
266+
if (isConstantVal(Op1Raw) && Op3Raw == dwarf::DW_OP_LLVM_arg &&
267+
isConstantVal(Op5Raw) &&
268+
operationsAreFoldableAndCommutative(Op2Raw, Op4Raw) &&
269+
operationsAreFoldableAndCommutative(Op4Raw, Op6Raw)) {
270+
auto Result = foldOperationIfPossible(Op2Raw, Op1Arg, Op5Arg);
271+
if (!Result)
272+
return false;
273+
WorkingOps.erase(WorkingOps.begin() + Loc + 6,
274+
WorkingOps.begin() + Loc + 9);
275+
WorkingOps[Loc] = dwarf::DW_OP_constu;
276+
WorkingOps[Loc + 1] = *Result;
277+
startFromBeginning(Loc, Cursor, WorkingOps);
278+
return true;
279+
}
280+
return false;
281+
}

0 commit comments

Comments
 (0)