Jlm
Loading...
Searching...
No Matches
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
20#include <jlm/rvsdg/control.hpp>
21#include <jlm/rvsdg/delta.hpp>
22#include <jlm/rvsdg/gamma.hpp>
25#include <jlm/rvsdg/theta.hpp>
26#include <jlm/rvsdg/view.hpp>
28
34static std::unique_ptr<jlm::util::Statistics>
36{
39 jlm::util::StatisticsCollector collector(settings);
40 fctinline.Run(rm, collector);
41
42 EXPECT_EQ(collector.NumCollectedStatistics(), 1u);
43 return collector.releaseStatistic(jlm::util::Statistics::Id::FunctionInlining);
44}
45
46TEST(FunctionInliningTests, testSimpleInlining)
47{
81 using namespace jlm::llvm;
82 using namespace jlm::rvsdg;
83
84 // Arrange
86 auto & graph = rm.Rvsdg();
87 auto vt = TestType::createValueType();
88 auto iOStateType = IOStateType::Create();
89 auto memoryStateType = MemoryStateType::Create();
90 auto i = &jlm::rvsdg::GraphImport::Create(graph, vt, "i");
91
92 Region * gammaRegion0 = nullptr;
93
94 auto SetupF1 = [&]()
95 {
96 auto functionType = jlm::rvsdg::FunctionType::Create(
97 { vt, IOStateType::Create(), MemoryStateType::Create() },
98 { vt, IOStateType::Create(), MemoryStateType::Create() });
99
100 auto lambda = jlm::rvsdg::LambdaNode::Create(
101 graph.GetRootRegion(),
102 LlvmLambdaOperation::Create(functionType, "f1", Linkage::externalLinkage));
103 lambda->AddContextVar(*i);
104
105 auto t = TestOperation::createNode(
106 lambda->subregion(),
107 { lambda->GetFunctionArguments()[0] },
108 { vt });
109
110 return lambda->finalize(
111 { t->output(0), lambda->GetFunctionArguments()[1], lambda->GetFunctionArguments()[2] });
112 };
113
114 auto SetupF2 = [&](jlm::rvsdg::Output * f1)
115 {
117 auto functionType = jlm::rvsdg::FunctionType::Create(
119 vt,
120 IOStateType::Create(),
121 MemoryStateType::Create() },
122 { vt, IOStateType::Create(), MemoryStateType::Create() });
123
124 auto lambda = jlm::rvsdg::LambdaNode::Create(
125 graph.GetRootRegion(),
126 LlvmLambdaOperation::Create(functionType, "f2", Linkage::externalLinkage));
127 auto d = lambda->AddContextVar(*f1).inner;
128 auto controlArgument = lambda->GetFunctionArguments()[0];
129 auto valueArgument = lambda->GetFunctionArguments()[1];
130 auto iOStateArgument = lambda->GetFunctionArguments()[2];
131 auto memoryStateArgument = lambda->GetFunctionArguments()[3];
132
133 auto gamma = jlm::rvsdg::GammaNode::create(controlArgument, 2);
134 gammaRegion0 = gamma->subregion(0);
135 auto gammaInputF1 = gamma->AddEntryVar(d);
136 auto gammaInputValue = gamma->AddEntryVar(valueArgument);
137 auto gammaInputIoState = gamma->AddEntryVar(iOStateArgument);
138 auto gammaInputMemoryState = gamma->AddEntryVar(memoryStateArgument);
139
140 auto callResults = CallOperation::Create(
141 gammaInputF1.branchArgument[0],
143 { gammaInputValue.branchArgument[0],
144 gammaInputIoState.branchArgument[0],
145 gammaInputMemoryState.branchArgument[0] });
146
147 auto gammaOutputValue =
148 gamma->AddExitVar({ callResults[0], gammaInputValue.branchArgument[1] });
149 auto gammaOutputIoState =
150 gamma->AddExitVar({ callResults[1], gammaInputIoState.branchArgument[1] });
151 auto gammaOutputMemoryState =
152 gamma->AddExitVar({ callResults[2], gammaInputMemoryState.branchArgument[1] });
153
154 return lambda->finalize(
155 { gammaOutputValue.output, gammaOutputIoState.output, gammaOutputMemoryState.output });
156 };
157
158 auto f1 = SetupF1();
159 auto f2 = SetupF2(f1);
160
161 GraphExport::Create(*f2, "f2");
162
163 // jlm::rvsdg::view(graph.GetRootRegion(), stdout);
164
165 // Act
166 auto statistics = runInlining(rm);
167
168 // jlm::rvsdg::view(graph.GetRootRegion(), stdout);
169
170 // Assert
171 // Check that the call has been replaced by the test operation inside f1
172 EXPECT_FALSE(Region::containsOperation<CallOperation>(graph.GetRootRegion(), true));
173 EXPECT_TRUE(Region::containsOperation<TestOperation>(*gammaRegion0, true));
174
175 // Check that the statistics match what we expect. f2 is technically inlineable
176 EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#Functions"), 2u);
177 EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#InlineableFunctions"), 2u);
178 EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#FunctionCalls"), 1u);
179 EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#InlinableCalls"), 1u);
180 EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#CallsInlined"), 1u);
181}
182
183TEST(FunctionInliningTests, testInliningWithAlloca)
184{
220 using namespace jlm::llvm;
221 using namespace jlm::rvsdg;
222
223 // Arrange
225 auto & graph = rm.Rvsdg();
226 auto vt = TestType::createValueType();
227 auto iOStateType = IOStateType::Create();
228 auto memoryStateType = MemoryStateType::Create();
229 auto i = &jlm::rvsdg::GraphImport::Create(graph, vt, "i");
230
231 Region * gammaRegion0 = nullptr;
232 Region * f2Region = nullptr;
233
234 auto SetupF1 = [&]()
235 {
236 auto functionType = jlm::rvsdg::FunctionType::Create(
237 { vt, IOStateType::Create(), MemoryStateType::Create() },
238 { IOStateType::Create(), MemoryStateType::Create() });
239
240 auto lambda = jlm::rvsdg::LambdaNode::Create(
241 graph.GetRootRegion(),
242 LlvmLambdaOperation::Create(functionType, "f1", Linkage::externalLinkage));
243 lambda->AddContextVar(*i);
244 auto lambdaArgs = lambda->GetFunctionArguments();
245
246 const auto & one = IntegerConstantOperation::Create(*lambda->subregion(), 32, 1);
247 auto alloca = AllocaOperation::create(vt, one.output(0), 4);
248 auto store = StoreNonVolatileOperation::Create(alloca[0], lambdaArgs[0], { lambdaArgs[2] }, 4);
249
250 return lambda->finalize({ lambdaArgs[1], store[0] });
251 };
252
253 auto SetupF2 = [&](jlm::rvsdg::Output * f1)
254 {
256 auto functionType = jlm::rvsdg::FunctionType::Create(
258 vt,
259 IOStateType::Create(),
260 MemoryStateType::Create() },
261 { IOStateType::Create(), MemoryStateType::Create() });
262
263 auto lambda = jlm::rvsdg::LambdaNode::Create(
264 graph.GetRootRegion(),
265 LlvmLambdaOperation::Create(functionType, "f2", Linkage::externalLinkage));
266 auto d = lambda->AddContextVar(*f1).inner;
267 auto controlArgument = lambda->GetFunctionArguments()[0];
268 auto valueArgument = lambda->GetFunctionArguments()[1];
269 auto iOStateArgument = lambda->GetFunctionArguments()[2];
270 auto memoryStateArgument = lambda->GetFunctionArguments()[3];
271 f2Region = lambda->subregion();
272
273 auto gamma = jlm::rvsdg::GammaNode::create(controlArgument, 2);
274 gammaRegion0 = gamma->subregion(0);
275 auto gammaInputF1 = gamma->AddEntryVar(d);
276 auto gammaInputValue = gamma->AddEntryVar(valueArgument);
277 auto gammaInputIoState = gamma->AddEntryVar(iOStateArgument);
278 auto gammaInputMemoryState = gamma->AddEntryVar(memoryStateArgument);
279
280 auto callResults = CallOperation::Create(
281 gammaInputF1.branchArgument[0],
283 { gammaInputValue.branchArgument[0],
284 gammaInputIoState.branchArgument[0],
285 gammaInputMemoryState.branchArgument[0] });
286
287 auto gammaOutputIoState =
288 gamma->AddExitVar({ callResults[0], gammaInputIoState.branchArgument[1] });
289 auto gammaOutputMemoryState =
290 gamma->AddExitVar({ callResults[1], gammaInputMemoryState.branchArgument[1] });
291
292 return lambda->finalize({ gammaOutputIoState.output, gammaOutputMemoryState.output });
293 };
294
295 auto f1 = SetupF1();
296 auto f2 = SetupF2(f1);
297
298 GraphExport::Create(*f2, "f2");
299
300 // jlm::rvsdg::view(graph.GetRootRegion(), stdout);
301
302 // Act
303 runInlining(rm);
304
305 // jlm::rvsdg::view(&graph.GetRootRegion(), stdout);
306
307 // Assert
308 // Check that the call is gone
309 EXPECT_FALSE(Region::containsOperation<CallOperation>(graph.GetRootRegion(), true));
310 // A store should have taken its place in the gamma subregion
311 EXPECT_TRUE(Region::containsOperation<StoreNonVolatileOperation>(*gammaRegion0, true));
312 // Check that the alloca operation is not inside the gamma subregion
313 EXPECT_FALSE(Region::containsOperation<AllocaOperation>(*gammaRegion0, true));
314 // The alloca should have been moved to the top level of f2
315 EXPECT_TRUE(Region::containsOperation<AllocaOperation>(*f2Region, false));
316}
317
318TEST(FunctionInliningTests, testIndirectCall)
319{
326 using namespace jlm::llvm;
327 using namespace jlm::rvsdg;
328
329 // Arrange
330 auto vt = TestType::createValueType();
331 auto iOStateType = IOStateType::Create();
332 auto memoryStateType = MemoryStateType::Create();
333
334 auto functionType1 = jlm::rvsdg::FunctionType::Create(
335 { vt, IOStateType::Create(), MemoryStateType::Create() },
336 { IOStateType::Create(), MemoryStateType::Create() });
337 auto pt = PointerType::Create();
338
339 auto functionType2 = jlm::rvsdg::FunctionType::Create(
340 { PointerType::Create(), IOStateType::Create(), MemoryStateType::Create() },
341 { IOStateType::Create(), MemoryStateType::Create() });
342
344 auto & graph = rm.Rvsdg();
345 auto i = &jlm::rvsdg::GraphImport::Create(graph, functionType2, "i");
346
347 auto SetupF1 = [&](const std::shared_ptr<const jlm::rvsdg::FunctionType> & functionType)
348 {
349 auto lambda = jlm::rvsdg::LambdaNode::Create(
350 graph.GetRootRegion(),
351 LlvmLambdaOperation::Create(functionType, "f1", Linkage::externalLinkage));
352 return lambda->finalize(
353 { lambda->GetFunctionArguments()[1], lambda->GetFunctionArguments()[2] });
354 };
355
356 auto SetupF2 = [&](jlm::rvsdg::Output * f1)
357 {
358 auto functionType = jlm::rvsdg::FunctionType::Create(
359 { IOStateType::Create(), MemoryStateType::Create() },
360 { IOStateType::Create(), MemoryStateType::Create() });
361
362 auto lambda = jlm::rvsdg::LambdaNode::Create(
363 graph.GetRootRegion(),
364 LlvmLambdaOperation::Create(functionType, "f2", Linkage::externalLinkage));
365 auto cvi = lambda->AddContextVar(*i).inner;
366 auto cvf1 = lambda->AddContextVar(*f1).inner;
367 auto iOStateArgument = lambda->GetFunctionArguments()[0];
368 auto memoryStateArgument = lambda->GetFunctionArguments()[1];
369
370 auto callResults =
371 CallOperation::Create(cvi, functionType2, { cvf1, iOStateArgument, memoryStateArgument });
372
373 return lambda->finalize(callResults);
374 };
375
376 auto f1 = SetupF1(functionType1);
377 auto f2 = SetupF2(
378 jlm::rvsdg::CreateOpNode<FunctionToPointerOperation>({ f1 }, functionType1).output(0));
379
380 GraphExport::Create(*f2, "f2");
381
382 // jlm::rvsdg::view(&graph.GetRootRegion(), stdout);
383
384 // Act
385 auto statistics = runInlining(rm);
386
387 // jlm::rvsdg::view(&graph.GetRootRegion(), stdout);
388
389 // Assert
390 // No inlining happens in this test, but both f1 and f2 are technically possible to inline
391 EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#Functions"), 2u);
392 EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#InlineableFunctions"), 2u);
393 EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#FunctionCalls"), 1u);
394 EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#InlinableCalls"), 0u);
395 EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#CallsInlined"), 0u);
396}
397
402TEST(FunctionInliningTests, testFunctionWithDisqualifyingAlloca)
403{
404 using namespace jlm::llvm;
405 using namespace jlm::rvsdg;
406
407 // Arrange
408 auto vt = TestType::createValueType();
409 auto iOStateType = IOStateType::Create();
410 auto memoryStateType = MemoryStateType::Create();
411
413 auto & graph = rm.Rvsdg();
414
415 auto SetupF1 = [&]()
416 {
417 auto functionType = FunctionType::Create(
418 { IOStateType::Create(), MemoryStateType::Create() },
419 { IOStateType::Create(), MemoryStateType::Create() });
420
421 auto lambda = jlm::rvsdg::LambdaNode::Create(
422 graph.GetRootRegion(),
423 LlvmLambdaOperation::Create(functionType, "f1", Linkage::externalLinkage));
424 auto theta = ThetaNode::create(lambda->subregion());
425
426 const auto & one = IntegerConstantOperation::Create(*theta->subregion(), 32, 1);
427 AllocaOperation::create(vt, one.output(0), 4);
428
429 return lambda->finalize(
430 { lambda->GetFunctionArguments()[0], lambda->GetFunctionArguments()[1] });
431 };
432 SetupF1();
433
434 // Act
435 auto statistics = runInlining(rm);
436
437 // Assert
438 EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#Functions"), 1u);
439 // f1 should not be considered inlinable, due to the alloca
440 EXPECT_EQ(statistics->GetMeasurementValue<uint64_t>("#InlineableFunctions"), 0u);
441}
442
443TEST(FunctionInliningTests, testIOBarrierInsertion)
444{
445 using namespace jlm::llvm;
446 using namespace jlm::rvsdg;
447
448 // Arrange
449 LlvmRvsdgModule rvsdgModule(jlm::util::FilePath(""), "", "");
450 auto & graph = rvsdgModule.Rvsdg();
451
452 auto vt = TestType::createValueType();
453 auto i32Type = BitType::Create(32);
454 auto pointerType = PointerType::Create();
455 auto iOStateType = IOStateType::Create();
456 auto memoryStateType = MemoryStateType::Create();
457
458 auto SetupD1 = [&]()
459 {
460 auto deltaNode = DeltaNode::Create(
461 &graph.GetRootRegion(),
462 LlvmDeltaOperation::Create(i32Type, "delta", Linkage::externalLinkage, "", true, 4));
463 auto & constant = IntegerConstantOperation::Create(*deltaNode->subregion(), 32, 4);
464 deltaNode->finalize(constant.output(0));
465 return deltaNode;
466 };
467
468 auto SetupF1 = [&](Output * d1)
469 {
470 auto functionType = FunctionType::Create(
471 { pointerType, IOStateType::Create(), MemoryStateType::Create() },
472 { i32Type, i32Type, IOStateType::Create(), MemoryStateType::Create() });
473
474 auto lambda = LambdaNode::Create(
475 graph.GetRootRegion(),
476 LlvmLambdaOperation::Create(functionType, "f1", Linkage::externalLinkage));
477 auto ctxVar = lambda->AddContextVar(*d1);
478 auto ptrArgument = lambda->GetFunctionArguments()[0];
479 auto ioStateArgument = lambda->GetFunctionArguments()[1];
480 auto memoryStateArgument = lambda->GetFunctionArguments()[2];
481
482 auto & loadNode1 =
483 LoadNonVolatileOperation::CreateNode(*ptrArgument, { memoryStateArgument }, i32Type, 4);
484
485 auto & loadNode2 =
486 LoadNonVolatileOperation::CreateNode(*ctxVar.inner, { loadNode1.output(1) }, i32Type, 4);
487
488 return lambda->finalize(
489 { loadNode1.output(0), loadNode2.output(0), ioStateArgument, loadNode2.output(1) });
490 };
491
492 auto SetupF2 = [&](Output * f1)
493 {
494 auto ct = ControlType::Create(2);
495 auto functionType = FunctionType::Create(
496 { pointerType, IOStateType::Create(), MemoryStateType::Create() },
497 { i32Type, i32Type, IOStateType::Create(), MemoryStateType::Create() });
498
499 auto lambda = LambdaNode::Create(
500 graph.GetRootRegion(),
501 LlvmLambdaOperation::Create(functionType, "f2", Linkage::externalLinkage));
502 auto ctxVar = lambda->AddContextVar(*f1);
503 auto ptrArgument = lambda->GetFunctionArguments()[0];
504 auto iOStateArgument = lambda->GetFunctionArguments()[1];
505 auto memoryStateArgument = lambda->GetFunctionArguments()[2];
506
507 auto callResults = CallOperation::Create(
508 ctxVar.inner,
509 jlm::rvsdg::AssertGetOwnerNode<LambdaNode>(*f1).GetOperation().Type(),
510 { ptrArgument, iOStateArgument, memoryStateArgument });
511
512 lambda->finalize({ callResults[0], callResults[1], callResults[2], callResults[3] });
513 return lambda;
514 };
515
516 auto d1 = SetupD1();
517 auto f1 = SetupF1(&d1->output());
518 auto f2 = SetupF2(f1);
519
520 GraphExport::Create(*f2->output(), "f2");
521
522 // Act
523 auto statistics = runInlining(rvsdgModule);
524
525 // Assert
526 // Check that the call has been replaced by the test operation inside f1
527 EXPECT_FALSE(Region::containsOperation<CallOperation>(graph.GetRootRegion(), true));
528
529 // We expect a two load and IOBarrier nodes
530 EXPECT_EQ(f2->subregion()->numNodes(), 4);
531
532 {
533 auto [loadNode, loadOp] = TryGetSimpleNodeAndOptionalOp<LoadNonVolatileOperation>(
534 *f2->GetFunctionResults()[0]->origin());
535 EXPECT_NE(loadOp, nullptr);
536
537 auto [ioBarrierNode, ioBarrierOp] = TryGetSimpleNodeAndOptionalOp<IOBarrierOperation>(
538 *LoadOperation::AddressInput(*loadNode).origin());
539 EXPECT_NE(ioBarrierOp, nullptr);
540 EXPECT_EQ(ioBarrierNode->input(1)->origin(), f2->GetFunctionArguments()[1]);
541 }
542
543 {
544 auto [loadNode, loadOp] = TryGetSimpleNodeAndOptionalOp<LoadNonVolatileOperation>(
545 *f2->GetFunctionResults()[1]->origin());
546 EXPECT_NE(loadOp, nullptr);
547
548 auto [ioBarrierNode, ioBarrierOp] = TryGetSimpleNodeAndOptionalOp<IOBarrierOperation>(
549 *LoadOperation::AddressInput(*loadNode).origin());
550 EXPECT_NE(ioBarrierOp, nullptr);
551 EXPECT_EQ(ioBarrierNode->input(1)->origin(), f2->GetFunctionArguments()[1]);
552 }
553}
static std::unique_ptr< jlm::util::Statistics > runInlining(jlm::llvm::LlvmRvsdgModule &rm)
TEST(FunctionInliningTests, testSimpleInlining)
static const auto vt
Definition PullTests.cpp:16
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:521
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:141
Represent acyclic RVSDG subgraphs.
Definition region.hpp:213
Graph & Rvsdg() noexcept
Global memory state passed between functions.
NodeType * TryGetOwnerNode(const rvsdg::Input &input) noexcept
Checks if this is an input to a node of specified type.
Definition node.hpp:872