Jlm
TraceTests.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 <gtest/gtest.h>
7 
9 #include <jlm/rvsdg/gamma.hpp>
12 #include <jlm/rvsdg/TestType.hpp>
13 #include <jlm/rvsdg/theta.hpp>
14 #include <jlm/rvsdg/Trace.hpp>
15 #include <jlm/rvsdg/view.hpp>
16 
21 TEST(TraceTests, TestTraceOutputIntraProcedural_Gamma)
22 {
23  using namespace jlm::rvsdg;
24 
25  // Assert
26  const auto controlType = ControlType::Create(2);
27  const auto valueType = TestType::createValueType();
28 
29  Graph rvsdg;
30  auto & i0 = GraphImport::Create(rvsdg, controlType, "i0");
31  auto & i1 = GraphImport::Create(rvsdg, valueType, "i1");
32  auto & i2 = GraphImport::Create(rvsdg, valueType, "i2");
33 
34  const auto gammaNode = GammaNode::create(&i0, 2);
35  auto entryVar1 = gammaNode->AddEntryVar(&i1);
36  auto entryVar1Copy = gammaNode->AddEntryVar(&i1);
37  auto entryVar2 = gammaNode->AddEntryVar(&i2);
38 
39  auto node = TestOperation::createNode(
40  gammaNode->subregion(1),
41  { entryVar2.branchArgument[1] },
42  { valueType });
43 
44  auto exitVar0 =
45  gammaNode->AddExitVar({ entryVar1.branchArgument[0], entryVar1Copy.branchArgument[1] });
46  auto exitVar1 =
47  gammaNode->AddExitVar({ entryVar1.branchArgument[0], entryVar2.branchArgument[1] });
48  auto exitVar2 = gammaNode->AddExitVar({ entryVar2.branchArgument[0], node->output(0) });
49 
50  auto & x0 = GraphExport::Create(*exitVar0.output, "x0");
51  auto & x1 = GraphExport::Create(*exitVar1.output, "x1");
52  auto & x2 = GraphExport::Create(*exitVar2.output, "x2");
53 
54  view(&rvsdg.GetRootRegion(), stdout);
55 
56  // Act
57  const auto & tracedX0 = traceOutputIntraProcedurally(*x0.origin());
58  const auto & tracedX1 = traceOutputIntraProcedurally(*x1.origin());
59  const auto & tracedX2 = traceOutputIntraProcedurally(*x2.origin());
60  // Trace from within one of the context variables of the gamma
61  const auto & traceGammaEntry = traceOutputIntraProcedurally(*entryVar1.branchArgument[0]);
62  const auto & tracedNodeInput = traceOutputIntraProcedurally(*node->input(0)->origin());
63 
64  // Assert
65  EXPECT_EQ(&tracedX0, &i1);
66  EXPECT_EQ(&tracedX1, x1.origin());
67  EXPECT_EQ(&tracedX2, x2.origin());
68  EXPECT_EQ(&traceGammaEntry, &i1);
69  EXPECT_EQ(&tracedNodeInput, &i2);
70 }
71 
75 TEST(TraceTests, TestTraceOutputIntraProcedural_Theta)
76 {
77  using namespace jlm::rvsdg;
78 
79  // Assert
80  const auto valueType = TestType::createValueType();
81 
82  Graph rvsdg;
83  auto & i0 = GraphImport::Create(rvsdg, valueType, "i0");
84  auto & i1 = GraphImport::Create(rvsdg, valueType, "i1");
85 
86  const auto thetaNode = ThetaNode::create(&rvsdg.GetRootRegion());
87  // loopVar0 is trivially loop invariant
88  auto loopVar0 = thetaNode->AddLoopVar(&i0);
89  auto loopVar1 = thetaNode->AddLoopVar(&i1);
90 
91  auto node = TestOperation::createNode(thetaNode->subregion(), { loopVar1.pre }, { valueType });
92  loopVar1.post->divert_to(node->output(0));
93 
94  auto & x0 = GraphExport::Create(*loopVar0.output, "x0");
95  auto & x1 = GraphExport::Create(*loopVar1.output, "x1");
96 
97  view(&rvsdg.GetRootRegion(), stdout);
98 
99  // Act
100  const auto & tracedX0 = traceOutputIntraProcedurally(*x0.origin());
101  const auto & tracedX1 = traceOutputIntraProcedurally(*x1.origin());
102  const auto & traceLoopVar0Pre = traceOutputIntraProcedurally(*loopVar0.pre);
103  const auto & traceLoopVar1Pre = traceOutputIntraProcedurally(*loopVar1.pre);
104  const auto & tracedNodeInput = traceOutputIntraProcedurally(*node->input(0)->origin());
105 
106  // Assert
107  EXPECT_EQ(&tracedX0, &i0);
108  EXPECT_EQ(&tracedX1, x1.origin());
109  EXPECT_EQ(&traceLoopVar0Pre, &i0);
110  EXPECT_EQ(&traceLoopVar1Pre, loopVar1.pre);
111  EXPECT_EQ(&tracedNodeInput, loopVar1.pre);
112 }
113 
117 TEST(TraceTests, TestTraceNestedStructuralNodes)
118 {
119  using namespace jlm::rvsdg;
120 
121  // Assert
122  const auto controlType = ControlType::Create(2);
123  const auto valueType = TestType::createValueType();
124 
125  Graph rvsdg;
126  auto & i0 = GraphImport::Create(rvsdg, valueType, "i0");
127  auto & i1 = GraphImport::Create(rvsdg, valueType, "i1");
128  auto & i2 = GraphImport::Create(rvsdg, valueType, "i2");
129 
130  const auto thetaNode = ThetaNode::create(&rvsdg.GetRootRegion());
131  // loop variables 0 and 1 go through the inner gamma. loopVar2 is trivially loop invariant
132  auto loopVar0 = thetaNode->AddLoopVar(&i0);
133  auto loopVar1 = thetaNode->AddLoopVar(&i1);
134  auto loopVar2 = thetaNode->AddLoopVar(&i2);
135 
136  // Create the gamma that sends loopVar0 and loopVar1 directly through
137  auto & undefNode =
138  jlm::rvsdg::CreateOpNode<TestNullaryOperation>(*thetaNode->subregion(), controlType);
139  const auto gammaNode = GammaNode::create(undefNode.output(0), 2);
140  auto entryVar0 = gammaNode->AddEntryVar(loopVar0.pre);
141  auto entryVar1 = gammaNode->AddEntryVar(loopVar1.pre);
142  auto exitVar0 =
143  gammaNode->AddExitVar({ entryVar0.branchArgument[0], entryVar0.branchArgument[1] });
144  auto exitVar1 =
145  gammaNode->AddExitVar({ entryVar1.branchArgument[0], entryVar1.branchArgument[1] });
146 
147  // Both loopVar0 and loopVar1 get loopVar0.pre as their indirect origin
148  // This means only loopVar0 is loop invariant
149  loopVar0.post->divert_to(exitVar0.output);
150  loopVar1.post->divert_to(exitVar0.output);
151 
152  auto & x0 = GraphExport::Create(*loopVar0.output, "x0");
153  auto & x1 = GraphExport::Create(*loopVar1.output, "x1");
154  auto & x2 = GraphExport::Create(*loopVar2.output, "x2");
155 
156  view(&rvsdg.GetRootRegion(), stdout);
157 
158  // Act & Assert 1
159  {
160  const auto & tracedX0 = traceOutputIntraProcedurally(*x0.origin());
161  const auto & tracedX1 = traceOutputIntraProcedurally(*x1.origin());
162  const auto & tracedX2 = traceOutputIntraProcedurally(*x2.origin());
163  const auto & traceExitVar0 = traceOutputIntraProcedurally(*exitVar0.output);
164  const auto & traceExitVar1 = traceOutputIntraProcedurally(*exitVar1.output);
165  const auto & traceBranchArgument0 = traceOutputIntraProcedurally(*entryVar0.branchArgument[0]);
166  const auto & traceBranchArgument1 = traceOutputIntraProcedurally(*entryVar1.branchArgument[1]);
167  const auto & traceLoopVar0Pre = traceOutputIntraProcedurally(*loopVar0.pre);
168  const auto & traceLoopVar1Pre = traceOutputIntraProcedurally(*loopVar1.pre);
169  const auto & traceLoopVar2Pre = traceOutputIntraProcedurally(*loopVar2.pre);
170 
171  EXPECT_EQ(&tracedX0, &i0);
172  EXPECT_EQ(&tracedX1, loopVar1.output);
173  EXPECT_EQ(&tracedX2, &i2);
174  EXPECT_EQ(&traceExitVar0, &i0);
175  EXPECT_EQ(&traceExitVar1, loopVar1.pre);
176  EXPECT_EQ(&traceBranchArgument0, &i0);
177  EXPECT_EQ(&traceBranchArgument1, loopVar1.pre);
178  EXPECT_EQ(&traceLoopVar0Pre, &i0);
179  EXPECT_EQ(&traceLoopVar1Pre, loopVar1.pre);
180  EXPECT_EQ(&traceLoopVar2Pre, &i2);
181  }
182 
183  // Act & Assert 2
184  {
185  // Create an alternative tracer that does not perform deep tracing
186  constexpr bool enableCaching = false;
187  OutputTracer shallowTracer(enableCaching);
188  shallowTracer.setTraceThroughStructuralNodes(false);
189 
190  const auto & tracedX0 = shallowTracer.trace(*x0.origin());
191  const auto & tracedX1 = shallowTracer.trace(*x1.origin());
192  const auto & tracedX2 = shallowTracer.trace(*x2.origin());
193  const auto & traceExitVar0 = shallowTracer.trace(*exitVar0.output);
194  const auto & traceExitVar1 = shallowTracer.trace(*exitVar1.output);
195  const auto & traceBranchArgument0 = shallowTracer.trace(*entryVar0.branchArgument[0]);
196  const auto & traceBranchArgument1 = shallowTracer.trace(*entryVar1.branchArgument[1]);
197  const auto & traceLoopVar0Pre = shallowTracer.trace(*loopVar0.pre);
198  const auto & traceLoopVar1Pre = shallowTracer.trace(*loopVar1.pre);
199  const auto & traceLoopVar2Pre = shallowTracer.trace(*loopVar2.pre);
200 
201  EXPECT_EQ(&tracedX0, loopVar0.output);
202  EXPECT_EQ(&tracedX1, loopVar1.output);
203  EXPECT_EQ(&tracedX2, &i2); // loopVar2 can still be traced through as it is trivially invariant
204  EXPECT_EQ(&traceExitVar0, loopVar0.pre);
205  EXPECT_EQ(&traceExitVar1, loopVar1.pre);
206  EXPECT_EQ(&traceBranchArgument0, loopVar0.pre);
207  EXPECT_EQ(&traceBranchArgument1, loopVar1.pre);
208  EXPECT_EQ(&traceLoopVar0Pre, loopVar0.pre);
209  EXPECT_EQ(&traceLoopVar1Pre, loopVar1.pre);
210  EXPECT_EQ(&traceLoopVar2Pre, &i2);
211  }
212 }
213 
219 TEST(TraceTests, TestIndirectLoopInvariantOutput)
220 {
221  using namespace jlm::rvsdg;
222 
243  Graph rvsdg;
244  auto & c20 = BitConstantOperation::create(rvsdg.GetRootRegion(), { 32, 20 });
245  auto & c40 = BitConstantOperation::create(rvsdg.GetRootRegion(), { 32, 40 });
246 
247  const auto thetaNode = ThetaNode::create(&rvsdg.GetRootRegion());
248  auto invariantLoopVar = thetaNode->AddLoopVar(&c20);
249  auto indirectLoopVar = thetaNode->AddLoopVar(&c40);
250 
251  // USER1 uses the pre-iteration value of the non-invariant loop variable.
252  auto user1 = TestOperation::createNode(thetaNode->subregion(), { indirectLoopVar.pre }, {});
253 
254  // Make the post value of indirectLoopVar come from the trivially invariant loop var.
255  indirectLoopVar.post->divert_to(invariantLoopVar.pre);
256 
257  // USER2 uses the loop ouput value of the non-trivially invariant loop variable.
258  auto user2 = TestOperation::createNode(&rvsdg.GetRootRegion(), { indirectLoopVar.output }, {});
259 
260  view(&rvsdg.GetRootRegion(), stdout);
261 
262  // Act
263  const auto & tracedUser1 = traceOutputIntraProcedurally(*user1->input(0)->origin());
264  const auto & tracedUser2 = traceOutputIntraProcedurally(*user2->input(0)->origin());
265 
266  // Assert
267  EXPECT_TRUE(ThetaLoopVarIsInvariant(invariantLoopVar));
268  EXPECT_FALSE(ThetaLoopVarIsInvariant(indirectLoopVar));
269  EXPECT_EQ(&tracedUser1, indirectLoopVar.pre);
270  EXPECT_EQ(&tracedUser2, &c20);
271 }
272 
277 TEST(TraceTests, TestIndirectLoopInvariance)
278 {
279  using namespace jlm::rvsdg;
280 
301  Graph rvsdg;
302  auto & c20 = BitConstantOperation::create(rvsdg.GetRootRegion(), { 32, 20 });
303 
304  const auto thetaNode = ThetaNode::create(&rvsdg.GetRootRegion());
305  auto invariantLoopVar = thetaNode->AddLoopVar(&c20);
306  auto indirectLoopVar = thetaNode->AddLoopVar(&c20);
307 
308  // USER1 uses the pre-iteration value of the non-trivially invariant loop variable.
309  auto user1 = TestOperation::createNode(thetaNode->subregion(), { indirectLoopVar.pre }, {});
310 
311  // Make the post value of indirectLoopVar come from the trivially invariant loop var.
312  indirectLoopVar.post->divert_to(invariantLoopVar.pre);
313 
314  // USER2 uses the loop ouput value of the indirectly invariant loop variable.
315  auto user2 = TestOperation::createNode(&rvsdg.GetRootRegion(), { indirectLoopVar.output }, {});
316 
317  view(&rvsdg.GetRootRegion(), stdout);
318 
319  // Act
320  const auto & tracedUser1 = traceOutputIntraProcedurally(*user1->input(0)->origin());
321  const auto & tracedUser2 = traceOutputIntraProcedurally(*user2->input(0)->origin());
322 
323  // Assert
324  EXPECT_TRUE(ThetaLoopVarIsInvariant(invariantLoopVar));
325  EXPECT_FALSE(ThetaLoopVarIsInvariant(indirectLoopVar));
326  EXPECT_EQ(&tracedUser1, &c20);
327  EXPECT_EQ(&tracedUser2, &c20);
328 }
329 
330 TEST(TraceTests, GammaCachingTest)
331 {
332  using namespace jlm::rvsdg;
333 
334  // Arrange
335  const auto controlType = ControlType::Create(2);
336  const auto valueType = TestType::createValueType();
337 
338  Graph rvsdg;
339 
340  auto & predicate = GraphImport::Create(rvsdg, controlType, "predicate");
341  auto & i1 = GraphImport::Create(rvsdg, valueType, "i1");
342  auto & i2 = GraphImport::Create(rvsdg, valueType, "i2");
343 
344  auto gammaNode = GammaNode::create(&predicate, 2);
345  auto i1EntryVar = gammaNode->AddEntryVar(&i1);
346  auto i2EntryVar = gammaNode->AddEntryVar(&i2);
347 
348  auto exitVar =
349  gammaNode->AddExitVar({ i1EntryVar.branchArgument[0], i1EntryVar.branchArgument[1] });
350 
351  auto & graphExport = GraphExport::Create(*exitVar.output, "export");
352 
353  constexpr bool enableCaching = true;
354  OutputTracer tracer(enableCaching);
355 
356  // Act & Assert
357  // This is the first time we are tracing this output. We expect it to arrive at i1.
358  auto traceResult = &tracer.trace(*graphExport.origin());
359  assert(traceResult == &i1);
360 
361  // Divert the origins of the exit variable results to i2.
362  exitVar.branchResult[0]->divert_to(i2EntryVar.branchArgument[0]);
363  exitVar.branchResult[1]->divert_to(i2EntryVar.branchArgument[1]);
364 
365  // Since we traced graphExport already and had caching enabled in the tracer, we expect the tracer
366  // to still return i1 even though we redirected the origins of the exit variable results to i2.
367  traceResult = &tracer.trace(*graphExport.origin());
368  assert(traceResult == &i1);
369 
370  // Clear the tracing cache. We should now arrive at i2.
371  tracer.clearCache();
372  traceResult = &tracer.trace(*graphExport.origin());
373  assert(traceResult == &i2);
374 }
375 
376 TEST(TraceTests, ThetaCachingTest)
377 {
378  using namespace jlm::rvsdg;
379 
380  // Arrange
381  const auto controlType = ControlType::Create(2);
382  const auto valueType = TestType::createValueType();
383 
384  Graph rvsdg;
385 
386  auto & i1 = GraphImport::Create(rvsdg, valueType, "i1");
387  auto & i2 = GraphImport::Create(rvsdg, valueType, "i2");
388 
389  auto thetaNode = ThetaNode::create(&rvsdg.GetRootRegion());
390  auto loopVar1 = thetaNode->AddLoopVar(&i1);
391  auto loopVar2 = thetaNode->AddLoopVar(&i2);
392 
393  auto & graphExport = GraphExport::Create(*loopVar1.output, "export");
394 
395  constexpr bool enableCaching = true;
396  OutputTracer tracer(enableCaching);
397 
398  // Act & Assert
399  // This is the first time we are tracing this output. We expect it to arrive at i1.
400  auto traceResult = &tracer.trace(*graphExport.origin());
401  assert(traceResult == &i1);
402 
403  // Divert the origins of the loop variables' post value
404  loopVar1.post->divert_to(loopVar2.pre);
405  loopVar2.post->divert_to(loopVar1.pre);
406 
407  // Since we traced graphExport already and had caching enabled in the tracer, we expect the tracer
408  // to still return i1 even though we redirected loopVar1.
409  traceResult = &tracer.trace(*graphExport.origin());
410  assert(traceResult == &i1);
411 
412  // Clear the tracing cache. We should now arrive at the output of loopVar1.
413  tracer.clearCache();
414  traceResult = &tracer.trace(*graphExport.origin());
415  assert(traceResult == loopVar1.output);
416 }
static Output & create(Region &region, BitValueRepresentation value)
Definition: constant.hpp:44
static std::shared_ptr< const ControlType > Create(std::size_t nalternatives)
Instantiates control type.
Definition: control.cpp:50
static GammaNode * create(jlm::rvsdg::Output *predicate, size_t nalternatives)
Definition: gamma.hpp:161
static GraphExport & Create(Output &origin, std::string name)
Definition: graph.cpp:62
static GraphImport & Create(Graph &graph, std::shared_ptr< const rvsdg::Type > type, std::string name)
Definition: graph.cpp:36
Region & GetRootRegion() const noexcept
Definition: graph.hpp:99
void setTraceThroughStructuralNodes(bool value) noexcept
Definition: Trace.hpp:59
Output & trace(Output &output)
Definition: Trace.cpp:22
static SimpleNode * createNode(Region *region, const std::vector< Output * > &operands, std::vector< std::shared_ptr< const Type >> resultTypes)
static std::shared_ptr< const TestType > createValueType()
Definition: TestType.cpp:67
static ThetaNode * create(rvsdg::Region *parent)
Definition: theta.hpp:73
TEST(TraceTests, testTracingIOBarrier)
Definition: TraceTests.cpp:20
static bool ThetaLoopVarIsInvariant(const ThetaNode::LoopVar &loopVar) noexcept
Definition: theta.hpp:227
std::string view(const rvsdg::Region *region)
Definition: view.cpp:142
Output & traceOutputIntraProcedurally(Output &output)
Definition: Trace.cpp:283