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
30
33 rvsdg::Output & output,
34 BackEdgeState backEdgeState,
35 const rvsdg::Region * withinRegion)
36{
37 const auto trace1 = rvsdg::OutputTracer::traceStep(output, backEdgeState, withinRegion);
38
39 // Impossible origins can not be traced any further
40 if (trace1.isDeadEnd())
41 return trace1;
42
43 auto & trace1Output = trace1.getOutput();
44
46 {
47 if (const auto [node, ioBarrierOp] =
49 node && ioBarrierOp)
50 {
51 return TraceStepResult::createStepOutput(*IOBarrierOperation::BarredInput(*node).origin());
52 }
53
54 if (const auto [node, memoryHoistBarrierOp] =
56 node && memoryHoistBarrierOp)
57 {
58 return TraceStepResult::createStepOutput(
60 }
61 }
62
63 // If enabled, try tracing through the memory states of load nodes
65 {
66 if (const auto [node, loadOp] =
68 node && loadOp)
69 {
70 if (is<MemoryStateType>(trace1Output.Type()))
71 {
72 // Map the memory state output to the corresponding memory state input
73 auto & memoryStateInput = LoadOperation::MapMemoryStateOutputToInput(trace1Output);
74 return TraceStepResult::createStepOutput(*memoryStateInput.origin());
75 }
76 }
77 }
78
79 // We were not able to make any extra progress on the trace result, so return it as is
80 return trace1;
81}
82
84traceOutput(rvsdg::Output & output, bool mayEnterSubregions, const rvsdg::Region * withinRegion)
85{
86 OutputTracer tracer;
88 mayEnterSubregions
91 tracer.setRegionPredicateCheckingEnabled(mayEnterSubregions);
92 tracer.setEnterPhiNodes(mayEnterSubregions);
93 return tracer.trace(output, withinRegion);
94}
95
96std::optional<int64_t>
98{
99 const auto & normalized = llvm::traceOutput(output, true);
100
101 if (const auto [_, constant] =
103 constant)
104 {
105 const auto & rep = constant->Representation();
106 if (rep.is_known() && rep.nbits() <= 64)
107 return rep.to_int();
108 return std::nullopt;
109 }
110
111 if (const auto [_, constant] =
113 constant)
114 {
115 const auto & rep = constant->value();
116 if (rep.is_known() && rep.nbits() <= 64)
117 return rep.to_int();
118 return std::nullopt;
119 }
120
121 if (const auto [sextNode, sextOp] =
123 sextOp)
124 {
125 const auto inputValue = tryGetConstantSignedInteger(*sextNode->input(0)->origin());
126 if (!inputValue.has_value())
127 return std::nullopt;
128
129 // When doing sign extensions, we only need to care about the size of the input type
130 const auto inputBits = sextOp->nsrcbits();
131 return util::truncateAndSignExtend(*inputValue, inputBits);
132 }
133
134 if (const auto [zextNode, zextOp] =
136 zextOp)
137 {
138 const auto inputValue = tryGetConstantSignedInteger(*zextNode->input(0)->origin());
139 if (!inputValue.has_value())
140 return std::nullopt;
141
142 // When doing zero extensions, we only need to care about the size of the input type
143 const auto inputBits = zextOp->nsrcbits();
144 return util::truncateAndZeroExtend(*inputValue, inputBits);
145 }
146
147 if (const auto [truncNode, truncOp] =
149 truncOp)
150 {
151 const auto inputValue = tryGetConstantSignedInteger(*truncNode->input(0)->origin());
152 if (!inputValue.has_value())
153 return std::nullopt;
154
155 const auto outputBits = truncOp->ndstbits();
156 // When truncating, we still need to fill the high bits with something.
157 // We chose to sign extend, but this is mainly an aestetic choice
158 return util::truncateAndSignExtend(*inputValue, outputBits);
159 }
160
161 return std::nullopt;
162}
163
164std::optional<int64_t>
166{
167 if (!gepConstants.has_value())
168 return std::nullopt;
169
170 int64_t offsetInBytes = 0;
171 for (auto gepConstant : *gepConstants)
172 {
173 offsetInBytes += gepConstant.getOffsetInBytes();
174 }
175
176 return offsetInBytes;
177}
178
181{
182 const rvsdg::Output * base = &p;
183 std::vector<GetElementPtrOperation::Constant> gepConstants;
184
185 while (true)
186 {
187 // Use normalization function to get past all trivially invariant operations
188 base = &llvm::traceOutput(*base, true);
189
190 if (const auto [gepNode, gepOperation] =
192 gepOperation)
193 {
194 if (const auto gepConstantOpt = GetElementPtrOperation::tryGetAsConstant(*gepNode);
195 gepConstantOpt.has_value())
196 {
197 base = gepNode->input(0)->origin();
198 gepConstants.emplace_back(gepConstantOpt.value());
199 continue;
200 }
201 }
202
203 // We were not able to trace further
204 break;
205 }
206
207 return TracedPointerOrigin{ base, gepConstants };
208}
209
210static bool
212 const rvsdg::Output * basePointer,
213 std::optional<int64_t> offsetInBytes,
214 TraceCollection & traceCollection,
215 const size_t maxTraceCollectionSize)
216{
217 if (traceCollection.AllTracedOutputs.size() >= maxTraceCollectionSize)
218 return false;
219
220 // Normalize the pointer first, to avoid tracing trivial temporary outputs
221 basePointer = &llvm::traceOutput(*basePointer, true);
222
223 auto it = traceCollection.AllTracedOutputs.find(basePointer);
224 if (it != traceCollection.AllTracedOutputs.end())
225 {
226 // If the base pointer has already been traced with an unknown offset, we have nothing to add
227 if (!it->second.has_value())
228 return true;
229
230 // The offset used for the base pointer the last time it was traced
231 const auto prevOffset = *it->second;
232
233 // If we are visiting the same base pointer again with the same offset, we have nothing to add
234 if (offsetInBytes.has_value() && *offsetInBytes == prevOffset)
235 return true;
236
237 // We have different offsets to last time, collapse to unknown offset
238 offsetInBytes = std::nullopt;
239 }
240
241 traceCollection.AllTracedOutputs[basePointer] = offsetInBytes;
242
243 // If it is a GEP, we can trace through it, but possibly lose precise offset information
244 if (const auto [gepNode, gepOperation] =
246 gepOperation)
247 {
248 // Update the base pointer and offset to represent the other side of the GEP
249 basePointer = GetElementPtrOperation::getBaseAddressInput(*gepNode).origin();
250
251 // If we have precisely tracked the offset so far, try updating it with the GEPs offset
252 if (offsetInBytes.has_value())
253 {
254 if (const auto gepConstant = GetElementPtrOperation::tryGetAsConstant(*gepNode);
255 gepConstant.has_value())
256 offsetInBytes = *offsetInBytes + gepConstant->getOffsetInBytes();
257 else
258 offsetInBytes = std::nullopt;
259 }
260
262 basePointer,
263 offsetInBytes,
264 traceCollection,
265 maxTraceCollectionSize);
266 }
267
268 // If the node is a \ref SelectOperation, trace through both possible inputs
269 if (const auto [node, select] =
271 select)
272 {
274 node->input(1)->origin(),
275 offsetInBytes,
276 traceCollection,
277 maxTraceCollectionSize)
279 node->input(2)->origin(),
280 offsetInBytes,
281 traceCollection,
282 maxTraceCollectionSize);
283 }
284
285 // If we reach undef nodes, do not include them in the TopOrigins
287 {
288 return true;
289 }
290
291 // Trace into gamma nodes
292 if (auto gamma = rvsdg::TryGetOwnerNode<rvsdg::GammaNode>(*basePointer))
293 {
294 auto exitVar = gamma->MapOutputExitVar(*basePointer);
295 for (auto result : exitVar.branchResult)
296 {
297 // If tracing gives up, we give up
299 result->origin(),
300 offsetInBytes,
301 traceCollection,
302 maxTraceCollectionSize))
303 return false;
304 }
305
306 return true;
307 }
308
309 // Normalization never stops at a gamma entry variable
311
312 // Trace into theta nodes
313 if (auto theta = rvsdg::TryGetOwnerNode<rvsdg::ThetaNode>(*basePointer))
314 {
315 auto loopVar = theta->MapOutputLoopVar(*basePointer);
316
317 // Invariant loop variables should already have been handled by normalization
320 loopVar.post->origin(),
321 offsetInBytes,
322 traceCollection,
323 maxTraceCollectionSize);
324 }
325
326 // Trace loop variable pre arguments in theta nodes
327 if (auto theta = rvsdg::TryGetRegionParentNode<rvsdg::ThetaNode>(*basePointer))
328 {
329 auto loopVar = theta->MapPreLoopVar(*basePointer);
330
331 // Invariant loop variables should already have been handled by normalization
333
334 // Trace both from the post and the loop variable input
336 loopVar.post->origin(),
337 offsetInBytes,
338 traceCollection,
339 maxTraceCollectionSize)
341 loopVar.input->origin(),
342 offsetInBytes,
343 traceCollection,
344 maxTraceCollectionSize);
345 }
346
347 // We could not trace further, add p as a TopOrigin
348 traceCollection.TopOrigins[basePointer] = offsetInBytes;
349 return true;
350}
351
352bool
355 TraceCollection & traceCollection,
356 const size_t maxTraceCollectionSize)
357{
359 p.BasePointer,
361 traceCollection,
362 maxTraceCollectionSize);
363}
364
365}
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
static rvsdg::Input & getAddressInput(const rvsdg::Node &node) noexcept
bool traceThroughHoistBarriers_
Definition Trace.hpp:72
TraceStepResult traceStep(rvsdg::Output &output, BackEdgeState backEdgeState, const rvsdg::Region *withinRegion) override
Definition Trace.cpp:32
Output * origin() const noexcept
Definition node.hpp:58
void setRegionPredicateCheckingEnabled(bool value) noexcept
Definition Trace.hpp:165
void setEnterPhiNodes(bool value) noexcept
Definition Trace.hpp:153
virtual TraceStepResult traceStep(Output &output, BackEdgeState backEdgeState, const Region *withinRegion)
Definition Trace.cpp:330
Output & trace(Output &output)
Definition Trace.cpp:21
void setStructuralNodePolicy(StructuralNodePolicy value) noexcept
Definition Trace.hpp:106
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:353
static bool traceAllPointerOriginsInternal(const rvsdg::Output *basePointer, std::optional< int64_t > offsetInBytes, TraceCollection &traceCollection, const size_t maxTraceCollectionSize)
Definition Trace.cpp:211
rvsdg::Output & traceOutput(rvsdg::Output &output, bool mayEnterSubregions, const rvsdg::Region *withinRegion)
Definition Trace.cpp:84
TracedPointerOrigin TracePointerOriginPrecise(const rvsdg::Output &p)
Definition Trace.cpp:180
std::optional< int64_t > tryGetConstantSignedInteger(const rvsdg::Output &output)
Definition Trace.cpp:97
static bool ThetaLoopVarIsInvariant(const ThetaNode::LoopVar &loopVar) noexcept
Definition theta.hpp:266
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:165
std::unordered_map< const rvsdg::Output *, std::optional< int64_t > > AllTracedOutputs
Definition Trace.hpp:158
std::optional< std::vector< GetElementPtrOperation::Constant > > gepConstants
Definition Trace.hpp:132
const rvsdg::Output * BasePointer
Definition Trace.hpp:131
std::optional< int64_t > getOffsetInBytes() const noexcept
Definition Trace.cpp:165