Jlm
LocalAliasAnalysisTests.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 
17 #include <jlm/llvm/TestRvsdgs.hpp>
20 #include <jlm/rvsdg/control.hpp>
21 #include <jlm/rvsdg/gamma.hpp>
22 #include <jlm/rvsdg/view.hpp>
23 
27 static void
30  const jlm::rvsdg::Output & p1,
31  size_t s1,
32  const jlm::rvsdg::Output & p2,
33  size_t s2,
35 {
36  const auto actual = aa.Query(p1, s1, p2, s2);
37  EXPECT_EQ(actual, expected);
38 
39  // An alias analysis query should always be symmetrical, so check the opposite as well
40  const auto mirror = aa.Query(p2, s2, p1, s1);
41  EXPECT_EQ(mirror, expected);
42 }
43 
79 {
80  struct Outputs
81  {
99  };
100 
101 public:
102  const Outputs &
103  GetOutputs() const noexcept
104  {
105  return Outputs_;
106  }
107 
108 private:
109  std::unique_ptr<jlm::llvm::LlvmRvsdgModule>
110  SetupRvsdg() override
111  {
112  using namespace jlm;
113  using namespace jlm::llvm;
114 
115  auto rvsdgModule = LlvmRvsdgModule::Create(jlm::util::FilePath(""), "", "");
116  auto & rvsdg = rvsdgModule->Rvsdg();
117 
118  const auto pointerType = PointerType::Create();
119  const auto intType = rvsdg::BitType::Create(32);
120  const auto shortType = rvsdg::BitType::Create(16);
121  const auto byteType = rvsdg::BitType::Create(8);
122  const auto intArrayType = ArrayType::Create(intType, 10);
123  const auto ioStateType = IOStateType::Create();
124  const auto memoryStateType = MemoryStateType::Create();
125 
126  const auto funcType = rvsdg::FunctionType::Create(
127  { pointerType, ioStateType, memoryStateType },
128  { ioStateType, memoryStateType });
129 
130  const auto getPtrFuncType = rvsdg::FunctionType::Create(
131  { ioStateType, memoryStateType },
132  { pointerType, ioStateType, memoryStateType });
133 
134  Outputs_.GetPtr = &LlvmGraphImport::createFunctionImport(
135  rvsdg,
136  getPtrFuncType,
137  "getPtr",
138  Linkage::externalLinkage,
139  CallingConvention::Default);
140 
141  Outputs_.Global = &LlvmGraphImport::createGlobalImport(
142  rvsdg,
143  intType,
144  pointerType,
145  "global",
146  Linkage::externalLinkage,
147  false,
148  4);
149  Outputs_.GlobalShort = &LlvmGraphImport::createGlobalImport(
150  rvsdg,
151  shortType,
152  pointerType,
153  "globalShort",
154  Linkage::externalLinkage,
155  false,
156  4);
157  Outputs_.Array = &LlvmGraphImport::createGlobalImport(
158  rvsdg,
159  intArrayType,
160  pointerType,
161  "array",
162  Linkage::externalLinkage,
163  false,
164  4);
165 
166  // Setup the function "func"
167  {
168  auto & lambdaNode = *rvsdg::LambdaNode::Create(
169  rvsdg.GetRootRegion(),
170  LlvmLambdaOperation::Create(funcType, "func", Linkage::internalLinkage));
171 
172  Outputs_.P = lambdaNode.GetFunctionArguments()[0];
173  auto ioState = lambdaNode.GetFunctionArguments()[1];
174  auto memoryState = lambdaNode.GetFunctionArguments()[2];
175 
176  const auto getPtrCtxVar = lambdaNode.AddContextVar(*Outputs_.GetPtr).inner;
177  const auto arrayCtxVar = lambdaNode.AddContextVar(*Outputs_.Array).inner;
178  const auto globalCtxVar = lambdaNode.AddContextVar(*Outputs_.Global).inner;
179 
180  const auto constantOne =
181  &rvsdg::BitConstantOperation::create(*lambdaNode.subregion(), { 32, 1 });
182  const auto constantTwo =
183  &rvsdg::BitConstantOperation::create(*lambdaNode.subregion(), { 32, 2 });
184  const auto constantThree =
185  &rvsdg::BitConstantOperation::create(*lambdaNode.subregion(), { 32, 3 });
186  const auto constantFour =
187  &rvsdg::BitConstantOperation::create(*lambdaNode.subregion(), { 32, 4 });
188  const auto constantMinusTwo =
189  &rvsdg::BitConstantOperation::create(*lambdaNode.subregion(), { 32, -2 });
190 
191  const auto alloca1Outputs = AllocaOperation::create(intType, constantOne, 4);
192  const auto alloca2Outputs = AllocaOperation::create(intType, constantOne, 4);
193 
194  Outputs_.Alloca1 = alloca1Outputs[0];
195  Outputs_.Alloca2 = alloca2Outputs[0];
196 
197  memoryState =
198  MemoryStateMergeOperation::Create({ memoryState, alloca1Outputs[1], alloca2Outputs[1] });
199 
200  // Load from the pointer p
201  const auto loadP =
202  LoadNonVolatileOperation::Create(Outputs_.P, { memoryState }, pointerType, 8);
203  memoryState = loadP[1];
204 
205  Outputs_.Q = GetElementPtrOperation::create(loadP[0], { constantTwo }, intType);
206  Outputs_.QPlus2 = GetElementPtrOperation::create(loadP[0], { constantFour }, intType);
207  Outputs_.QAgain =
208  GetElementPtrOperation::create(Outputs_.QPlus2, { constantMinusTwo }, intType);
209 
210  // Create offsets into array
211  Outputs_.Arr1 = GetElementPtrOperation::create(arrayCtxVar, { constantOne }, intType);
212  Outputs_.Arr2 = GetElementPtrOperation::create(arrayCtxVar, { constantTwo }, intType);
213  Outputs_.Arr3 = GetElementPtrOperation::create(arrayCtxVar, { constantThree }, intType);
214 
215  // Create a load of the global integer variable "global"
216  const auto loadGlobal =
217  LoadNonVolatileOperation::Create(globalCtxVar, { memoryState }, intType, 4);
218  memoryState = loadGlobal[1];
220  GetElementPtrOperation::create(arrayCtxVar, { loadGlobal[0] }, byteType);
221 
222  // Make alloca2 escape
223  const auto storeOutputs =
224  StoreNonVolatileOperation::Create(Outputs_.P, Outputs_.Alloca2, { memoryState }, 4);
225  memoryState = storeOutputs[0];
226 
227  // Get bytePtr by calling getPtr()
228  const auto callOutputs =
229  CallOperation::Create(getPtrCtxVar, getPtrFuncType, { ioState, memoryState });
230  Outputs_.BytePtr = callOutputs[0];
231  ioState = callOutputs[1];
232  memoryState = callOutputs[2];
233 
235  GetElementPtrOperation::create(Outputs_.BytePtr, { constantTwo }, byteType);
236 
237  lambdaNode.finalize({ ioState, memoryState });
238  Outputs_.Func = lambdaNode.output();
239  }
240 
241  return rvsdgModule;
242  }
243 
245 };
246 
247 TEST(LocalAliasAnalysisTests, TestLocalAliasAnalysis)
248 {
249  using namespace jlm::llvm::aa;
250 
251  // Arrange
253  rvsdg.InitializeTest();
254  const auto & outputs = rvsdg.GetOutputs();
255 
256  jlm::rvsdg::view(&rvsdg.graph().GetRootRegion(), stdout);
257 
259 
260  // Assert
261 
262  // Distinct global variables do not alias
263  Expect(aa, *outputs.Global, 4, *outputs.GlobalShort, 2, AliasAnalysis::NoAlias);
264  Expect(aa, *outputs.Global, 4, *outputs.Arr1, 4, AliasAnalysis::NoAlias);
265 
266  // An alloca never aliases any other memory allocating operation
267  Expect(aa, *outputs.Alloca2, 4, *outputs.Alloca1, 4, AliasAnalysis::NoAlias);
268  Expect(aa, *outputs.Alloca2, 4, *outputs.Global, 4, AliasAnalysis::NoAlias);
269  Expect(aa, *outputs.Alloca2, 4, *outputs.Array, 4, AliasAnalysis::NoAlias);
270  Expect(aa, *outputs.Alloca2, 4, *outputs.Arr1, 4, AliasAnalysis::NoAlias);
271 
272  // An alloca that has not "escaped" can not alias external pointers
273  Expect(aa, *outputs.Alloca1, 4, *outputs.BytePtr, 4, AliasAnalysis::NoAlias);
274 
275  // An alloca that has "escaped" may alias external pointers
276  Expect(aa, *outputs.Alloca2, 4, *outputs.BytePtr, 4, AliasAnalysis::MayAlias);
277 
278  // Distinct offsets can not alias, unless the access regions overlap
279  Expect(aa, *outputs.Q, 8, *outputs.QPlus2, 8, AliasAnalysis::NoAlias);
280  Expect(aa, *outputs.Q, 9, *outputs.QPlus2, 8, AliasAnalysis::MayAlias);
281  Expect(aa, *outputs.Q, 8, *outputs.QPlus2, 16, AliasAnalysis::NoAlias);
282 
283  // Identical offsets are MustAlias
284  Expect(aa, *outputs.Q, 4, *outputs.QAgain, 4, AliasAnalysis::MustAlias);
285 
286  // q is at least 8 bytes into the storage instance of *p
287  // so it can not alias with the first 8 bytes of array
288  Expect(aa, *outputs.Array, 8, *outputs.Q, 4, AliasAnalysis::NoAlias);
289  Expect(aa, *outputs.Array, 9, *outputs.Q, 4, AliasAnalysis::MayAlias);
290  // We know that arr1, arr2 and arr3 are 4, 8 and 12 bytes into array
291  Expect(aa, *outputs.Arr1, 4, *outputs.Q, 4, AliasAnalysis::NoAlias);
292  Expect(aa, *outputs.Arr1, 5, *outputs.Q, 4, AliasAnalysis::MayAlias);
293  Expect(aa, *outputs.Arr2, 4, *outputs.Q, 4, AliasAnalysis::MayAlias);
294  Expect(aa, *outputs.Arr3, 4, *outputs.Q, 4, AliasAnalysis::MayAlias);
295 
296  // An unknown offset into array can only alias with array, at all offsets
297  Expect(aa, *outputs.ArrUnknown, 4, *outputs.Array, 4, AliasAnalysis::MayAlias);
298  Expect(aa, *outputs.ArrUnknown, 4, *outputs.Arr1, 4, AliasAnalysis::MayAlias);
299  Expect(aa, *outputs.ArrUnknown, 4, *outputs.Arr2, 4, AliasAnalysis::MayAlias);
300  Expect(aa, *outputs.ArrUnknown, 4, *outputs.Arr3, 4, AliasAnalysis::MayAlias);
301  Expect(aa, *outputs.ArrUnknown, 4, *outputs.Global, 4, AliasAnalysis::NoAlias);
302  // Q may be a pointer into array, so it is also "MayAlias"
303  Expect(aa, *outputs.ArrUnknown, 4, *outputs.Q, 4, AliasAnalysis::MayAlias);
304 
305  // We know that q is at least 16 bytes into its storage instance,
306  // so it may not alias with storage instances that are 16 bytes or less
307  Expect(aa, *outputs.Q, 4, *outputs.Global, 4, AliasAnalysis::NoAlias);
308 
309  // A five byte operation can never target the 4 byte global variable
310  Expect(aa, *outputs.BytePtr, 5, *outputs.Global, 4, AliasAnalysis::NoAlias);
311  // A four byte operation can, however
312  Expect(aa, *outputs.BytePtr, 4, *outputs.Global, 4, AliasAnalysis::MayAlias);
313  // Even a 40 byte operation can target the 40 byte global array
314  Expect(aa, *outputs.BytePtr, 40, *outputs.Array, 4, AliasAnalysis::MayAlias);
315  // The 40 byte operation may overlap with 4 bytes at any offset within the Array
316  Expect(aa, *outputs.BytePtr, 40, *outputs.Arr3, 4, AliasAnalysis::MayAlias);
317 
318  // BytePtrPlus2 has an offset of at least 2, so can not alias with the first 2 bytes of anything
319  Expect(aa, *outputs.BytePtrPlus2, 2, *outputs.Alloca2, 2, AliasAnalysis::NoAlias);
320  Expect(aa, *outputs.BytePtrPlus2, 2, *outputs.Alloca2, 3, AliasAnalysis::MayAlias);
321  Expect(aa, *outputs.BytePtrPlus2, 2, *outputs.Array, 2, AliasAnalysis::NoAlias);
322  Expect(aa, *outputs.BytePtrPlus2, 2, *outputs.Array, 3, AliasAnalysis::MayAlias);
323  // Arr1 is already 4 bytes into array, so BytePtrPlus2 can alias with it
324  Expect(aa, *outputs.BytePtrPlus2, 2, *outputs.Arr1, 2, AliasAnalysis::MayAlias);
325 }
326 
358 {
359  struct Outputs
360  {
361  jlm::rvsdg::Output * Func = {};
363  jlm::rvsdg::Output * Ptr = {};
364  jlm::rvsdg::Output * Alloca1 = {};
365  jlm::rvsdg::Output * Alloca2 = {};
366  jlm::rvsdg::Output * Alloca3 = {};
367  jlm::rvsdg::Output * AllocaUnknown = {};
368  jlm::rvsdg::Output * AllocaUnknownPlus1 = {};
369  jlm::rvsdg::Output * Alloca3Plus1 = {};
370  jlm::rvsdg::Output * Alloca3UnknownOffset = {};
371  jlm::rvsdg::Output * Alloca3KnownOffset = {};
372  };
373 
374 public:
375  const Outputs &
376  GetOutputs() const noexcept
377  {
378  return Outputs_;
379  }
380 
381 private:
382  std::unique_ptr<jlm::llvm::LlvmRvsdgModule>
383  SetupRvsdg() override
384  {
385  using namespace jlm;
386  using namespace jlm::llvm;
387 
388  auto rvsdgModule = LlvmRvsdgModule::Create(jlm::util::FilePath(""), "", "");
389  auto & rvsdg = rvsdgModule->Rvsdg();
390 
391  const auto pointerType = PointerType::Create();
392  const auto int1Type = rvsdg::BitType::Create(1);
393  const auto int32Type = rvsdg::BitType::Create(32);
394  const auto int64Type = rvsdg::BitType::Create(64);
395  const auto intArrayType = ArrayType::Create(int32Type, 2);
396  const auto ioStateType = IOStateType::Create();
397  const auto memoryStateType = MemoryStateType::Create();
398 
399  const auto funcType = rvsdg::FunctionType::Create(
400  { int1Type, pointerType, ioStateType, memoryStateType },
401  { ioStateType, memoryStateType });
402 
403  // Setup the function "func"
404  {
405  auto & lambdaNode = *rvsdg::LambdaNode::Create(
406  rvsdg.GetRootRegion(),
407  LlvmLambdaOperation::Create(funcType, "func", Linkage::internalLinkage));
408 
409  Outputs_.X = lambdaNode.GetFunctionArguments()[0];
410  Outputs_.Ptr = lambdaNode.GetFunctionArguments()[1];
411  auto ioState = lambdaNode.GetFunctionArguments()[2];
412  auto memoryState = lambdaNode.GetFunctionArguments()[3];
413 
414  const auto constantZero =
415  &rvsdg::BitConstantOperation::create(*lambdaNode.subregion(), { 32, 0 });
416  const auto constantOne =
417  &rvsdg::BitConstantOperation::create(*lambdaNode.subregion(), { 32, 1 });
418 
419  const auto alloca1Outputs = AllocaOperation::create(int32Type, constantOne, 4);
420  const auto alloca2Outputs = AllocaOperation::create(int64Type, constantOne, 4);
421  const auto alloca3Outputs = AllocaOperation::create(intArrayType, constantOne, 4);
422 
423  Outputs_.Alloca1 = alloca1Outputs[0];
424  Outputs_.Alloca2 = alloca2Outputs[0];
425  Outputs_.Alloca3 = alloca3Outputs[0];
426 
427  memoryState = MemoryStateMergeOperation::Create(
428  { memoryState, alloca1Outputs[1], alloca2Outputs[1], alloca3Outputs[1] });
429 
430  const auto matchResult = rvsdg::MatchOperation::Create(*Outputs_.X, { { 1, 1 } }, 0, 2);
431  const auto gamma = rvsdg::GammaNode::create(matchResult, 2);
432  const auto entryVarA1 = gamma->AddEntryVar(Outputs_.Alloca1);
433  const auto entryVarA2 = gamma->AddEntryVar(Outputs_.Alloca2);
434  const auto exitVar =
435  gamma->AddExitVar({ entryVarA1.branchArgument[0], entryVarA2.branchArgument[1] });
436  Outputs_.AllocaUnknown = exitVar.output;
437 
438  Outputs_.AllocaUnknownPlus1 =
439  GetElementPtrOperation::create(Outputs_.AllocaUnknown, { constantOne }, int32Type);
440 
441  Outputs_.Alloca3Plus1 = GetElementPtrOperation::create(
442  Outputs_.Alloca3,
443  { constantZero, constantOne },
444  intArrayType);
445 
446  Outputs_.Alloca3UnknownOffset = rvsdg::CreateOpNode<SelectOperation>(
447  { Outputs_.X, Outputs_.Alloca3, Outputs_.Alloca3Plus1 },
448  pointerType)
449  .output(0);
450 
451  Outputs_.Alloca3KnownOffset =
452  rvsdg::CreateOpNode<SelectOperation>(
453  { Outputs_.X, Outputs_.Alloca3Plus1, Outputs_.Alloca3Plus1 },
454  pointerType)
455  .output(0);
456 
457  lambdaNode.finalize({ ioState, memoryState });
458  Outputs_.Func = lambdaNode.output();
459  }
460 
461  return rvsdgModule;
462  }
463 
465 };
466 
467 TEST(LocalAliasAnalysisTests, TestLocalAliasAnalysisMultipleOrigins)
468 {
469  using namespace jlm::llvm::aa;
470 
471  // Arrange
473  rvsdg.InitializeTest();
474  const auto & outputs = rvsdg.GetOutputs();
475 
476  jlm::rvsdg::view(&rvsdg.graph().GetRootRegion(), stdout);
477 
479 
480  // Assert
481 
482  // First check that none of the allocas have been mixed up with unknown pointers
483  Expect(aa, *outputs.Alloca1, 4, *outputs.Ptr, 4, AliasAnalysis::NoAlias);
484  Expect(aa, *outputs.Alloca2, 4, *outputs.Ptr, 4, AliasAnalysis::NoAlias);
485  Expect(aa, *outputs.Alloca3, 4, *outputs.Ptr, 4, AliasAnalysis::NoAlias);
486  Expect(aa, *outputs.Alloca3Plus1, 4, *outputs.Ptr, 4, AliasAnalysis::NoAlias);
487 
488  // Check that allocaUnknown may alias only alloca1 or alloca2
489  Expect(aa, *outputs.AllocaUnknown, 4, *outputs.Alloca1, 4, AliasAnalysis::MayAlias);
490  Expect(aa, *outputs.AllocaUnknown, 4, *outputs.Alloca2, 4, AliasAnalysis::MayAlias);
491  Expect(aa, *outputs.AllocaUnknown, 4, *outputs.Alloca3, 4, AliasAnalysis::NoAlias);
492  Expect(aa, *outputs.AllocaUnknown, 4, *outputs.Alloca3Plus1, 4, AliasAnalysis::NoAlias);
493  Expect(aa, *outputs.AllocaUnknown, 4, *outputs.Ptr, 4, AliasAnalysis::NoAlias);
494 
495  // If performing an 8 byte operation, it may only alias alloca2, becoming a must alias
496  Expect(aa, *outputs.AllocaUnknown, 8, *outputs.Alloca1, 4, AliasAnalysis::NoAlias);
497  Expect(aa, *outputs.AllocaUnknown, 8, *outputs.Alloca2, 4, AliasAnalysis::MustAlias);
498  // Performing a 9 byte operation is neither legal for alloca1 nor alloca2
499  Expect(aa, *outputs.AllocaUnknown, 9, *outputs.Alloca2, 4, AliasAnalysis::NoAlias);
500 
501  // Adding a 4 byte offset forces all operations to be on alloca2
502  Expect(aa, *outputs.AllocaUnknownPlus1, 1, *outputs.Alloca1, 1, AliasAnalysis::NoAlias);
503  // We also know that we are 4 bytes into alloca2
504  Expect(aa, *outputs.AllocaUnknownPlus1, 4, *outputs.Alloca2, 4, AliasAnalysis::NoAlias);
505  Expect(aa, *outputs.AllocaUnknownPlus1, 4, *outputs.Alloca2, 5, AliasAnalysis::MayAlias);
506  // Performing a 5 byte operation is neither legal for alloca1 nor alloca2
507  Expect(aa, *outputs.AllocaUnknownPlus1, 5, *outputs.Alloca2, 8, AliasAnalysis::NoAlias);
508 
509  // Check that the offset of allocaUnknown is correctly calculated (4 bytes)
510  Expect(aa, *outputs.AllocaUnknown, 4, *outputs.AllocaUnknownPlus1, 4, AliasAnalysis::NoAlias);
511  Expect(aa, *outputs.AllocaUnknown, 5, *outputs.AllocaUnknownPlus1, 4, AliasAnalysis::MayAlias);
512 
513  // Check that the pointer with an unknown offset into alloca3 does not alias anything else
514  Expect(aa, *outputs.Alloca3UnknownOffset, 4, *outputs.Alloca1, 4, AliasAnalysis::NoAlias);
515  Expect(aa, *outputs.Alloca3UnknownOffset, 4, *outputs.Alloca2, 4, AliasAnalysis::NoAlias);
516  Expect(aa, *outputs.Alloca3UnknownOffset, 4, *outputs.AllocaUnknown, 4, AliasAnalysis::NoAlias);
517  Expect(aa, *outputs.Alloca3UnknownOffset, 4, *outputs.Ptr, 4, AliasAnalysis::NoAlias);
518 
519  // It may alias alloca3 and alloca3 + 1
520  Expect(aa, *outputs.Alloca3UnknownOffset, 4, *outputs.Alloca3, 4, AliasAnalysis::MayAlias);
521  Expect(aa, *outputs.Alloca3UnknownOffset, 4, *outputs.Alloca3Plus1, 4, AliasAnalysis::MayAlias);
522 
523  // If performing an 8 byte operation, we know that we are at the start of alloca3
524  Expect(aa, *outputs.Alloca3UnknownOffset, 8, *outputs.Alloca3, 4, AliasAnalysis::MustAlias);
525  // We still overlap with the second half of alloca3
526  Expect(aa, *outputs.Alloca3UnknownOffset, 8, *outputs.Alloca3Plus1, 4, AliasAnalysis::MayAlias);
527 
528  // The select with duplicate operands should be a single origin: alloca3
529  Expect(aa, *outputs.Alloca3KnownOffset, 4, *outputs.Alloca3, 4, AliasAnalysis::NoAlias);
530  Expect(aa, *outputs.Alloca3KnownOffset, 4, *outputs.Alloca3Plus1, 4, AliasAnalysis::MustAlias);
531 }
TEST(LocalAliasAnalysisTests, TestLocalAliasAnalysis)
static void Expect(jlm::llvm::aa::AliasAnalysis &aa, const jlm::rvsdg::Output &p1, size_t s1, const jlm::rvsdg::Output &p2, size_t s2, jlm::llvm::aa::AliasAnalysis::AliasQueryResponse expected)
const Outputs & GetOutputs() const noexcept
std::unique_ptr< jlm::llvm::LlvmRvsdgModule > SetupRvsdg() override
Create RVSDG for this test.
const Outputs & GetOutputs() const noexcept
std::unique_ptr< jlm::llvm::LlvmRvsdgModule > SetupRvsdg() override
Create RVSDG for this test.
RvsdgTest class.
Definition: TestRvsdgs.hpp:29
const rvsdg::Graph & graph()
Definition: TestRvsdgs.hpp:41
virtual AliasQueryResponse Query(const rvsdg::Output &p1, size_t s1, const rvsdg::Output &p2, size_t s2)=0
Region & GetRootRegion() const noexcept
Definition: graph.hpp:99
Global memory state passed between functions.
static std::vector< Output * > Outputs(const Node &node, const size_t startIdx, const size_t size)
Definition: node.hpp:1077
std::string view(const rvsdg::Region *region)
Definition: view.cpp:142
static std::vector< jlm::rvsdg::Output * > outputs(const Node *node)
Definition: node.hpp:1058