Jlm
Loading...
Searching...
No Matches
Trace.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2025 HÃ¥vard Krogstie <krogstie.havard@gmail.com>
3 * See COPYING for terms of redistribution.
4 */
5
12#include <jlm/llvm/ir/Trace.hpp>
13#include <jlm/llvm/ir/types.hpp>
15#include <jlm/rvsdg/gamma.hpp>
16#include <jlm/rvsdg/region.hpp>
18#include <jlm/rvsdg/theta.hpp>
19#include <jlm/rvsdg/Trace.hpp>
20#include <jlm/rvsdg/type.hpp>
21#include <jlm/util/common.hpp>
22#include <jlm/util/Math.hpp>
23
24namespace jlm::llvm
25{
26
27OutputTracer::OutputTracer(const bool enableCaching)
28 : rvsdg::OutputTracer(enableCaching)
29{}
30
33{
34 auto & trace1 = rvsdg::OutputTracer::traceStep(output, withinRegion);
35
36 if (const auto [node, ioBarrierOp] =
38 node && ioBarrierOp)
39 {
41 }
42
43 // If enabled, try tracing through the memory states of load nodes
45 {
46 if (const auto [node, loadOp] = rvsdg::TryGetSimpleNodeAndOptionalOp<LoadOperation>(trace1);
47 node && loadOp)
48 {
49 if (is<MemoryStateType>(trace1.Type()))
50 {
51 // Map the memory state output to the corresponding memory state input
52 auto & memoryStateInput = LoadOperation::MapMemoryStateOutputToInput(trace1);
53 return *memoryStateInput.origin();
54 }
55 }
56 }
57
58 return trace1;
59}
60
62traceOutput(rvsdg::Output & output, const rvsdg::Region * withinRegion)
63{
64 constexpr bool enableCaching = false;
65 OutputTracer tracer(enableCaching);
66 return tracer.trace(output, withinRegion);
67}
68
69std::optional<int64_t>
71{
72 const auto & normalized = llvm::traceOutput(output, nullptr);
73
74 if (const auto [_, constant] =
76 constant)
77 {
78 const auto & rep = constant->Representation();
79 if (rep.is_known() && rep.nbits() <= 64)
80 return rep.to_int();
81 return std::nullopt;
82 }
83
84 if (const auto [_, constant] =
86 constant)
87 {
88 const auto & rep = constant->value();
89 if (rep.is_known() && rep.nbits() <= 64)
90 return rep.to_int();
91 return std::nullopt;
92 }
93
94 if (const auto [sextNode, sextOp] =
96 sextOp)
97 {
98 const auto inputValue = tryGetConstantSignedInteger(*sextNode->input(0)->origin());
99 if (!inputValue.has_value())
100 return std::nullopt;
101
102 // When doing sign extensions, we only need to care about the size of the input type
103 const auto inputBits = sextOp->nsrcbits();
104 return util::truncateAndSignExtend(*inputValue, inputBits);
105 }
106
107 if (const auto [zextNode, zextOp] =
109 zextOp)
110 {
111 const auto inputValue = tryGetConstantSignedInteger(*zextNode->input(0)->origin());
112 if (!inputValue.has_value())
113 return std::nullopt;
114
115 // When doing zero extensions, we only need to care about the size of the input type
116 const auto inputBits = zextOp->nsrcbits();
117 return util::truncateAndZeroExtend(*inputValue, inputBits);
118 }
119
120 if (const auto [truncNode, truncOp] =
122 truncOp)
123 {
124 const auto inputValue = tryGetConstantSignedInteger(*truncNode->input(0)->origin());
125 if (!inputValue.has_value())
126 return std::nullopt;
127
128 const auto outputBits = truncOp->ndstbits();
129 // When truncating, we still need to fill the high bits with something.
130 // We chose to sign extend, but this is mainly an aestetic choice
131 return util::truncateAndSignExtend(*inputValue, outputBits);
132 }
133
134 return std::nullopt;
135}
136
137std::optional<int64_t>
139{
140 if (!gepConstants.has_value())
141 return std::nullopt;
142
143 int64_t offsetInBytes = 0;
144 for (auto gepConstant : *gepConstants)
145 {
146 offsetInBytes += gepConstant.getOffsetInBytes();
147 }
148
149 return offsetInBytes;
150}
151
154{
155 const rvsdg::Output * base = &p;
156 std::vector<GetElementPtrOperation::Constant> gepConstants;
157
158 while (true)
159 {
160 // Use normalization function to get past all trivially invariant operations
161 base = &llvm::traceOutput(*base);
162
163 if (const auto [gepNode, gepOperation] =
165 gepOperation)
166 {
167 if (const auto gepConstantOpt = GetElementPtrOperation::tryGetAsConstant(*gepNode);
168 gepConstantOpt.has_value())
169 {
170 base = gepNode->input(0)->origin();
171 gepConstants.emplace_back(gepConstantOpt.value());
172 continue;
173 }
174 }
175
176 // We were not able to trace further
177 break;
178 }
179
180 return TracedPointerOrigin{ base, gepConstants };
181}
182
183static bool
185 const rvsdg::Output * basePointer,
186 std::optional<int64_t> offsetInBytes,
187 TraceCollection & traceCollection,
188 const size_t maxTraceCollectionSize)
189{
190 if (traceCollection.AllTracedOutputs.size() >= maxTraceCollectionSize)
191 return false;
192
193 // Normalize the pointer first, to avoid tracing trivial temporary outputs
194 basePointer = &llvm::traceOutput(*basePointer);
195
196 auto it = traceCollection.AllTracedOutputs.find(basePointer);
197 if (it != traceCollection.AllTracedOutputs.end())
198 {
199 // If the base pointer has already been traced with an unknown offset, we have nothing to add
200 if (!it->second.has_value())
201 return true;
202
203 // The offset used for the base pointer the last time it was traced
204 const auto prevOffset = *it->second;
205
206 // If we are visiting the same base pointer again with the same offset, we have nothing to add
207 if (offsetInBytes.has_value() && *offsetInBytes == prevOffset)
208 return true;
209
210 // We have different offsets to last time, collapse to unknown offset
211 offsetInBytes = std::nullopt;
212 }
213
214 traceCollection.AllTracedOutputs[basePointer] = offsetInBytes;
215
216 // If it is a GEP, we can trace through it, but possibly lose precise offset information
217 if (const auto [gepNode, gepOperation] =
219 gepOperation)
220 {
221 // Update the base pointer and offset to represent the other side of the GEP
222 basePointer = GetElementPtrOperation::getBaseAddressInput(*gepNode).origin();
223
224 // If we have precisely tracked the offset so far, try updating it with the GEPs offset
225 if (offsetInBytes.has_value())
226 {
227 if (const auto gepConstant = GetElementPtrOperation::tryGetAsConstant(*gepNode);
228 gepConstant.has_value())
229 offsetInBytes = *offsetInBytes + gepConstant->getOffsetInBytes();
230 else
231 offsetInBytes = std::nullopt;
232 }
233
235 basePointer,
236 offsetInBytes,
237 traceCollection,
238 maxTraceCollectionSize);
239 }
240
241 // If the node is a \ref SelectOperation, trace through both possible inputs
242 if (const auto [node, select] =
244 select)
245 {
247 node->input(1)->origin(),
248 offsetInBytes,
249 traceCollection,
250 maxTraceCollectionSize)
252 node->input(2)->origin(),
253 offsetInBytes,
254 traceCollection,
255 maxTraceCollectionSize);
256 }
257
258 // If we reach undef nodes, do not include them in the TopOrigins
260 {
261 return true;
262 }
263
264 // Trace into gamma nodes
265 if (auto gamma = rvsdg::TryGetOwnerNode<rvsdg::GammaNode>(*basePointer))
266 {
267 auto exitVar = gamma->MapOutputExitVar(*basePointer);
268 for (auto result : exitVar.branchResult)
269 {
270 // If tracing gives up, we give up
272 result->origin(),
273 offsetInBytes,
274 traceCollection,
275 maxTraceCollectionSize))
276 return false;
277 }
278
279 return true;
280 }
281
282 // Normalization never stops at a gamma entry variable
284
285 // Trace into theta nodes
286 if (auto theta = rvsdg::TryGetOwnerNode<rvsdg::ThetaNode>(*basePointer))
287 {
288 auto loopVar = theta->MapOutputLoopVar(*basePointer);
289
290 // Invariant loop variables should already have been handled by normalization
293 loopVar.post->origin(),
294 offsetInBytes,
295 traceCollection,
296 maxTraceCollectionSize);
297 }
298
299 // Trace loop variable pre arguments in theta nodes
300 if (auto theta = rvsdg::TryGetRegionParentNode<rvsdg::ThetaNode>(*basePointer))
301 {
302 auto loopVar = theta->MapPreLoopVar(*basePointer);
303
304 // Invariant loop variables should already have been handled by normalization
306
307 // Trace both from the post and the loop variable input
309 loopVar.post->origin(),
310 offsetInBytes,
311 traceCollection,
312 maxTraceCollectionSize)
314 loopVar.input->origin(),
315 offsetInBytes,
316 traceCollection,
317 maxTraceCollectionSize);
318 }
319
320 // We could not trace further, add p as a TopOrigin
321 traceCollection.TopOrigins[basePointer] = offsetInBytes;
322 return true;
323}
324
325bool
328 TraceCollection & traceCollection,
329 const size_t maxTraceCollectionSize)
330{
332 p.BasePointer,
334 traceCollection,
335 maxTraceCollectionSize);
336}
337
338}
static rvsdg::Input & getBaseAddressInput(rvsdg::Node &node)
static std::optional< Constant > tryGetAsConstant(const rvsdg::SimpleNode &gepNode)
static rvsdg::Input & BarredInput(const rvsdg::Node &node) noexcept
Definition IOBarrier.hpp:70
static rvsdg::Input & MapMemoryStateOutputToInput(const rvsdg::Output &output)
Definition Load.hpp:157
rvsdg::Output & traceStep(rvsdg::Output &output, const rvsdg::Region *withinRegion) override
Definition Trace.cpp:32
OutputTracer(bool enableCaching)
Definition Trace.cpp:27
Output * origin() const noexcept
Definition node.hpp:58
Output & trace(Output &output)
Definition Trace.cpp:22
virtual Output & traceStep(Output &output, const rvsdg::Region *withinRegion)
Definition Trace.cpp:145
Represent acyclic RVSDG subgraphs.
Definition region.hpp:213
#define JLM_ASSERT(x)
Definition common.hpp:16
Global memory state passed between functions.
bool TraceAllPointerOrigins(TracedPointerOrigin p, TraceCollection &traceCollection, const size_t maxTraceCollectionSize)
Definition Trace.cpp:326
rvsdg::Output & traceOutput(rvsdg::Output &output, const rvsdg::Region *withinRegion)
Definition Trace.cpp:62
static bool traceAllPointerOriginsInternal(const rvsdg::Output *basePointer, std::optional< int64_t > offsetInBytes, TraceCollection &traceCollection, const size_t maxTraceCollectionSize)
Definition Trace.cpp:184
TracedPointerOrigin TracePointerOriginPrecise(const rvsdg::Output &p)
Definition Trace.cpp:153
std::optional< int64_t > tryGetConstantSignedInteger(const rvsdg::Output &output)
Definition Trace.cpp:70
static bool ThetaLoopVarIsInvariant(const ThetaNode::LoopVar &loopVar) noexcept
Definition theta.hpp:227
NodeType * TryGetOwnerNode(const rvsdg::Input &input) noexcept
Checks if this is an input to a node of specified type.
Definition node.hpp:872
int64_t truncateAndZeroExtend(int64_t value, uint64_t keepBits)
Definition Math.hpp:156
int64_t truncateAndSignExtend(int64_t value, uint64_t keepBits)
Definition Math.hpp:134
std::unordered_map< const rvsdg::Output *, std::optional< int64_t > > TopOrigins
Definition Trace.hpp:135
std::unordered_map< const rvsdg::Output *, std::optional< int64_t > > AllTracedOutputs
Definition Trace.hpp:128
std::optional< std::vector< GetElementPtrOperation::Constant > > gepConstants
Definition Trace.hpp:102
const rvsdg::Output * BasePointer
Definition Trace.hpp:101
std::optional< int64_t > getOffsetInBytes() const noexcept
Definition Trace.cpp:138