Jlm
InliningTests.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2017 Nico Reißmann <nico.reissmann@gmail.com>
3  * Copyright 2025 Håvard Krogstie <krogstie.havard@gmail.com>
4  * See COPYING for terms of redistribution.
5  */
6 
7 #include <gtest/gtest.h>
8 
17 #include <jlm/rvsdg/control.hpp>
18 #include <jlm/rvsdg/gamma.hpp>
20 #include <jlm/rvsdg/TestType.hpp>
21 #include <jlm/rvsdg/theta.hpp>
22 #include <jlm/rvsdg/view.hpp>
23 #include <jlm/util/Statistics.hpp>
24 
30 static std::unique_ptr<jlm::util::Statistics>
32 {
35  jlm::util::StatisticsCollector collector(settings);
36  fctinline.Run(rm, collector);
37 
38  EXPECT_EQ(collector.NumCollectedStatistics(), 1u);
39  return collector.releaseStatistic(jlm::util::Statistics::Id::FunctionInlining);
40 }
41 
42 TEST(FunctionInliningTests, testSimpleInlining)
43 {
77  using namespace jlm::llvm;
78  using namespace jlm::rvsdg;
79 
80  // Arrange
82  auto & graph = rm.Rvsdg();
83  auto vt = TestType::createValueType();
84  auto iOStateType = IOStateType::Create();
85  auto memoryStateType = MemoryStateType::Create();
86  auto i = &jlm::rvsdg::GraphImport::Create(graph, vt, "i");
87 
88  Region * gammaRegion0 = nullptr;
89 
90  auto SetupF1 = [&]()
91  {
92  auto functionType = jlm::rvsdg::FunctionType::Create(
95 
96  auto lambda = jlm::rvsdg::LambdaNode::Create(
97  graph.GetRootRegion(),
99  lambda->AddContextVar(*i);
100 
101  auto t = TestOperation::createNode(
102  lambda->subregion(),
103  { lambda->GetFunctionArguments()[0] },
104  { vt });
105 
106  return lambda->finalize(
107  { t->output(0), lambda->GetFunctionArguments()[1], lambda->GetFunctionArguments()[2] });
108  };
109 
110  auto SetupF2 = [&](jlm::rvsdg::Output * f1)
111  {
112  auto ct = jlm::rvsdg::ControlType::Create(2);
113  auto functionType = jlm::rvsdg::FunctionType::Create(
115  vt,
119 
120  auto lambda = jlm::rvsdg::LambdaNode::Create(
121  graph.GetRootRegion(),
123  auto d = lambda->AddContextVar(*f1).inner;
124  auto controlArgument = lambda->GetFunctionArguments()[0];
125  auto valueArgument = lambda->GetFunctionArguments()[1];
126  auto iOStateArgument = lambda->GetFunctionArguments()[2];
127  auto memoryStateArgument = lambda->GetFunctionArguments()[3];
128 
129  auto gamma = jlm::rvsdg::GammaNode::create(controlArgument, 2);
130  gammaRegion0 = gamma->subregion(0);
131  auto gammaInputF1 = gamma->AddEntryVar(d);
132  auto gammaInputValue = gamma->AddEntryVar(valueArgument);
133  auto gammaInputIoState = gamma->AddEntryVar(iOStateArgument);
134  auto gammaInputMemoryState = gamma->AddEntryVar(memoryStateArgument);
135 
136  auto callResults = CallOperation::Create(
137  gammaInputF1.branchArgument[0],
138  jlm::rvsdg::AssertGetOwnerNode<jlm::rvsdg::LambdaNode>(*f1).GetOperation().Type(),
139  { gammaInputValue.branchArgument[0],
140  gammaInputIoState.branchArgument[0],
141  gammaInputMemoryState.branchArgument[0] });
142 
143  auto gammaOutputValue =
144  gamma->AddExitVar({ callResults[0], gammaInputValue.branchArgument[1] });
145  auto gammaOutputIoState =
146  gamma->AddExitVar({ callResults[1], gammaInputIoState.branchArgument[1] });
147  auto gammaOutputMemoryState =
148  gamma->AddExitVar({ callResults[2], gammaInputMemoryState.branchArgument[1] });
149 
150  return lambda->finalize(
151  { gammaOutputValue.output, gammaOutputIoState.output, gammaOutputMemoryState.output });
152  };
153 
154  auto f1 = SetupF1();
155  auto f2 = SetupF2(f1);
156 
157  GraphExport::Create(*f2, "f2");
158 
159  // jlm::rvsdg::view(graph.GetRootRegion(), stdout);
160 
161  // Act
162  auto statistics = runInlining(rm);
163 
164  // jlm::rvsdg::view(graph.GetRootRegion(), stdout);
165 
166  // Assert
167  // Check that the call has been replaced by the test operation inside f1
168  EXPECT_FALSE(Region::ContainsOperation<CallOperation>(graph.GetRootRegion(), true));
169  EXPECT_TRUE(Region::ContainsOperation<TestOperation>(*gammaRegion0, true));
170 
171  // Check that the statistics match what we expect. f2 is technically inlineable
172  EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#Functions"), 2u);
173  EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#InlineableFunctions"), 2u);
174  EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#FunctionCalls"), 1u);
175  EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#InlinableCalls"), 1u);
176  EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#CallsInlined"), 1u);
177 }
178 
179 TEST(FunctionInliningTests, testInliningWithAlloca)
180 {
216  using namespace jlm::llvm;
217  using namespace jlm::rvsdg;
218 
219  // Arrange
221  auto & graph = rm.Rvsdg();
222  auto vt = TestType::createValueType();
223  auto iOStateType = IOStateType::Create();
224  auto memoryStateType = MemoryStateType::Create();
225  auto i = &jlm::rvsdg::GraphImport::Create(graph, vt, "i");
226 
227  Region * gammaRegion0 = nullptr;
228  Region * f2Region = nullptr;
229 
230  auto SetupF1 = [&]()
231  {
232  auto functionType = jlm::rvsdg::FunctionType::Create(
235 
236  auto lambda = jlm::rvsdg::LambdaNode::Create(
237  graph.GetRootRegion(),
239  lambda->AddContextVar(*i);
240  auto lambdaArgs = lambda->GetFunctionArguments();
241 
242  const auto & one = IntegerConstantOperation::Create(*lambda->subregion(), 32, 1);
243  auto alloca = AllocaOperation::create(vt, one.output(0), 4);
244  auto store = StoreNonVolatileOperation::Create(alloca[0], lambdaArgs[0], { lambdaArgs[2] }, 4);
245 
246  return lambda->finalize({ lambdaArgs[1], store[0] });
247  };
248 
249  auto SetupF2 = [&](jlm::rvsdg::Output * f1)
250  {
251  auto ct = jlm::rvsdg::ControlType::Create(2);
252  auto functionType = jlm::rvsdg::FunctionType::Create(
254  vt,
258 
259  auto lambda = jlm::rvsdg::LambdaNode::Create(
260  graph.GetRootRegion(),
262  auto d = lambda->AddContextVar(*f1).inner;
263  auto controlArgument = lambda->GetFunctionArguments()[0];
264  auto valueArgument = lambda->GetFunctionArguments()[1];
265  auto iOStateArgument = lambda->GetFunctionArguments()[2];
266  auto memoryStateArgument = lambda->GetFunctionArguments()[3];
267  f2Region = lambda->subregion();
268 
269  auto gamma = jlm::rvsdg::GammaNode::create(controlArgument, 2);
270  gammaRegion0 = gamma->subregion(0);
271  auto gammaInputF1 = gamma->AddEntryVar(d);
272  auto gammaInputValue = gamma->AddEntryVar(valueArgument);
273  auto gammaInputIoState = gamma->AddEntryVar(iOStateArgument);
274  auto gammaInputMemoryState = gamma->AddEntryVar(memoryStateArgument);
275 
276  auto callResults = CallOperation::Create(
277  gammaInputF1.branchArgument[0],
278  jlm::rvsdg::AssertGetOwnerNode<jlm::rvsdg::LambdaNode>(*f1).GetOperation().Type(),
279  { gammaInputValue.branchArgument[0],
280  gammaInputIoState.branchArgument[0],
281  gammaInputMemoryState.branchArgument[0] });
282 
283  auto gammaOutputIoState =
284  gamma->AddExitVar({ callResults[0], gammaInputIoState.branchArgument[1] });
285  auto gammaOutputMemoryState =
286  gamma->AddExitVar({ callResults[1], gammaInputMemoryState.branchArgument[1] });
287 
288  return lambda->finalize({ gammaOutputIoState.output, gammaOutputMemoryState.output });
289  };
290 
291  auto f1 = SetupF1();
292  auto f2 = SetupF2(f1);
293 
294  GraphExport::Create(*f2, "f2");
295 
296  // jlm::rvsdg::view(graph.GetRootRegion(), stdout);
297 
298  // Act
299  runInlining(rm);
300 
301  // jlm::rvsdg::view(&graph.GetRootRegion(), stdout);
302 
303  // Assert
304  // Check that the call is gone
305  EXPECT_FALSE(Region::ContainsOperation<CallOperation>(graph.GetRootRegion(), true));
306  // A store should have taken its place in the gamma subregion
307  EXPECT_TRUE(Region::ContainsOperation<StoreNonVolatileOperation>(*gammaRegion0, true));
308  // Check that the alloca operation is not inside the gamma subregion
309  EXPECT_FALSE(Region::ContainsOperation<AllocaOperation>(*gammaRegion0, true));
310  // The alloca should have been moved to the top level of f2
311  EXPECT_TRUE(Region::ContainsOperation<AllocaOperation>(*f2Region, false));
312 }
313 
314 TEST(FunctionInliningTests, testIndirectCall)
315 {
322  using namespace jlm::llvm;
323  using namespace jlm::rvsdg;
324 
325  // Arrange
326  auto vt = TestType::createValueType();
327  auto iOStateType = IOStateType::Create();
328  auto memoryStateType = MemoryStateType::Create();
329 
330  auto functionType1 = jlm::rvsdg::FunctionType::Create(
333  auto pt = PointerType::Create();
334 
335  auto functionType2 = jlm::rvsdg::FunctionType::Create(
338 
340  auto & graph = rm.Rvsdg();
341  auto i = &jlm::rvsdg::GraphImport::Create(graph, functionType2, "i");
342 
343  auto SetupF1 = [&](const std::shared_ptr<const jlm::rvsdg::FunctionType> & functionType)
344  {
345  auto lambda = jlm::rvsdg::LambdaNode::Create(
346  graph.GetRootRegion(),
348  return lambda->finalize(
349  { lambda->GetFunctionArguments()[1], lambda->GetFunctionArguments()[2] });
350  };
351 
352  auto SetupF2 = [&](jlm::rvsdg::Output * f1)
353  {
354  auto functionType = jlm::rvsdg::FunctionType::Create(
357 
358  auto lambda = jlm::rvsdg::LambdaNode::Create(
359  graph.GetRootRegion(),
361  auto cvi = lambda->AddContextVar(*i).inner;
362  auto cvf1 = lambda->AddContextVar(*f1).inner;
363  auto iOStateArgument = lambda->GetFunctionArguments()[0];
364  auto memoryStateArgument = lambda->GetFunctionArguments()[1];
365 
366  auto callResults =
367  CallOperation::Create(cvi, functionType2, { cvf1, iOStateArgument, memoryStateArgument });
368 
369  return lambda->finalize(callResults);
370  };
371 
372  auto f1 = SetupF1(functionType1);
373  auto f2 = SetupF2(
374  jlm::rvsdg::CreateOpNode<FunctionToPointerOperation>({ f1 }, functionType1).output(0));
375 
376  GraphExport::Create(*f2, "f2");
377 
378  // jlm::rvsdg::view(&graph.GetRootRegion(), stdout);
379 
380  // Act
381  auto statistics = runInlining(rm);
382 
383  // jlm::rvsdg::view(&graph.GetRootRegion(), stdout);
384 
385  // Assert
386  // No inlining happens in this test, but both f1 and f2 are technically possible to inline
387  EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#Functions"), 2u);
388  EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#InlineableFunctions"), 2u);
389  EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#FunctionCalls"), 1u);
390  EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#InlinableCalls"), 0u);
391  EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#CallsInlined"), 0u);
392 }
393 
398 TEST(FunctionInliningTests, testFunctionWithDisqualifyingAlloca)
399 {
400  using namespace jlm::llvm;
401  using namespace jlm::rvsdg;
402 
403  // Arrange
404  auto vt = TestType::createValueType();
405  auto iOStateType = IOStateType::Create();
406  auto memoryStateType = MemoryStateType::Create();
407 
409  auto & graph = rm.Rvsdg();
410 
411  auto SetupF1 = [&]()
412  {
413  auto functionType = FunctionType::Create(
416 
417  auto lambda = jlm::rvsdg::LambdaNode::Create(
418  graph.GetRootRegion(),
420  auto theta = ThetaNode::create(lambda->subregion());
421 
422  const auto & one = IntegerConstantOperation::Create(*theta->subregion(), 32, 1);
423  AllocaOperation::create(vt, one.output(0), 4);
424 
425  return lambda->finalize(
426  { lambda->GetFunctionArguments()[0], lambda->GetFunctionArguments()[1] });
427  };
428  SetupF1();
429 
430  // Act
431  auto statistics = runInlining(rm);
432 
433  // Assert
434  EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#Functions"), 1u);
435  // f1 should not be considered inlinable, due to the alloca
436  EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#InlineableFunctions"), 0u);
437 }
static std::unique_ptr< jlm::util::Statistics > runInlining(jlm::llvm::LlvmRvsdgModule &rm)
TEST(FunctionInliningTests, testSimpleInlining)
static const auto vt
Definition: PullTests.cpp:16
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::vector< rvsdg::Output * > Create(rvsdg::Output *function, std::shared_ptr< const rvsdg::FunctionType > functionType, const std::vector< rvsdg::Output * > &arguments)
Definition: call.hpp:464
Performs function inlining on functions that are determined to be good candidates,...
Definition: inlining.hpp:25
void Run(rvsdg::RvsdgModule &module, util::StatisticsCollector &statisticsCollector) override
Perform RVSDG transformation.
Definition: inlining.cpp:508
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 std::shared_ptr< const MemoryStateType > Create()
Definition: types.cpp:379
static std::shared_ptr< const PointerType > Create()
Definition: types.cpp:45
static std::unique_ptr< llvm::ThreeAddressCode > Create(const Variable *address, const Variable *value, const Variable *state, size_t alignment)
Definition: Store.hpp:304
static std::shared_ptr< const ControlType > Create(std::size_t nalternatives)
Instantiates control type.
Definition: control.cpp:50
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 nalternatives)
Definition: gamma.hpp:161
static GraphImport & Create(Graph &graph, std::shared_ptr< const rvsdg::Type > type, std::string name)
Definition: graph.cpp:36
static LambdaNode * Create(rvsdg::Region &parent, std::unique_ptr< LambdaOperation > operation)
Definition: lambda.cpp:140
Represent acyclic RVSDG subgraphs.
Definition: region.hpp:213
Graph & Rvsdg() noexcept
Definition: RvsdgModule.hpp:57
Global memory state passed between functions.