Jlm
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>
8 #include <jlm/rvsdg/lambda.hpp>
9 #include <jlm/rvsdg/Phi.hpp>
10 #include <jlm/rvsdg/theta.hpp>
11 #include <jlm/rvsdg/Trace.hpp>
12 
13 namespace jlm::rvsdg
14 {
15 OutputTracer::~OutputTracer() = default;
16 
17 OutputTracer::OutputTracer(const bool enableCaching) noexcept
18  : enableCaching_(enableCaching)
19 {}
20 
21 Output &
23 {
24  return trace(output, nullptr);
25 }
26 
27 Output &
28 OutputTracer::trace(Output & output, const rvsdg::Region * withinRegion)
29 {
30  Output * head = &output;
31 
32  // Keep tracing until the output stops changing
33  while (true)
34  {
35  Output * prevHead = head;
36  head = &traceStep(*head, withinRegion);
37  if (head == prevHead)
38  {
39  return *head;
40  }
41  }
42 }
43 
50 static Output &
52 {
53  return *gammaNode.mapBranchArgumentToInput(output).origin();
54 }
55 
56 Output *
58 {
59  if (const auto traceResultOpt = lookupInCache(output); traceResultOpt.has_value())
60  {
61  return traceResultOpt.value();
62  }
63 
64  const auto exitVar = gammaNode.MapOutputExitVar(output);
65 
66  // The shared output that is the origin of the entry variable(s) going into the gamma node
67  Output * commonOrigin = nullptr;
68  Input * gammaInput = nullptr;
69 
70  for (auto branchResult : exitVar.branchResult)
71  {
72  auto tracedInner = branchResult->origin();
73 
74  // If deep tracing is enabled, make a greater effort to trace up to a region argument
76  {
77  // Trace the branch result origin, but only within the gamma subregion
78  tracedInner = &trace(*tracedInner, tracedInner->region());
79  }
80 
81  // The traced output must reach a region argument in the gamma subregion
82  if (TryGetRegionParentNode<GammaNode>(*tracedInner) != &gammaNode)
83  return insertInCache(output, nullptr);
84 
85  // Get the origin of the region argument outside the gamma
86  gammaInput = &gammaNode.mapBranchArgumentToInput(*tracedInner);
87  Output & outerOrigin = *gammaInput->origin();
88 
89  // Check that the origin matches with all other origins
90  if (commonOrigin == nullptr)
91  {
92  commonOrigin = &outerOrigin;
93  }
94  else if (commonOrigin != &outerOrigin)
95  {
96  return insertInCache(output, nullptr);
97  }
98  }
99 
100  JLM_ASSERT(commonOrigin != nullptr);
101  JLM_ASSERT(gammaInput != nullptr);
102  return insertInCache(output, gammaInput);
103 }
104 
105 Output *
107 {
108  if (const auto traceResultOpt = lookupInCache(output); traceResultOpt.has_value())
109  {
110  return traceResultOpt.value();
111  }
112 
113  const auto loopVar = thetaNode.MapOutputLoopVar(output);
114 
115  auto tracedInner = loopVar.post->origin();
116 
117  // If deep tracing is enabled, make a greater effort in tracing up to a region argument
119  {
120  // trace the origin within the thetaNode, but only within the theta's subregion
121  tracedInner = &trace(*tracedInner, thetaNode.subregion());
122  }
123 
124  // If tracing reached the pre argument of the same loop variable, it is invariant
125  if (tracedInner == loopVar.pre)
126  {
127  return insertInCache(output, loopVar.input);
128  }
129 
130  // If tracing from the post result lead to the pre argument of a different loop variable,
131  // check if that loop variable is trivially invariant, and if it is, return its input origin.
132  if (TryGetRegionParentNode<ThetaNode>(*tracedInner) == &thetaNode)
133  {
134  auto originLoopVar = thetaNode.MapPreLoopVar(*tracedInner);
135  if (ThetaLoopVarIsInvariant(originLoopVar))
136  {
137  return insertInCache(output, originLoopVar.input);
138  }
139  }
140 
141  return insertInCache(output, nullptr);
142 }
143 
144 Output &
145 OutputTracer::traceStep(Output & output, const rvsdg::Region * withinRegion)
146 {
147  if (withinRegion && withinRegion == TryGetOwnerRegion(output))
148  {
149  // We are not allowed to leave this region, and tracing has reached one of its arguments
150  return output;
151  }
152 
153  // Handle gamma node outputs
154  if (const auto gammaNode = TryGetOwnerNode<GammaNode>(output))
155  {
156  if (const auto traced = tryTraceThroughGamma(*gammaNode, output))
157  return *traced;
158 
159  return output;
160  }
161 
162  // Handle gamma node arguments
163  if (const auto gammaNode = TryGetRegionParentNode<GammaNode>(output))
164  {
165  return mapGammaArgumentToOrigin(*gammaNode, output);
166  }
167 
168  // Handle theta node outputs
169  if (const auto thetaNode = TryGetOwnerNode<ThetaNode>(output))
170  {
171  if (const auto traced = tryTraceThroughTheta(*thetaNode, output))
172  {
173  return *traced;
174  }
175 
176  return output;
177  }
178 
179  // Handle theta node arguments
180  if (const auto thetaNode = TryGetRegionParentNode<ThetaNode>(output))
181  {
182  // Tracing from inside a theta to outside it is only valid if the loop variable is invariant.
183  // This is determined by tracing from the loop variable's post result,
184  // and seeing if it leads to the same input origin as the loop variable's own input.
185 
186  // The loop variable whose pre argument is being traced from
187  const auto loopVar = thetaNode->MapPreLoopVar(output);
188 
189  // The origin of the loop variable's input.
190  const auto inputOrigin = loopVar.input->origin();
191 
192  // The origin reached when tracing from the loop variable's post result,
193  // if it reaches an invariant loop variable and "escapes" the theta.
194  // The invariant loop variable found does not have to be the same as the above loopVar.
195  // See TraceTests' TestIndirectLoopInvariance.
196  const auto postOrigin = tryTraceThroughTheta(*thetaNode, *loopVar.output);
197 
198  if (postOrigin == inputOrigin)
199  {
200  return *inputOrigin;
201  }
202 
203  return output;
204  }
205 
206  // If we are not doing interprocedural tracing, stop tracing now
207  if (!isInterprocedural_)
208  return output;
209 
210  // Handle lambda context variables
211  if (const auto lambda = TryGetRegionParentNode<LambdaNode>(output))
212  {
213  // If the argument is a contex variable, continue tracing
214  if (const auto ctxVar = lambda->MapBinderContextVar(output))
215  return *ctxVar->input->origin();
216 
217  return output;
218  }
219 
220  // Handle delta context variables
221  if (const auto delta = TryGetRegionParentNode<DeltaNode>(output))
222  {
223  // If the argument is a contex variable, continue tracing
224  const auto ctxVar = delta->MapBinderContextVar(output);
225  return *ctxVar.input->origin();
226  }
227 
228  // Handle phi outputs
229  if (const auto phiNode = TryGetOwnerNode<PhiNode>(output))
230  {
231  if (enterPhiNodes_)
232  {
233  const auto fixVar = phiNode->MapOutputFixVar(output);
234  return *fixVar.result->origin();
235  }
236  return output;
237  }
238 
239  // Handle phi region arguments
240  if (const auto phiNode = TryGetRegionParentNode<PhiNode>(output))
241  {
242  // Wo only trace through contex variables.
243  // Going through recursion variables would hide the fact that recursion is happening,
244  // and risks producing an output that is a successor of the output we started with in the DAG.
245  const auto argument = phiNode->MapArgument(output);
246  if (const auto ctxVar = std::get_if<PhiNode::ContextVar>(&argument))
247  {
248  // Follow the context variable to outside the phi
249  return *ctxVar->input->origin();
250  }
251  return output;
252  }
253 
254  return output;
255 }
256 
257 Output *
258 OutputTracer::insertInCache(const Output & output, Input * traceResult)
259 {
260  if (!enableCaching_)
261  return traceResult != nullptr ? traceResult->origin() : nullptr;
262 
263  JLM_ASSERT(traceCache_.find(&output) == traceCache_.end());
264  traceCache_[&output] = traceResult;
265  return traceResult != nullptr ? traceResult->origin() : nullptr;
266 }
267 
268 std::optional<Output *>
270 {
271  if (!enableCaching_)
272  return std::nullopt;
273 
274  if (const auto it = traceCache_.find(&output); it != traceCache_.end())
275  {
276  return it->second != nullptr ? it->second->origin() : nullptr;
277  }
278 
279  return std::nullopt;
280 }
281 
282 Output &
284 {
285  constexpr bool enableCaching = false;
286  OutputTracer tracer(enableCaching);
287  tracer.setInterprocedural(false);
288  return tracer.trace(output);
289 }
290 
291 Output &
292 traceOutput(Output & output, const rvsdg::Region * withinRegion)
293 {
294  constexpr bool enableCaching = false;
295  OutputTracer tracer(enableCaching);
296  return tracer.trace(output, withinRegion);
297 }
298 
299 }
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
Output * tryTraceThroughGamma(GammaNode &gammaNode, Output &output)
Definition: Trace.cpp:57
void setInterprocedural(bool value) noexcept
Definition: Trace.hpp:106
Output * insertInCache(const Output &output, Input *traceResult)
Definition: Trace.cpp:258
Output & trace(Output &output)
Definition: Trace.cpp:22
virtual Output & traceStep(Output &output, const rvsdg::Region *withinRegion)
Definition: Trace.cpp:145
bool traceThroughStrucutalNodes_
Definition: Trace.hpp:199
std::unordered_map< const Output *, Input * > traceCache_
Definition: Trace.hpp:211
OutputTracer(bool enableCaching) noexcept
Definition: Trace.cpp:17
Output * tryTraceThroughTheta(ThetaNode &thetaNode, Output &output)
Definition: Trace.cpp:106
std::optional< Output * > lookupInCache(const Output &output)
Definition: Trace.cpp:269
Represent acyclic RVSDG subgraphs.
Definition: region.hpp:213
LoopVar MapOutputLoopVar(const rvsdg::Output &output) const
Maps variable at exit to full varibale description.
Definition: theta.cpp:166
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:79
#define JLM_ASSERT(x)
Definition: common.hpp:16
static Output & mapGammaArgumentToOrigin(GammaNode &gammaNode, Output &output)
Definition: Trace.cpp:51
static bool ThetaLoopVarIsInvariant(const ThetaNode::LoopVar &loopVar) noexcept
Definition: theta.hpp:227
Output & traceOutput(Output &output, const rvsdg::Region *withinRegion)
Definition: Trace.cpp:292
Output & traceOutputIntraProcedurally(Output &output)
Definition: Trace.cpp:283
Region * TryGetOwnerRegion(const rvsdg::Input &input) noexcept
Definition: node.hpp:1021
rvsdg::Input * post
Variable after iteration (output result from subregion).
Definition: theta.hpp:62