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
6#include <jlm/rvsdg/delta.hpp>
7#include <jlm/rvsdg/gamma.hpp>
9#include <jlm/rvsdg/Phi.hpp>
10#include <jlm/rvsdg/theta.hpp>
11#include <jlm/rvsdg/Trace.hpp>
12
13namespace jlm::rvsdg
14{
16
19
20Output &
22{
23 return trace(output, nullptr);
24}
25
26Output &
28{
29 // FIXME(perf): Querying the region predication checker class causes repeated re-traversal of
30 // the same paths through the region hierarchy that the tracer is already taking.
31 // Performance could be improved by adding a way of extracting impossible regions directy.
33
34 // Mark this as the starting output, which becomes the target for region predicate reachability
35 startingOutput_ = &output;
36 // Since the current output is the starting output, no back-edges have been followed
38
39 // To disable region predicate checking, always assume back-edges have been taken
42
43 auto result = traceInternal(output, backEdgeState, withinRegion);
44
45 // If the output is unreachable, tracing may reach a dead end.
46 // In which case return the original output
47 if (result.isDeadEnd())
48 return output;
49
50 JLM_ASSERT(result.isFinalOutput());
51 return result.getOutput();
52}
53
56 Output & output,
58 const Region * withinRegion)
59{
60 Output * head = &output;
61
62 // Keep tracing until a final result is reached
63 while (true)
64 {
66
67 // If the tracing reached a final output, or it was determined that tracing from the
68 // given output always reaches dead ends, stop now
69 if (traceStepResult.isFinalOutput() || traceStepResult.isDeadEnd())
70 return traceStepResult;
71
72 // Otherwise the result is a tracing step, and it must have made progress
73 JLM_ASSERT(traceStepResult.isStepOutput());
74 JLM_ASSERT(&traceStepResult.getOutput() != head);
75
76 // Keep tracing from the step result
77 head = &traceStepResult.getOutput();
78 }
79}
80
87static Output &
89{
90 return *gammaNode.mapBranchArgumentToInput(output).origin();
91}
92
93OutputTracer::TraceStepResult
95{
96 // First check the invariance cache
97 if (const auto invariantValueInput = lookupInInvarianceCache(output))
98 {
100 }
101
102 const auto exitVar = gammaNode.MapOutputExitVar(output);
103
104 // The shared output that is the origin of the entry variable(s) going into the gamma node
105 // nullopt means no subregion has been traced yet.
106 // nullptr means there can be no shared common outer origin.
107 std::optional<Output *> commonOuterOrigin;
108 // The gamma input that gets its value from the common outer origin
109 Input * commonGammaInput = nullptr;
110
111 // If only a single subregion can provide the value, this is the origin within that subregion.
112 // nullopt means no valid subregion been found yet.
113 // nullptr means there are multiple valid subregions.
114 std::optional<Output *> singleInnerOrigin;
116 {
117 singleInnerOrigin = nullptr;
118 }
119
120 for (auto branchResult : exitVar.branchResult)
121 {
122 // Region predication checking requires that no back-edge has been taken around the gamma
124 {
125 // If control flow cannot go from the gamma subregion to the region of the starting output,
126 // it cannot be the origin of the traced value.
129 *branchResult->region()))
130 continue;
131 }
132
133 auto innerOrigin = branchResult->origin();
134
136 {
137 // Trace the branch result origin, but only within the gamma subregion
139
140 // Tracing inside the subregion only leads to regions that cannot reach the starting output
141 if (traceInnerResult.isDeadEnd())
142 continue;
143
144 innerOrigin = &traceInnerResult.getOutput();
145 }
146
147 // Set the single inner origin, or clear it if we already had one
148 if (!singleInnerOrigin.has_value())
149 {
151 }
152 else
153 {
154 singleInnerOrigin = nullptr;
155 }
156
157 // Check if the subregion result was traced all the way to an argument
159 {
160 // Get the origin of the region argument outside the gamma
162 Output & outerOrigin = *gammaInput.origin();
163
164 // If this is the first outer origin, make it the common outer origin for now
165 if (!commonOuterOrigin.has_value())
166 {
169 }
170 else if (*commonOuterOrigin != &outerOrigin)
171 {
172 // Mismatching outer origins found, given up on finding a common outer origin
173 commonOuterOrigin = nullptr;
174 }
175 }
176 else
177 {
178 // The subregion result could not be traced to an outer origin
179 commonOuterOrigin = nullptr;
180 }
181
182 // Stop looping through subregions if there is neither an inner origin or a common outer origin
183 if (commonOuterOrigin == nullptr && singleInnerOrigin == nullptr)
185 }
186
187 // All subregions either agree with commonOuterOrigin, or set it to nullptr
188 // If it is still nullopt, that means none of the subregions were reachable.
189 if (!commonOuterOrigin.has_value())
190 {
192 }
193
194 // If we found a common outer origin, continue tracing from there
195 if (*commonOuterOrigin != nullptr)
196 {
197 JLM_ASSERT(commonGammaInput != nullptr);
198
199 // If the gamma was invariant, even with no assumptions about back-edges not being taken
200 // around the gamma, the invariance can be added to the cache
202 {
204 }
205
207 }
208
209 // If only a single gamma subregion provides a possible origin, use it
210 if (singleInnerOrigin.has_value() && *singleInnerOrigin != nullptr)
211 {
213
214 // The origins found inside subregions have already been fully traced, so they are final
216 }
217
218 // Tracing was unable to make any progress beyond the gamma output
220}
221
224{
225 // Lookup the output in the invariance cache
226 if (const auto invariantValueInput = lookupInInvarianceCache(output))
227 {
229 }
230
231 const auto loopVar = thetaNode.MapOutputLoopVar(output);
232
233 auto innerOrigin = loopVar.post->origin();
234
235 // If invariance detection is enabled, perform tracing inside the subregion
237 {
238 // trace the origin within the thetaNode, but only within the theta's subregion
240
241 // If tracing in the theta only reaches regions that cannot reach the starting output,
242 // the theta itself cannot be the origin providing values to the starting output.
243 if (tracedInnerResult.isDeadEnd())
245
246 innerOrigin = &tracedInnerResult.getOutput();
247 }
248
249 // If tracing reached the pre argument of the same loop variable, it might be invariant
250 if (innerOrigin == loopVar.pre)
251 {
252 // If the loop variable was found to be invariant,
253 // but we also made an assumption about not taking any back-edges around the theta subregion,
254 // we must check again without making that assumption to be sure it is acutually invariant.
255
257 {
258 // The tracing already made no assumptions about back-edges.
259 // The loop variable is definitely invariant
261 }
262
263 // Try tracing from the loop var post again, this time with no assumptions about back-edges
265 *loopVar.post->origin(),
267 thetaNode.subregion());
268
269 // The loop variable has already been traced once without being impossible.
270 // Tracing again with weaker assumptions can never fail.
271 JLM_ASSERT(!tracedInnerAgain.isDeadEnd());
272
273 if (&tracedInnerAgain.getOutput() == loopVar.pre)
274 {
275 // The loop variable is in fact invariant, connect the output to the loop variable input
277 }
278
279 // If we get here, it means that the loop variable was only found to be invariant in the final
280 // iteration of the loop, but not in every iteration
282 }
283 else if (TryGetRegionParentNode<ThetaNode>(*innerOrigin) == &thetaNode)
284 {
285 // Tracing from the post result lead to the pre argument of a different loop variable.
286 // Check if that loop variable is trivially invariant, and if it is, return its input origin.
287
288 auto originLoopVar = thetaNode.MapPreLoopVar(*innerOrigin);
290 {
292 insertInInvarianceCache(output, *originLoopVar.input));
293 }
294 }
295
296 // If we are allowed to return outputs from inside the subregion,
297 // return the result from tracing inside the subregion
299 {
300 // The origin found inside the theta is already fully traced, so it is final
302 }
303
304 // Otherwise, we are unable to trace further from the theta output
306}
307
310{
311 // Get the loop variable
312 auto loopVar = thetaNode.MapPreLoopVar(output);
313
314 // Trace from the corresponding theta output by following the back-edge
315 auto outputTraceResult =
317
318 // If the loop output is invariant and has the same origin as the loop variable,
319 // tracing can continue outside the theta, so we get a step output result
320 if (outputTraceResult.isStepOutput() && &outputTraceResult.getOutput() == loopVar.input->origin())
321 {
322 return outputTraceResult;
323 }
324
325 // Otherwise tracing stops at the theta argument
327}
328
331{
333 {
334 // We are not allowed to leave this region, and tracing has reached one of its arguments
336 }
337
338 // Handle gamma node outputs
339 if (const auto gammaNode = TryGetOwnerNode<GammaNode>(output))
340 {
341 return traceGammaOutput(*gammaNode, output, backEdgeState);
342 }
343
344 // Handle gamma node arguments
345 if (const auto gammaNode = TryGetRegionParentNode<GammaNode>(output))
346 {
348 }
349
350 // Handle theta node outputs
351 if (const auto thetaNode = TryGetOwnerNode<ThetaNode>(output))
352 {
353 return traceThetaOutput(*thetaNode, output, backEdgeState);
354 }
355
356 // Handle theta node arguments
357 if (const auto thetaNode = TryGetRegionParentNode<ThetaNode>(output))
358 {
359 // The backEdgeState is not provided to this function,
360 // since it must anyways immediately follow a back-edge to determine loop invariance
361 return traceThetaArgument(*thetaNode, output);
362 }
363
364 // If we are not doing interprocedural tracing, stop tracing now
367
368 // Handle lambda context variables
369 if (const auto lambda = TryGetRegionParentNode<LambdaNode>(output))
370 {
371 // If the argument is a contex variable, continue tracing
372 if (const auto ctxVar = lambda->MapBinderContextVar(output))
373 return TraceStepResult::createStepOutput(*ctxVar->input->origin());
374
376 }
377
378 // Handle delta context variables
379 if (const auto delta = TryGetRegionParentNode<DeltaNode>(output))
380 {
381 // If the argument is a contex variable, continue tracing
382 const auto ctxVar = delta->MapBinderContextVar(output);
383 return TraceStepResult::createStepOutput(*ctxVar.input->origin());
384 }
385
386 // Handle phi outputs
387 if (const auto phiNode = TryGetOwnerNode<PhiNode>(output))
388 {
389 if (enterPhiNodes_)
390 {
391 const auto fixVar = phiNode->MapOutputFixVar(output);
392 return TraceStepResult::createStepOutput(*fixVar.result->origin());
393 }
395 }
396
397 // Handle phi region arguments
398 if (const auto phiNode = TryGetRegionParentNode<PhiNode>(output))
399 {
400 // Wo only trace through contex variables.
401 // Going through recursion variables would hide the fact that recursion is happening,
402 // and risks producing an output that is a successor of the output we started with in the DAG.
403 const auto argument = phiNode->MapArgument(output);
404 if (const auto ctxVar = std::get_if<PhiNode::ContextVar>(&argument))
405 {
406 // Follow the context variable to outside the phi
407 return TraceStepResult::createStepOutput(*ctxVar->input->origin());
408 }
410 }
411
413}
414
415Output &
417{
419 {
420 const auto [_, inserted] = invariantOutputCache_.emplace(&output, &traceResult);
422 }
423
424 return *traceResult.origin();
425}
426
427Input *
429{
431 {
432 if (const auto it = invariantOutputCache_.find(&output); it != invariantOutputCache_.end())
433 {
434 return it->second;
435 }
436 }
437
438 return nullptr;
439}
440
441Output &
452
453Output &
464
465}
bool isReachableFromRegion(Region &targetRegion, Region &originRegion)
Conditional operator / pattern matching.
Definition gamma.hpp:99
ExitVar MapOutputExitVar(const rvsdg::Output &output) const
Maps gamma output to exit variable description.
Definition gamma.cpp:397
const rvsdg::Input & mapBranchArgumentToInput(const rvsdg::Output &output) const
Maps branch subregion entry argument to its corresponding gamma input.
Definition gamma.cpp:344
Output * origin() const noexcept
Definition node.hpp:58
static TraceStepResult createFinalOutput(Output &output)
Definition Trace.hpp:321
static TraceStepResult createDeadEndResult()
Definition Trace.hpp:331
static TraceStepResult createStepOutput(Output &output)
Definition Trace.hpp:311
TraceStepResult traceInternal(Output &output, BackEdgeState backEdgeState, const Region *withinRegion)
Definition Trace.cpp:55
void setRegionPredicateCheckingEnabled(bool value) noexcept
Definition Trace.hpp:165
Output & insertInInvarianceCache(const Output &structuralOutput, Input &structuralInput)
Definition Trace.cpp:416
void setEnterPhiNodes(bool value) noexcept
Definition Trace.hpp:153
AlternativeRegionPredicateTracer regionPredicateTracer_
Definition Trace.hpp:478
TraceStepResult traceThetaOutput(ThetaNode &thetaNode, Output &output, BackEdgeState backEdgeState)
Definition Trace.cpp:223
virtual TraceStepResult traceStep(Output &output, BackEdgeState backEdgeState, const Region *withinRegion)
Definition Trace.cpp:330
bool isDeepInvarianceCheckingEnabled() const noexcept
Definition Trace.hpp:83
void setInterprocedural(bool value) noexcept
Definition Trace.hpp:129
TraceStepResult traceGammaOutput(GammaNode &gammaNode, Output &output, BackEdgeState backEdgeState)
Definition Trace.cpp:94
const Output * startingOutput_
Definition Trace.hpp:482
Output & trace(Output &output)
Definition Trace.cpp:21
bool isRegionPredicateCheckingEnabled() const noexcept
Definition Trace.hpp:159
void setStructuralNodePolicy(StructuralNodePolicy value) noexcept
Definition Trace.hpp:106
Input * lookupInInvarianceCache(const Output &structuralOutput)
Definition Trace.cpp:428
OutputTracer() noexcept
Definition Trace.cpp:17
std::unordered_map< const Output *, Input * > invariantOutputCache_
Definition Trace.hpp:490
TraceStepResult traceThetaArgument(ThetaNode &thetaNode, Output &output)
Definition Trace.cpp:309
bool isTracingIntoSubregionsEnabled() const noexcept
Definition Trace.hpp:96
rvsdg::Region * region() const noexcept
Definition node.cpp:151
Represent acyclic RVSDG subgraphs.
Definition region.hpp:213
StructuralOutput * output(size_t index) const noexcept
LoopVar MapOutputLoopVar(const rvsdg::Output &output) const
Maps variable at exit to full varibale description.
Definition theta.cpp:183
LoopVar MapPreLoopVar(const rvsdg::Output &argument) const
Maps variable at start of loop iteration to full varibale description.
Definition theta.cpp:140
rvsdg::Region * subregion() const noexcept
Definition theta.hpp:90
#define JLM_ASSERT(x)
Definition common.hpp:16
Output & traceOutput(Output &output, bool mayEnterSubregions, const Region *withinRegion)
Definition Trace.cpp:454
static bool ThetaLoopVarIsInvariant(const ThetaNode::LoopVar &loopVar) noexcept
Definition theta.hpp:266
Output & traceOutputIntraProcedurally(Output &output, bool mayEnterSubregions)
Definition Trace.cpp:442
Region * TryGetOwnerRegion(const rvsdg::Input &input) noexcept
Definition node.hpp:1021
NodeType * TryGetOwnerNode(const rvsdg::Input &input) noexcept
Checks if this is an input to a node of specified type.
Definition node.hpp:872
static Output & mapGammaArgumentToOrigin(GammaNode &gammaNode, Output &output)
Definition Trace.cpp:88
rvsdg::Input * post
Variable after iteration (output result from subregion).
Definition theta.hpp:62