Jlm
StoreValueForwardingTests.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2026 HÃ¥vard Krogstie <krogstie.havard@gmail.com>
3  * See COPYING for terms of redistribution.
4  */
5 
6 #include <gtest/gtest.h>
7 
16 #include <jlm/llvm/ir/Trace.hpp>
18 #include <jlm/rvsdg/gamma.hpp>
20 #include <jlm/rvsdg/TestType.hpp>
21 #include <jlm/rvsdg/theta.hpp>
22 #include <jlm/rvsdg/UnitType.hpp>
23 #include <jlm/rvsdg/view.hpp>
24 #include <jlm/util/Statistics.hpp>
25 
26 static void
28 {
30  jlm::llvm::StoreValueForwarding storeValueForwarding;
31  storeValueForwarding.Run(rvsdgModule, statisticsCollector);
32 }
33 
34 TEST(StoreValueForwardingTests, NestedAllocas)
35 {
36  using namespace jlm;
37  using namespace jlm::llvm;
38 
54  // Arrange
55  LlvmRvsdgModule rvsdgModule(jlm::util::FilePath(""), "", "");
56  auto & graph = rvsdgModule.Rvsdg();
57  const auto pointerType = PointerType::Create();
58  const auto intType = rvsdg::BitType::Create(32);
59  const auto ioStateType = IOStateType::Create();
60  const auto memoryStateType = MemoryStateType::Create();
61 
62  const auto funcType = rvsdg::FunctionType::Create(
63  { ioStateType, memoryStateType },
64  { intType, ioStateType, memoryStateType });
65 
66  // Setup the function "func"
67  auto & lambdaNode = *rvsdg::LambdaNode::Create(
68  graph.GetRootRegion(),
70 
71  const auto io0 = lambdaNode.GetFunctionArguments()[0];
72  const auto mem0 = lambdaNode.GetFunctionArguments()[1];
73 
74  auto & constantOne = IntegerConstantOperation::Create(*lambdaNode.subregion(), 32, 1);
75  auto allocaPOutputs = AllocaOperation::create(intType, constantOne.output(0), 4);
76  auto allocaAOutputs = AllocaOperation::create(intType, constantOne.output(0), 4);
77 
78  // Create constant 20 for the second STORE
79  auto & constantTwenty = IntegerConstantOperation::Create(*lambdaNode.subregion(), 32, 20);
80 
81  // STORE p, a, memP0, memA0
82  auto & storePANode = StoreNonVolatileOperation::CreateNode(
83  *allocaPOutputs[0],
84  *allocaAOutputs[0],
85  { allocaPOutputs[1], allocaAOutputs[1] },
86  4);
87 
88  // STORE a, 20, memP1, memA1
89  auto & storeA20Node = StoreNonVolatileOperation::CreateNode(
90  *allocaAOutputs[0],
91  *constantTwenty.output(0),
92  { storePANode.output(0), storePANode.output(1) },
93  4);
94 
95  // LOAD p, memP2, memA2
96  auto & loadPNode = LoadNonVolatileOperation::CreateNode(
97  *allocaPOutputs[0],
98  { storeA20Node.output(0), storeA20Node.output(1) },
99  pointerType,
100  8);
101 
102  // LOAD a0, memP3, memA3
103  auto & loadA0Node = LoadNonVolatileOperation::CreateNode(
104  *loadPNode.output(0),
105  { loadPNode.output(1), loadPNode.output(2) },
106  intType,
107  4);
108 
109  lambdaNode.finalize({ loadA0Node.output(0), io0, mem0 });
110 
111  // std::cout << rvsdg::view(&graph.GetRootRegion()) << std::endl;
112 
113  // Act
114  RunStoreValueForwarding(rvsdgModule);
115 
116  // std::cout << rvsdg::view(&graph.GetRootRegion()) << std::endl;
117 
118  // Assert
119 
120  // Both ALLOCAs should be gone, and only the LOAD of q should remain.
121  size_t allocaCount = 0;
122  size_t storeCount = 0;
123  size_t loadCount = 0;
124 
125  for (auto & node : lambdaNode.subregion()->Nodes())
126  {
127  if (is<AllocaOperation>(&node))
128  allocaCount++;
129  else if (is<StoreOperation>(&node))
130  storeCount++;
131  else if (is<LoadOperation>(&node))
132  loadCount++;
133  }
134  EXPECT_EQ(allocaCount, 0u);
135  EXPECT_EQ(storeCount, 0u);
136  EXPECT_EQ(loadCount, 0u);
137 
138  // Verify that the return value is a constant 20
139  const auto & result = *lambdaNode.GetFunctionResults()[0]->origin();
140  const auto resultValue = tryGetConstantSignedInteger(result);
141  EXPECT_EQ(resultValue, 20);
142 }
143 
144 TEST(StoreValueForwardingTests, GetElementPointerOffsets)
145 {
146  using namespace jlm;
147  using namespace jlm::llvm;
148 
167  // Arrange
168  LlvmRvsdgModule rvsdgModule(jlm::util::FilePath(""), "", "");
169  auto & graph = rvsdgModule.Rvsdg();
170  const auto bits32Type = rvsdg::BitType::Create(32);
171  const auto bits64Type = rvsdg::BitType::Create(64);
172  const auto byteType = rvsdg::BitType::Create(8);
173  const auto ioStateType = IOStateType::Create();
174  const auto memoryStateType = MemoryStateType::Create();
175 
176  const auto funcType = rvsdg::FunctionType::Create(
177  { ioStateType, memoryStateType },
178  { bits64Type, bits32Type, bits32Type, ioStateType, memoryStateType });
179 
180  // Setup the function "func"
181  auto & lambdaNode = *rvsdg::LambdaNode::Create(
182  graph.GetRootRegion(),
184 
185  const auto io0 = lambdaNode.GetFunctionArguments()[0];
186  const auto mem0 = lambdaNode.GetFunctionArguments()[1];
187 
188  auto & constantTwo = IntegerConstantOperation::Create(*lambdaNode.subregion(), 32, 2);
189  auto allocaAOutputs = AllocaOperation::create(bits32Type, constantTwo.output(0), 4);
190 
191  // Create constant 40 and 20 for the STOREs
192  auto & constantTwenty = IntegerConstantOperation::Create(*lambdaNode.subregion(), 32, 20);
193  auto & constantForty = IntegerConstantOperation::Create(*lambdaNode.subregion(), 32, 40);
194 
195  // STORE a, 40, memA0
196  auto & storeA40Node = StoreNonVolatileOperation::CreateNode(
197  *allocaAOutputs[0],
198  *constantForty.output(0),
199  { allocaAOutputs[1] },
200  4);
201 
202  // b = GetElementPointer a, bits32[1]
203  auto & constantOne = IntegerConstantOperation::Create(*lambdaNode.subregion(), 32, 1);
204  auto gepBOutput =
205  GetElementPtrOperation::create(allocaAOutputs[0], { constantOne.output(0) }, bits32Type);
206 
207  // STORE b, 20, memA1
208  auto & storeB20Node = StoreNonVolatileOperation::CreateNode(
209  *gepBOutput,
210  *constantTwenty.output(0),
211  { storeA40Node.output(0) },
212  4);
213 
214  // l1, memA3 = LOAD[bits64] a, memA2
215  auto & loadL1Node = LoadNonVolatileOperation::CreateNode(
216  *allocaAOutputs[0],
217  { storeB20Node.output(0) },
218  bits64Type,
219  8);
220 
221  // l2, memA4 = LOAD[bits32] a, memA3
222  auto & loadL2Node = LoadNonVolatileOperation::CreateNode(
223  *allocaAOutputs[0],
224  { loadL1Node.output(1) },
225  bits32Type,
226  4);
227 
228  // c = GetElementPointer[byte] a, 4
229  auto & constantFour = IntegerConstantOperation::Create(*lambdaNode.subregion(), 32, 4);
230  auto gepCOutput =
231  GetElementPtrOperation::create(allocaAOutputs[0], { constantFour.output(0) }, byteType);
232 
233  // l3, memA5 = LOAD[bits32] c, memA4
234  auto & loadL3Node =
235  LoadNonVolatileOperation::CreateNode(*gepCOutput, { loadL2Node.output(1) }, bits32Type, 4);
236 
237  lambdaNode.finalize(
238  { loadL1Node.output(0), loadL2Node.output(0), loadL3Node.output(0), io0, mem0 });
239 
240  // Act
241  RunStoreValueForwarding(rvsdgModule);
242 
243  // std::cout << rvsdg::view(&graph.GetRootRegion()) << std::endl;
244 
245  // Assert
246 
247  // The two stores should still be there, but only l1 should be the only LOAD
248  size_t storeCount = 0;
249  size_t loadCount = 0;
250 
251  for (auto & node : lambdaNode.subregion()->Nodes())
252  {
253  if (is<StoreOperation>(&node))
254  storeCount++;
255  else if (is<LoadOperation>(&node))
256  loadCount++;
257  }
258  EXPECT_EQ(storeCount, 2u);
259  EXPECT_EQ(loadCount, 1u);
260 
261  // Verify that the last two return values are constants 40 and 20
262  const auto results = lambdaNode.GetFunctionResults();
263  const auto r1 = tryGetConstantSignedInteger(*results[0]->origin());
264  const auto r2 = tryGetConstantSignedInteger(*results[1]->origin());
265  const auto r3 = tryGetConstantSignedInteger(*results[2]->origin());
266  EXPECT_FALSE(r1.has_value());
267  EXPECT_EQ(r2, 40);
268  EXPECT_EQ(r3, 20);
269 }
270 
271 TEST(StoreValueForwardingTests, RoutingIn)
272 {
273  using namespace jlm;
274  using namespace jlm::llvm;
275 
297  // Arrange
298  LlvmRvsdgModule rvsdgModule(jlm::util::FilePath(""), "", "");
299  auto & graph = rvsdgModule.Rvsdg();
300  const auto pointerType = PointerType::Create();
301  const auto bits32Type = rvsdg::BitType::Create(32);
302  const auto ioStateType = IOStateType::Create();
303  const auto memoryStateType = MemoryStateType::Create();
304  const auto unitType = rvsdg::UnitType::Create();
305 
306  const auto funcType = rvsdg::FunctionType::Create(
307  { pointerType, ioStateType, memoryStateType },
308  { bits32Type, ioStateType, memoryStateType });
309 
310  // Setup the function "func"
311  auto & lambdaNode = *rvsdg::LambdaNode::Create(
312  graph.GetRootRegion(),
314 
315  auto & q = *lambdaNode.GetFunctionArguments()[0];
316  auto & io0 = *lambdaNode.GetFunctionArguments()[1];
317  auto & mem0 = *lambdaNode.GetFunctionArguments()[2];
318 
319  // Create constant 40 for the STORE
320  auto & constantForty = IntegerConstantOperation::Create(*lambdaNode.subregion(), 32, 40);
321 
322  // mem1 = STORE[bits32] q, 40, mem0
323  auto & storeQ40Node =
324  StoreNonVolatileOperation::CreateNode(q, *constantForty.output(0), { &mem0 }, 4);
325  auto & mem1 = *StoreOperation::MemoryStateOutputs(storeQ40Node).begin();
326 
327  // Create theta node structure
328  auto & thetaNode = *rvsdg::ThetaNode::create(lambdaNode.subregion());
329 
330  auto qLoopVar = thetaNode.AddLoopVar(&q);
331  auto memLoopVar = thetaNode.AddLoopVar(&mem1);
332  auto undefL = UndefValueOperation::Create(*lambdaNode.subregion(), bits32Type);
333  auto lLoopVar = thetaNode.AddLoopVar(undefL);
334 
335  // Create gamma node inside theta
336  auto & predicate = *thetaNode.predicate()->origin();
337  auto & gammaNode = rvsdg::GammaNode::Create(predicate, 2, { unitType, unitType });
338 
339  auto qEntryVar = gammaNode.AddEntryVar(qLoopVar.pre);
340  auto memEntryVar = gammaNode.AddEntryVar(memLoopVar.pre);
341 
342  // Create first gamma case: LOAD operation
343  auto & loadNode = LoadNonVolatileOperation::CreateNode(
344  *qEntryVar.branchArgument[0],
345  { memEntryVar.branchArgument[0] },
346  bits32Type,
347  4);
348  auto & loadedValue = LoadOperation::LoadedValueOutput(loadNode);
349  auto & mem4 = *LoadOperation::MemoryStateOutputs(loadNode).begin();
350 
351  // Create second gamma case: constant 70
352  auto & gammaSubregion1 = *gammaNode.subregion(1);
353  auto & constantSeventy = IntegerConstantOperation::Create(gammaSubregion1, 32, 70);
354 
355  // Create gamma exit variables
356  auto lExitVar = gammaNode.AddExitVar({ &loadedValue, constantSeventy.output(0) });
357  auto memExitVar = gammaNode.AddExitVar({ &mem4, memEntryVar.branchArgument[1] });
358 
359  // route theta results
360  memLoopVar.post->divert_to(memExitVar.output);
361  lLoopVar.post->divert_to(lExitVar.output);
362 
363  // Finalize lambda node
364  lambdaNode.finalize({ lLoopVar.output, &io0, memLoopVar.output });
365 
366  std::cout << rvsdg::view(&graph.GetRootRegion()) << std::endl;
367 
368  // Act
369  RunStoreValueForwarding(rvsdgModule);
370 
371  std::cout << rvsdg::view(&graph.GetRootRegion()) << std::endl;
372 
373  // Assert
374 
375  // The result in gamma region 0 should be the constant 40
376  auto & branch0Result = *lExitVar.branchResult[0]->origin();
377  auto resultValue = tryGetConstantSignedInteger(branch0Result);
378  EXPECT_EQ(resultValue, 40);
379 
380  // The result should be routed in from the constant 40 node
381  auto & resultTraced = jlm::llvm::traceOutput(branch0Result);
382  EXPECT_EQ(&resultTraced, constantForty.output(0));
383 }
384 
385 TEST(StoreValueForwardingTests, RouteOut)
386 {
387  using namespace jlm;
388  using namespace jlm::llvm;
389 
414  // Arrange
415  LlvmRvsdgModule rvsdgModule(jlm::util::FilePath(""), "", "");
416  auto & graph = rvsdgModule.Rvsdg();
417  const auto pointerType = PointerType::Create();
418  const auto bits32Type = rvsdg::BitType::Create(32);
419  const auto ioStateType = IOStateType::Create();
420  const auto memoryStateType = MemoryStateType::Create();
421  const auto unitType = rvsdg::UnitType::Create();
422 
423  const auto funcType = rvsdg::FunctionType::Create(
424  { pointerType, ioStateType, memoryStateType },
425  { bits32Type, ioStateType, memoryStateType });
426 
427  // Setup the function "func"
428  auto & lambdaNode = *rvsdg::LambdaNode::Create(
429  graph.GetRootRegion(),
431 
432  auto & q = *lambdaNode.GetFunctionArguments()[0];
433  auto & io0 = *lambdaNode.GetFunctionArguments()[1];
434  auto & mem0 = *lambdaNode.GetFunctionArguments()[2];
435 
436  // Create constant 40 for the first STORE
437  auto & constantForty = IntegerConstantOperation::Create(*lambdaNode.subregion(), 32, 40);
438 
439  // mem1 = STORE[bits32] q, 40, mem0
440  auto & storeQ40Node =
441  StoreNonVolatileOperation::CreateNode(q, *constantForty.output(0), { &mem0 }, 4);
442  auto & mem1 = *StoreOperation::MemoryStateOutputs(storeQ40Node).begin();
443 
444  // Create theta node structure
445  auto & thetaNode = *rvsdg::ThetaNode::create(lambdaNode.subregion());
446 
447  auto qLoopVar = thetaNode.AddLoopVar(&q);
448  auto memLoopVar = thetaNode.AddLoopVar(&mem1);
449 
450  // Create gamma node inside theta
451  auto & predicate = *thetaNode.predicate()->origin();
452  auto & gammaNode = rvsdg::GammaNode::Create(predicate, 2, { unitType, unitType });
453 
454  auto qEntryVar = gammaNode.AddEntryVar(qLoopVar.pre);
455  auto memEntryVar = gammaNode.AddEntryVar(memLoopVar.pre);
456 
457  // Create first gamma case: STORE operation
458  auto & gammaSubregion0 = *gammaNode.subregion(0);
459  auto & constantTwenty = IntegerConstantOperation::Create(gammaSubregion0, 32, 20);
460  auto & storeQ20Node = StoreNonVolatileOperation::CreateNode(
461  *qEntryVar.branchArgument[0],
462  *constantTwenty.output(0),
463  { memEntryVar.branchArgument[0] },
464  4);
465  auto & mem4 = *StoreOperation::MemoryStateOutputs(storeQ20Node).begin();
466 
467  // Create gamma exit variables
468  auto memExitVar = gammaNode.AddExitVar({ &mem4, memEntryVar.branchArgument[1] });
469 
470  // route theta results
471  memLoopVar.post->divert_to(memExitVar.output);
472 
473  // l1, mem8 = LOAD[bits32] q, mem7
474  auto & loadNode =
475  LoadNonVolatileOperation::CreateNode(*qLoopVar.output, { memLoopVar.output }, bits32Type, 4);
476  auto & loadedValue = LoadOperation::LoadedValueOutput(loadNode);
477  auto & mem8 = *LoadOperation::MemoryStateOutputs(loadNode).begin();
478 
479  // Finalize lambda node
480  lambdaNode.finalize({ &loadedValue, &io0, &mem8 });
481 
482  // std::cout << rvsdg::view(&graph.GetRootRegion()) << std::endl;
483 
484  // Act
485  RunStoreValueForwarding(rvsdgModule);
486 
487  // std::cout << rvsdg::view(&graph.GetRootRegion()) << std::endl;
488 
489  // Assert
490 
491  // The LOAD should be gone, and the result should be routed through the gamma
492  const auto & resultOrigin = *lambdaNode.GetFunctionResults()[0]->origin();
493  const auto loopVar = thetaNode.MapOutputLoopVar(resultOrigin);
494 
495  // Inside the theta, the loop variable should come straight from the gamma
496  const auto & postOrigin = *loopVar.post->origin();
497  const auto exitVar = gammaNode.MapOutputExitVar(postOrigin);
498  // In the 0th subregion, the output should be a constant integer
499  const auto constInteger =
500  jlm::llvm::tryGetConstantSignedInteger(*exitVar.branchResult[0]->origin());
501  EXPECT_EQ(constInteger, 20);
502 
503  // In the 1st subregion, the output should be traced back to the loop var input
504  const auto & traced1stRegionOrigin = jlm::llvm::traceOutput(*exitVar.branchResult[1]->origin());
505  const auto loopVar2 = thetaNode.MapPreLoopVar(traced1stRegionOrigin);
506  EXPECT_EQ(loopVar.pre, loopVar2.pre);
507 
508  const auto constInputInteger = jlm::llvm::tryGetConstantSignedInteger(*loopVar.input->origin());
509  EXPECT_EQ(constInputInteger, 40);
510 }
511 
512 TEST(StoreValueForwardingTests, RouteAroundLoadLoop)
513 {
514  using namespace jlm;
515  using namespace jlm::llvm;
516 
536  // Arrange
537  LlvmRvsdgModule rvsdgModule(jlm::util::FilePath(""), "", "");
538  auto & graph = rvsdgModule.Rvsdg();
539  const auto pointerType = PointerType::Create();
540  const auto bits32Type = rvsdg::BitType::Create(32);
541  const auto ioStateType = IOStateType::Create();
542  const auto memoryStateType = MemoryStateType::Create();
543 
544  const auto funcType = rvsdg::FunctionType::Create(
545  { pointerType, ioStateType, memoryStateType },
546  { bits32Type, ioStateType, memoryStateType });
547 
548  // Setup the function "func"
549  auto & lambdaNode = *rvsdg::LambdaNode::Create(
550  graph.GetRootRegion(),
552 
553  auto & q = *lambdaNode.GetFunctionArguments()[0];
554  auto & io0 = *lambdaNode.GetFunctionArguments()[1];
555  auto & mem0 = *lambdaNode.GetFunctionArguments()[2];
556 
557  // mem1 = STORE[bits32] q, 40, mem0
558  auto & constantForty = IntegerConstantOperation::Create(*lambdaNode.subregion(), 32, 40);
559  auto & storeQ40Node =
560  StoreNonVolatileOperation::CreateNode(q, *constantForty.output(0), { &mem0 }, 4);
561  auto & mem1 = *StoreOperation::MemoryStateOutputs(storeQ40Node).begin();
562 
563  // _, mem4, l2 = theta q, mem1, undef
564  auto & thetaNode = *rvsdg::ThetaNode::create(lambdaNode.subregion());
565  auto qLoopVar = thetaNode.AddLoopVar(&q);
566  auto memLoopVar = thetaNode.AddLoopVar(&mem1);
567  auto undefL = UndefValueOperation::Create(*lambdaNode.subregion(), bits32Type);
568  auto lLoopVar = thetaNode.AddLoopVar(undefL);
569 
570  auto & loadInLoopNode =
571  LoadNonVolatileOperation::CreateNode(*qLoopVar.pre, { memLoopVar.pre }, bits32Type, 4);
572  auto & l1 = LoadOperation::LoadedValueOutput(loadInLoopNode);
573  auto & mem3 = *LoadOperation::MemoryStateOutputs(loadInLoopNode).begin();
574 
575  lLoopVar.post->divert_to(&l1);
576  memLoopVar.post->divert_to(&mem3);
577 
578  // l3, mem5 = LOAD[bits32] q, mem4
579  auto & loadAfterLoopNode =
580  LoadNonVolatileOperation::CreateNode(q, { memLoopVar.output }, bits32Type, 4);
581  auto & l3 = LoadOperation::LoadedValueOutput(loadAfterLoopNode);
582  auto & mem5 = *LoadOperation::MemoryStateOutputs(loadAfterLoopNode).begin();
583 
584  // add1 = ADD l2, l3
585  auto & addNode = rvsdg::CreateOpNode<IntegerAddOperation>({ lLoopVar.output, &l3 }, 32);
586  auto & add1 = *addNode.output(0);
587 
588  // return add1, io0, mem5
589  lambdaNode.finalize({ &add1, &io0, &mem5 });
590 
591  std::cout << rvsdg::view(&rvsdgModule.Rvsdg().GetRootRegion()) << std::endl;
592 
593  // Act
594  RunStoreValueForwarding(rvsdgModule);
595 
596  std::cout << rvsdg::view(&rvsdgModule.Rvsdg().GetRootRegion()) << std::endl;
597 
598  // Assert
599 
600  // The value replacing l2 should lead to a loop output variable,
601  // whose post origin is an invariant loop variable
602  const auto & addLhsOrigin = *addNode.input(0)->origin();
603  const auto loopVar1 = thetaNode.MapOutputLoopVar(addLhsOrigin);
604  const auto loopVar2 = thetaNode.MapPreLoopVar(*loopVar1.post->origin());
605  EXPECT_TRUE(rvsdg::ThetaLoopVarIsInvariant(loopVar2));
606  EXPECT_EQ(tryGetConstantSignedInteger(*loopVar2.input->origin()), 40);
607 
608  // The value replacing l3 should come from the constant directly, not a theta output.
609  const auto & addRhsOrigin = *addNode.input(1)->origin();
610  EXPECT_EQ(tryGetConstantSignedInteger(addRhsOrigin), 40);
611  EXPECT_EQ(rvsdg::TryGetOwnerNode<rvsdg::ThetaNode>(addRhsOrigin), nullptr);
612 }
613 
614 TEST(StoreValueForwardingTests, RouteUninitialized)
615 {
616  using namespace jlm;
617  using namespace jlm::llvm;
618 
641  // Arrange
642  LlvmRvsdgModule rvsdgModule(jlm::util::FilePath(""), "", "");
643  auto & graph = rvsdgModule.Rvsdg();
644  const auto bits32Type = rvsdg::BitType::Create(32);
645  const auto ioStateType = IOStateType::Create();
646  const auto memoryStateType = MemoryStateType::Create();
647  const auto unitType = rvsdg::UnitType::Create();
648 
649  const auto funcType = rvsdg::FunctionType::Create(
650  { ioStateType, memoryStateType },
651  { bits32Type, ioStateType, memoryStateType });
652 
653  auto & lambdaNode = *rvsdg::LambdaNode::Create(
654  graph.GetRootRegion(),
656 
657  auto & io0 = *lambdaNode.GetFunctionArguments()[0];
658  auto & mem0 = *lambdaNode.GetFunctionArguments()[1];
659 
660  // a, mem1 = ALLOCA[bits32], 1
661  auto & constantOne = IntegerConstantOperation::Create(*lambdaNode.subregion(), 32, 1);
662  auto allocaAOutputs = AllocaOperation::create(bits32Type, constantOne.output(0), 4);
663 
664  // pred = CTRL(0)
665  auto & predicate = rvsdg::ControlConstantOperation::create(*lambdaNode.subregion(), 2, 0);
666 
667  // mem5 = gamma pred, a, mem1
668  auto & gammaNode = rvsdg::GammaNode::Create(predicate, 2, { unitType, unitType });
669  auto aEntryVar = gammaNode.AddEntryVar(allocaAOutputs[0]);
670  auto memEntryVar = gammaNode.AddEntryVar(allocaAOutputs[1]);
671 
672  // [_, a1, mem2] { mem3 = STORE a1, 20, mem2 }[mem3]
673  auto & gammaSubregion0 = *gammaNode.subregion(0);
674  auto & constantTwenty = IntegerConstantOperation::Create(gammaSubregion0, 32, 20);
675  auto & storeA20Node = StoreNonVolatileOperation::CreateNode(
676  *aEntryVar.branchArgument[0],
677  *constantTwenty.output(0),
678  { memEntryVar.branchArgument[0] },
679  4);
680  auto & mem3 = *StoreOperation::MemoryStateOutputs(storeA20Node).begin();
681 
682  // [_, a2, mem4] { }[mem4]
683  auto memExitVar = gammaNode.AddExitVar({ &mem3, memEntryVar.branchArgument[1] });
684 
685  // ld, mem6 = LOAD[bits32] a, mem5
686  auto & loadNode = LoadNonVolatileOperation::CreateNode(
687  *allocaAOutputs[0],
688  { memExitVar.output },
689  bits32Type,
690  4);
691  auto & ld = LoadOperation::LoadedValueOutput(loadNode);
692 
693  lambdaNode.finalize({ &ld, &io0, &mem0 });
694 
695  // Act
696  RunStoreValueForwarding(rvsdgModule);
697 
698  // Assert
699  const auto & resultOrigin = *lambdaNode.GetFunctionResults()[0]->origin();
700  EXPECT_NE(rvsdg::TryGetOwnerNode<rvsdg::GammaNode>(resultOrigin), nullptr);
701 
702  const auto exitVar = gammaNode.MapOutputExitVar(resultOrigin);
703  EXPECT_EQ(jlm::llvm::tryGetConstantSignedInteger(*exitVar.branchResult[0]->origin()), 20);
704  const auto [undefNode, undefOperation] =
705  rvsdg::TryGetSimpleNodeAndOptionalOp<UndefValueOperation>(*exitVar.branchResult[1]->origin());
706  EXPECT_TRUE(undefNode && undefOperation);
707 }
708 
709 TEST(StoreValueForwardingTests, GepInLoop)
710 {
711  using namespace jlm;
712  using namespace jlm::llvm;
713 
742  // Arrange
743  LlvmRvsdgModule rvsdgModule(jlm::util::FilePath(""), "", "");
744  auto & graph = rvsdgModule.Rvsdg();
745  const auto pointerType = PointerType::Create();
746  const auto bits32Type = rvsdg::BitType::Create(32);
747  const auto intArrayType = ArrayType::Create(bits32Type, 4);
748  const auto ioStateType = IOStateType::Create();
749  const auto memoryStateType = MemoryStateType::Create();
750 
751  const auto funcType = rvsdg::FunctionType::Create(
752  { ioStateType, memoryStateType },
753  { bits32Type, ioStateType, memoryStateType });
754 
755  auto & lambdaNode = *rvsdg::LambdaNode::Create(
756  graph.GetRootRegion(),
758 
759  auto & io0 = *lambdaNode.GetFunctionArguments()[0];
760  auto & mem0 = *lambdaNode.GetFunctionArguments()[1];
761 
762  auto & constantZero = IntegerConstantOperation::Create(*lambdaNode.subregion(), 32, 0);
763  auto & constantOne = IntegerConstantOperation::Create(*lambdaNode.subregion(), 32, 1);
764  auto & constantTwo = IntegerConstantOperation::Create(*lambdaNode.subregion(), 32, 2);
765  auto & constantThree = IntegerConstantOperation::Create(*lambdaNode.subregion(), 32, 3);
766  auto & constantTwenty = IntegerConstantOperation::Create(*lambdaNode.subregion(), 32, 20);
767  auto & constantThirty = IntegerConstantOperation::Create(*lambdaNode.subregion(), 32, 30);
768 
769  // a, mem1 = ALLOCA[int[4]], 1
770  auto allocaAOutputs = AllocaOperation::create(intArrayType, constantOne.output(0), 4);
771 
772  // a2 = &a[2], a3 = &a[3]
774  allocaAOutputs[0],
775  { constantZero.output(0), constantTwo.output(0) },
776  intArrayType);
778  allocaAOutputs[0],
779  { constantZero.output(0), constantThree.output(0) },
780  intArrayType);
781 
782  // *a2 = 20; *a3 = 30;
783  auto & storeA220Node = StoreNonVolatileOperation::CreateNode(
784  *a2,
785  *constantTwenty.output(0),
786  { allocaAOutputs[1] },
787  4);
788  auto & mem1 = *StoreOperation::MemoryStateOutputs(storeA220Node).begin();
789  auto & storeA330Node =
790  StoreNonVolatileOperation::CreateNode(*a3, *constantThirty.output(0), { &mem1 }, 4);
791  auto & mem2 = *StoreOperation::MemoryStateOutputs(storeA330Node).begin();
792 
793  // do { ... } while (0)
794  auto & thetaNode = *rvsdg::ThetaNode::create(lambdaNode.subregion());
795  auto aLoopVar = thetaNode.AddLoopVar(allocaAOutputs[0]);
796  auto a2LoopVar = thetaNode.AddLoopVar(a2);
797  auto memLoopVar = thetaNode.AddLoopVar(&mem2);
798 
799  // loaded = *a2;
800  auto & loadInLoopNode =
801  LoadNonVolatileOperation::CreateNode(*a2LoopVar.pre, { memLoopVar.pre }, bits32Type, 4);
802  auto & loadedValue = LoadOperation::LoadedValueOutput(loadInLoopNode);
803  auto & mem3 = *LoadOperation::MemoryStateOutputs(loadInLoopNode).begin();
804 
805  // a1 = &a[1]; a22 = &a1[1];
806  auto & constantOneInLoop = IntegerConstantOperation::Create(*thetaNode.subregion(), 32, 1);
807  auto a1 =
808  GetElementPtrOperation::create(aLoopVar.pre, { constantOneInLoop.output(0) }, bits32Type);
809  auto a22 = GetElementPtrOperation::create(a1, { constantOneInLoop.output(0) }, bits32Type);
810 
811  // *a22 = loaded + 1;
812  auto & addLoadedOneNode =
813  rvsdg::CreateOpNode<IntegerAddOperation>({ &loadedValue, constantOneInLoop.output(0) }, 32);
814  auto & incrementedValue = *addLoadedOneNode.output(0);
815  auto & storeA22Node = StoreNonVolatileOperation::CreateNode(*a22, incrementedValue, { &mem3 }, 4);
816  auto & mem4 = *StoreOperation::MemoryStateOutputs(storeA22Node).begin();
817 
818  // *a1 = 10;
819  auto & constantTen = IntegerConstantOperation::Create(*thetaNode.subregion(), 32, 10);
820  auto & storeA1Node =
821  StoreNonVolatileOperation::CreateNode(*a1, *constantTen.output(0), { &mem4 }, 4);
822  auto & mem5 = *StoreOperation::MemoryStateOutputs(storeA1Node).begin();
823 
824  memLoopVar.post->divert_to(&mem5);
825 
826  // return *a2 + *a3;
827  auto & loadAfterLoopA2Node =
828  LoadNonVolatileOperation::CreateNode(*a2, { memLoopVar.output }, bits32Type, 4);
829  auto & loadedA2 = LoadOperation::LoadedValueOutput(loadAfterLoopA2Node);
830  auto & mem6 = *LoadOperation::MemoryStateOutputs(loadAfterLoopA2Node).begin();
831  auto & loadAfterLoopA3Node = LoadNonVolatileOperation::CreateNode(*a3, { &mem6 }, bits32Type, 4);
832  auto & loadedA3 = LoadOperation::LoadedValueOutput(loadAfterLoopA3Node);
833  auto & addResultNode = rvsdg::CreateOpNode<IntegerAddOperation>({ &loadedA2, &loadedA3 }, 32);
834  auto & resultValue = *addResultNode.output(0);
835 
836  lambdaNode.finalize({ &resultValue, &io0, &mem0 });
837 
838  std::cout << rvsdg::view(&graph.GetRootRegion()) << std::endl;
839 
840  // Act
841  RunStoreValueForwarding(rvsdgModule);
842 
843  std::cout << rvsdg::view(&graph.GetRootRegion()) << std::endl;
844 
845  // Assert
846 
847  // Check that the load of a[2] inside the loop is replaced by a loop variable
848  // which takes 20 as its initial value, and loaded + 1 as its post origin
849  const auto & loadedInLoopOrigin = *addLoadedOneNode.input(0)->origin();
850  const auto loadedLoopVar = thetaNode.MapPreLoopVar(loadedInLoopOrigin);
851  EXPECT_EQ(jlm::llvm::tryGetConstantSignedInteger(*loadedLoopVar.input->origin()), 20);
852  EXPECT_EQ(loadedLoopVar.post->origin(), addLoadedOneNode.output(0));
853 
854  // Check that the final load of a[2] is replaced by the value of loaded + 1 in the loop
855  const auto & addLhsOrigin = *addResultNode.input(0)->origin();
856  const auto a2ResultLoopVar = thetaNode.MapOutputLoopVar(addLhsOrigin);
857  EXPECT_EQ(a2ResultLoopVar.post->origin(), addLoadedOneNode.output(0));
858 
859  // Check that the final load of a[3] is directly attached to the constant 30,
860  // and that it does not go via an invariant loop variable
861  const auto & addRhsOrigin = *addResultNode.input(1)->origin();
862  EXPECT_EQ(&addRhsOrigin, constantThirty.output(0));
863 }
static jlm::util::StatisticsCollector statisticsCollector
TEST(StoreValueForwardingTests, NestedAllocas)
static void RunStoreValueForwarding(jlm::llvm::LlvmRvsdgModule &rvsdgModule)
static std::vector< rvsdg::Output * > create(std::shared_ptr< const rvsdg::Type > allocatedType, rvsdg::Output *count, const size_t alignment)
Definition: alloca.hpp:131
static std::shared_ptr< const ArrayType > Create(std::shared_ptr< const Type > type, size_t nelements)
Definition: types.hpp:98
static rvsdg::Output * create(rvsdg::Output *baseAddress, const std::vector< rvsdg::Output * > &indices, std::shared_ptr< const rvsdg::Type > pointeeType)
static std::shared_ptr< const IOStateType > Create()
Definition: types.cpp:343
static rvsdg::Node & Create(rvsdg::Region &region, IntegerValueRepresentation representation)
static std::unique_ptr< LlvmLambdaOperation > Create(std::shared_ptr< const jlm::rvsdg::FunctionType > type, std::string name, const jlm::llvm::Linkage &linkage, jlm::llvm::CallingConvention callingConvention, jlm::llvm::AttributeSet attributes)
Definition: lambda.hpp:84
static rvsdg::SimpleNode & CreateNode(rvsdg::Region &region, std::unique_ptr< LoadNonVolatileOperation > loadOperation, const std::vector< rvsdg::Output * > &operands)
Definition: Load.hpp:466
static rvsdg::Output & LoadedValueOutput(const rvsdg::Node &node)
Definition: Load.hpp:84
static rvsdg::Node::OutputIteratorRange MemoryStateOutputs(const rvsdg::Node &node) noexcept
Definition: Load.hpp:116
static std::shared_ptr< const MemoryStateType > Create()
Definition: types.cpp:379
static std::shared_ptr< const PointerType > Create()
Definition: types.cpp:45
static rvsdg::SimpleNode & CreateNode(rvsdg::Output &address, rvsdg::Output &value, const std::vector< rvsdg::Output * > &memoryStates, size_t alignment)
Definition: Store.hpp:323
static rvsdg::Node::OutputIteratorRange MemoryStateOutputs(const rvsdg::Node &node) noexcept
Definition: Store.hpp:93
Store Value Forwarding Optimization.
void Run(rvsdg::RvsdgModule &module, util::StatisticsCollector &statisticsCollector) override
Perform RVSDG transformation.
static jlm::rvsdg::Output * Create(rvsdg::Region &region, std::shared_ptr< const jlm::rvsdg::Type > type)
Definition: operators.hpp:1055
static std::shared_ptr< const BitType > Create(std::size_t nbits)
Creates bit type of specified width.
Definition: type.cpp:45
static Output & create(Region &region, ControlValueRepresentation value)
Definition: control.hpp:122
static std::shared_ptr< const FunctionType > Create(std::vector< std::shared_ptr< const jlm::rvsdg::Type >> argumentTypes, std::vector< std::shared_ptr< const jlm::rvsdg::Type >> resultTypes)
static GammaNode & Create(jlm::rvsdg::Output &predicate, size_t numAlternatives, std::vector< std::shared_ptr< const Type >> matchContentTypes)
Definition: gamma.hpp:167
EntryVar AddEntryVar(rvsdg::Output *origin)
Routes a variable into the gamma branches.
Definition: gamma.cpp:260
Region & GetRootRegion() const noexcept
Definition: graph.hpp:99
static LambdaNode * Create(rvsdg::Region &parent, std::unique_ptr< LambdaOperation > operation)
Definition: lambda.cpp:140
Graph & Rvsdg() noexcept
Definition: RvsdgModule.hpp:57
static ThetaNode * create(rvsdg::Region *parent)
Definition: theta.hpp:73
static std::shared_ptr< const UnitType > Create()
Definition: UnitType.cpp:33
Global memory state passed between functions.
rvsdg::Output & traceOutput(rvsdg::Output &output, const rvsdg::Region *withinRegion)
Definition: Trace.cpp:54
std::optional< int64_t > tryGetConstantSignedInteger(const rvsdg::Output &output)
Definition: Trace.cpp:62
static bool ThetaLoopVarIsInvariant(const ThetaNode::LoopVar &loopVar) noexcept
Definition: theta.hpp:227
std::string view(const rvsdg::Region *region)
Definition: view.cpp:142