Jlm
Loading...
Searching...
No Matches
RvsdgRoundtripTests.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2026 Magnus Sjalander <work@sjalander.com>
3 * See COPYING for terms of redistribution.
4 */
5
6#include <gtest/gtest.h>
7#include <queue>
8
18
19namespace
20{
21
22using namespace jlm::llvm;
23using namespace jlm::rvsdg;
24using namespace jlm::util;
25
26void
27CompareNodes(const Node & node1, const Node & node2);
28
29void
31
35static void
36CompareTypes(const Type & type1, const Type & type2)
37{
38 // If types are equal, return immediately.
39 if (type1 == type2)
40 return;
41
42 if (auto * bitType1 = dynamic_cast<const BitType *>(&type1))
43 {
45 ASSERT_EQ(bitType1->nbits(), bitType2->nbits())
46 << "CompareTypes: BitType nbits mismatch: '" << type1.debug_string() << "' vs '"
47 << type2.debug_string() << "'";
48 return;
49 }
50
51 if (auto * structType1 = dynamic_cast<const StructType *>(&type1))
52 {
54
55 ASSERT_EQ(structType1->numElements(), structType2->numElements())
56 << "CompareTypes: StructType element count mismatch: '" << type1.debug_string() << "' vs '"
57 << type2.debug_string() << "'";
58 for (size_t i = 0; i < structType1->numElements(); ++i)
59 {
60 CompareTypes(*structType1->getElementType(i), *structType2->getElementType(i));
61 }
62 ASSERT_EQ(structType1->IsPacked(), structType2->IsPacked())
63 << "CompareTypes: StructType packed mismatch: '" << type1.debug_string() << "' vs '"
64 << type2.debug_string() << "'";
65 return;
66 }
67
68 if (auto * arrayType1 = dynamic_cast<const ArrayType *>(&type1))
69 {
71 CompareTypes(arrayType1->element_type(), arrayType2->element_type());
72 ASSERT_EQ(arrayType1->nelements(), arrayType2->nelements())
73 << "CompareTypes: ArrayType element count mismatch: '" << type1.debug_string() << "' vs '"
74 << type2.debug_string() << "'";
75 return;
76 }
77
78 if (auto * fnType1 = dynamic_cast<const FunctionType *>(&type1))
79 {
81 ASSERT_EQ(fnType1->NumArguments(), fnType2->NumArguments())
82 << "CompareTypes: FunctionType argument count mismatch: '" << type1.debug_string()
83 << "' vs '" << type2.debug_string() << "'";
84 ASSERT_EQ(fnType1->NumResults(), fnType2->NumResults())
85 << "CompareTypes: FunctionType result count mismatch: '" << type1.debug_string() << "' vs '"
86 << type2.debug_string() << "'";
87 for (size_t i = 0; i < fnType1->NumArguments(); ++i)
88 {
89 CompareTypes(fnType1->ArgumentType(i), fnType2->ArgumentType(i));
90 }
91 for (size_t i = 0; i < fnType1->NumResults(); ++i)
92 {
93 CompareTypes(fnType1->ResultType(i), fnType2->ResultType(i));
94 }
95 return;
96 }
97
98 // Fallback to regular equality for any remaining types
99 ASSERT_EQ(type1, type2) << "CompareTypes: Type mismatch - expected '" << type1.debug_string()
100 << "' but got '" << type2.debug_string() << "'";
101}
102
110static void
112{
113 if (op1 == op2)
114 return;
115
116 // Handle operations whose operator== relies on pointer identity,
117 // which would incorrectly report them as unequal.
118
119 if (auto * alloca1 = dynamic_cast<const AllocaOperation *>(&op1))
120 {
122 CompareTypes(*alloca1->allocatedType(), *alloca2->allocatedType());
123 ASSERT_EQ(alloca1->alignment(), alloca2->alignment())
124 << "CompareOperations: Alloca alignment mismatch: '" << op1.debug_string() << "' vs '"
125 << op2.debug_string() << "'";
126 return;
127 }
128
129 if (auto * malloc1 = dynamic_cast<const MallocOperation *>(&op1))
130 {
132 CompareTypes(malloc1->getSizeType(), malloc2->getSizeType());
133 return;
134 }
135
136 if (auto * free1 = dynamic_cast<const FreeOperation *>(&op1))
137 {
139 ASSERT_EQ(free1->narguments(), free2->narguments())
140 << "CompareOperations: Free argument count mismatch: '" << op1.debug_string() << "' vs '"
141 << op2.debug_string() << "'";
142 return;
143 }
144
145 if (auto * constDataArr1 = dynamic_cast<const ConstantDataArrayOperation *>(&op1))
146 {
148 CompareTypes(*constDataArr1->result(0), *constDataArr2->result(0));
149 return;
150 }
151
152 if (auto * constArr1 = dynamic_cast<const ConstantArrayOperation *>(&op1))
153 {
155 CompareTypes(*constArr1->result(0), *constArr2->result(0));
156 return;
157 }
158
159 if (auto * constAggZero1 = dynamic_cast<const ConstantAggregateZeroOperation *>(&op1))
160 {
162 CompareTypes(*constAggZero1->result(0), *constAggZero2->result(0));
163 return;
164 }
165
166 if (auto * constStruct1 = dynamic_cast<const ConstantStructOperation *>(&op1))
167 {
169 CompareTypes(*constStruct1->result(0), *constStruct2->result(0));
170 return;
171 }
172
173 if (auto * call1 = dynamic_cast<const CallOperation *>(&op1))
174 {
176 CompareTypes(*call1->GetFunctionType(), *call2->GetFunctionType());
177 return;
178 }
179
180 if (auto * memcpy1 = dynamic_cast<const jlm::llvm::MemCpyNonVolatileOperation *>(&op1))
181 {
183 CompareTypes(memcpy1->LengthType(), memcpy2->LengthType());
184 ASSERT_EQ(memcpy1->NumMemoryStates(), memcpy2->NumMemoryStates())
185 << "CompareOperations: MemCpyNonVolatile memory state count mismatch: '"
186 << op1.debug_string() << "' vs '" << op2.debug_string() << "'";
187 return;
188 }
189
190 if (auto * vmemcpy1 = dynamic_cast<const jlm::llvm::MemCpyVolatileOperation *>(&op1))
191 {
193 CompareTypes(vmemcpy1->LengthType(), vmemcpy2->LengthType());
194 ASSERT_EQ(vmemcpy1->NumMemoryStates(), vmemcpy2->NumMemoryStates())
195 << "CompareOperations: MemCpyVolatile memory state count mismatch: '" << op1.debug_string()
196 << "' vs '" << op2.debug_string() << "'";
197 return;
198 }
199
200 FAIL() << "CompareOperations: Unknown operation comparison: " << op1.debug_string() << " vs "
201 << op2.debug_string();
202}
203
210static void
212{
213 // Verify input types match
214 CompareTypes(*lv1.input->Type(), *lv2.input->Type());
215
216 // Verify pre (loop variable value before iteration) types match
217 ASSERT_NE(lv1.pre, nullptr) << "CompareThetaLoopVars: Theta LoopVar.pre is null in graph 1";
218 ASSERT_NE(lv2.pre, nullptr) << "CompareThetaLoopVars: Theta LoopVar.pre is null in graph 2";
219 CompareTypes(*lv1.pre->Type(), *lv2.pre->Type());
220
221 // Verify post (loop variable value after iteration) types match
222 ASSERT_NE(lv1.post, nullptr) << "CompareThetaLoopVars: Theta LoopVar.post is null in graph 1";
223 ASSERT_NE(lv2.post, nullptr) << "CompareThetaLoopVars: Theta LoopVar.post is null in graph 2";
224 CompareTypes(*lv1.post->Type(), *lv2.post->Type());
225
226 // Verify output (final value at loop exit) types match
227 ASSERT_NE(lv1.output, nullptr) << "CompareThetaLoopVars: Theta LoopVar.output is null in graph 1";
228 ASSERT_NE(lv2.output, nullptr) << "CompareThetaLoopVars: Theta LoopVar.output is null in graph 2";
229 CompareTypes(*lv1.output->Type(), *lv2.output->Type());
230}
231
238static void
240{
241 ASSERT_EQ(ev1.branchResult.size(), ev2.branchResult.size())
242 << "CompareGammaExitVars: Gamma ExitVar branchResult count mismatch";
243
244 for (size_t i = 0; i < ev1.branchResult.size(); ++i)
245 {
246 CompareTypes(*ev1.branchResult[i]->Type(), *ev2.branchResult[i]->Type());
247 }
248
249 // Verify output linkage integrity
250 ASSERT_NE(ev1.output, nullptr) << "CompareGammaExitVars: Gamma ExitVar.output is null in graph 1";
251 ASSERT_NE(ev2.output, nullptr) << "CompareGammaExitVars: Gamma ExitVar.output is null in graph 2";
252 CompareTypes(*ev1.output->Type(), *ev2.output->Type());
253}
254
261static void
263{
264 // Verify recref (recursive reference to self/other fixpoint) types match
265 ASSERT_NE(fv1.recref, nullptr) << "ComparePhiFixVars: Phi FixVar.recref is null in graph 1";
266 ASSERT_NE(fv2.recref, nullptr) << "ComparePhiFixVars: Phi FixVar.recref is null in graph 2";
267 // Recreftype comparison: the recref is a region argument of the phi subregion.
268 // It doesn't have an origin() because it IS the value source for recursive calls.
269 CompareTypes(*fv1.recref->Type(), *fv2.recref->Type());
270
271 // Verify result (definition from phi region) types match
272 ASSERT_NE(fv1.result, nullptr) << "ComparePhiFixVars: Phi FixVar.result is null in graph 1";
273 ASSERT_NE(fv2.result, nullptr) << "ComparePhiFixVars: Phi FixVar.result is null in graph 2";
274 CompareTypes(*fv1.result->Type(), *fv2.result->Type());
275
276 // Verify output (external reference to fixpoint value) types match
277 ASSERT_NE(fv1.output, nullptr) << "ComparePhiFixVars: Phi FixVar.output is null in graph 1";
278 ASSERT_NE(fv2.output, nullptr) << "ComparePhiFixVars: Phi FixVar.output is null in graph 2";
279 CompareTypes(*fv1.output->Type(), *fv2.output->Type());
280}
281
285void
286CompareNodes(const Node & node1, const Node & node2)
287{
288 if (auto * snode1 = dynamic_cast<const StructuralNode *>(&node1))
289 {
291
292 CompareOperations(snode1->GetOperation(), snode2->GetOperation());
293 ASSERT_EQ(snode1->nsubregions(), snode2->nsubregions())
294 << "CompareNodes: StructuralNode subregion count mismatch";
295
296 for (size_t r = 0; r < snode1->nsubregions(); ++r)
297 {
298 CompareRegions(*snode1->subregion(r), *snode2->subregion(r));
299 }
300
301 ASSERT_EQ(snode1->ninputs(), snode2->ninputs())
302 << "CompareNodes: Structural node input count mismatch";
303 for (size_t i = 0; i < snode1->ninputs(); ++i)
304 {
305 CompareTypes(*snode1->input(i)->Type(), *snode2->input(i)->Type());
306 }
307
308 ASSERT_EQ(snode1->noutputs(), snode2->noutputs())
309 << "CompareNodes: Structural node output count mismatch";
310 for (size_t i = 0; i < snode1->noutputs(); ++i)
311 {
312 CompareTypes(*snode1->output(i)->Type(), *snode2->output(i)->Type());
313 }
314
315 // Theta-specific: verify loop variable count and compare each var
316 if (auto * theta1 = dynamic_cast<const ThetaNode *>(&node1))
317 {
319
320 auto lvList1 = theta1->GetLoopVars();
321 auto lvList2 = theta2->GetLoopVars();
322
323 ASSERT_EQ(
324 std::distance(lvList1.begin(), lvList1.end()),
325 std::distance(lvList2.begin(), lvList2.end()))
326 << "CompareNodes: Theta node loop variable count mismatch";
327
328 auto it1 = lvList1.begin(), it2 = lvList2.begin();
329 while (it1 != lvList1.end() && it2 != lvList2.end())
330 {
332 ++it1;
333 ++it2;
334 }
335 }
336
337 // Gamma-specific: verify exit variable count and compare each var
338 if (auto * gamma1 = dynamic_cast<const GammaNode *>(&node1))
339 {
341
342 auto evList1 = gamma1->GetExitVars();
343 auto evList2 = gamma2->GetExitVars();
344
345 ASSERT_EQ(
346 std::distance(evList1.begin(), evList1.end()),
347 std::distance(evList2.begin(), evList2.end()))
348 << "CompareNodes: Gamma node exit variable count mismatch";
349
350 auto it1 = evList1.begin(), it2 = evList2.begin();
351 while (it1 != evList1.end() && it2 != evList2.end())
352 {
354 ++it1;
355 ++it2;
356 }
357 }
358
359 // PhiNode-specific: verify fixpoint variable count and compare each var
360 if (auto * phi1 = dynamic_cast<const PhiNode *>(&node1))
361 {
363
364 auto fvList1 = phi1->GetFixVars();
365 auto fvList2 = phi2->GetFixVars();
366
367 ASSERT_EQ(
368 std::distance(fvList1.begin(), fvList1.end()),
369 std::distance(fvList2.begin(), fvList2.end()))
370 << "CompareNodes: Phi node fixpoint variable count mismatch";
371
372 auto it1 = fvList1.begin(), it2 = fvList2.begin();
373 while (it1 != fvList1.end() && it2 != fvList2.end())
374 {
376 ++it1;
377 ++it2;
378 }
379 }
380 return;
381 }
382
383 if (auto * simp1 = dynamic_cast<const SimpleNode *>(&node1))
384 {
386
387 CompareOperations(simp1->GetOperation(), simp2->GetOperation());
388
389 ASSERT_EQ(simp1->ninputs(), simp2->ninputs())
390 << "CompareNodes: Simple node input count mismatch";
391 for (size_t i = 0; i < simp1->ninputs(); ++i)
392 {
393 CompareTypes(*simp1->input(i)->Type(), *simp2->input(i)->Type());
394 }
395
396 ASSERT_EQ(simp1->noutputs(), simp2->noutputs())
397 << "CompareNodes: Simple node output count mismatch";
398 for (size_t i = 0; i < simp1->noutputs(); ++i)
399 {
400 CompareTypes(*simp1->output(i)->Type(), *simp2->output(i)->Type());
401 }
402 return;
403 }
404 ADD_FAILURE() << "CompareNodes: Could not identify node type";
405}
406
410template<typename CvList1, typename CvList2>
411static void
413 const CvList1 & cvList1,
414 const CvList2 & cvList2,
415 std::vector<std::pair<const Node *, const Node *>> & origins)
416{
417 auto it1 = cvList1.begin(), it2 = cvList2.begin();
418 while (it1 != cvList1.end() && it2 != cvList2.end())
419 {
420 if (auto * origin1 = TryGetOwnerNode<Node>(*it1->input->origin()))
421 {
422 auto * origin2 = TryGetOwnerNode<Node>(*it2->input->origin());
423 if (origin2)
424 origins.push_back({ origin1, origin2 });
425 }
426 ++it1;
427 ++it2;
428 }
429
430 EXPECT_EQ(
431 std::distance(cvList1.begin(), cvList1.end()),
432 std::distance(cvList2.begin(), cvList2.end()))
433 << "CollectOriginsFromCvPairs: ContextVar count mismatch";
434}
435
444static std::vector<std::pair<const Node *, const Node *>>
446{
447 std::vector<std::pair<const Node *, const Node *>> origins;
448
449 if (auto * lambda1 = dynamic_cast<const LambdaNode *>(&node1))
450 {
452 CollectOriginsFromCvPairs(lambda1->GetContextVars(), lambda2->GetContextVars(), origins);
453 }
454 else if (auto * delta1 = dynamic_cast<const DeltaNode *>(&node1))
455 {
457 CollectOriginsFromCvPairs(delta1->GetContextVars(), delta2->GetContextVars(), origins);
458 }
459 else if (auto * phi1 = dynamic_cast<const PhiNode *>(&node1))
460 {
462 CollectOriginsFromCvPairs(phi1->GetContextVars(), phi2->GetContextVars(), origins);
463 }
464
465 return origins;
466}
467
475static void
477 const Node & node1,
478 const Node & node2,
479 std::unordered_set<const Node *> & visited,
480 std::queue<std::pair<const Node *, const Node *>> & nodeQueue)
481{
482 // Context variable origins (LambdaNode, DeltaNode, PhiNode)
484
485 for (auto [origin1, origin2] : cvOrigins)
486 {
487 if (!visited.count(origin1))
488 {
489 visited.insert(origin1);
491 nodeQueue.push({ origin1, origin2 });
492
493 // Recursively expand from this origin's context variables too.
494 // This handles cases like: Lambda A has context var from Lambda B,
495 // which itself has context vars we need to visit.
497 }
498 }
499}
500
508void
510{
511 ASSERT_EQ(region1.narguments(), region2.narguments())
512 << "CompareRegions: Region number of arguments mismatch";
513 for (size_t i = 0; i < region1.narguments(); ++i)
514 {
515 auto * arg1 = region1.argument(i);
516 auto * arg2 = region2.argument(i);
517 CompareTypes(*arg1->Type(), *arg2->Type());
518 }
519
520 ASSERT_EQ(region1.nresults(), region2.nresults())
521 << "CompareRegions: Region number of results mismatch";
522 for (size_t i = 0; i < region1.nresults(); ++i)
523 {
524 CompareTypes(*region1.result(i)->Type(), *region2.result(i)->Type());
525 }
526
527 ASSERT_EQ(region1.numNodes(), region2.numNodes()) << "CompareRegions: Number of nodes mismatch.";
528
529 std::unordered_set<const Node *> visited;
530 std::queue<std::pair<const Node *, const Node *>> nodeQueue;
531
532 // Seed from each region result and find the node that produces it
533 for (size_t i = 0; i < region1.nresults(); ++i)
534 {
535 auto * origin1 = region1.result(i)->origin();
536 auto * origin2 = region2.result(i)->origin();
537
538 ASSERT_TRUE(origin1 && origin2) << "CompareRegions: Result origin is null at index " << i;
539
540 CompareTypes(*origin1->Type(), *origin2->Type());
541
542 if (auto * node1 = TryGetOwnerNode<Node>(*origin1))
543 {
545 ASSERT_NE(node2, nullptr) << "CompareRegions: Origin1 is a node but Origin2 is not";
546 // Seed BFS from this node pair. The node itself is part of the region, so it has to be
547 // compared here: the BFS below only reaches the nodes that produce this node's inputs.
548 if (!visited.count(node1))
549 {
550 visited.insert(node1);
551 nodeQueue.push({ node1, node2 });
554 }
555 }
556 else if (auto * arg1 = dynamic_cast<RegionArgument *>(origin1))
557 {
559 CompareTypes(*arg1->Type(), *arg2->Type());
560 }
561 else
562 {
563 JLM_UNREACHABLE("This should not happen");
564 }
565 }
566
567 // BFS traversal - follow inputs backwards through the graph
568 while (!nodeQueue.empty())
569 {
570 auto * node1 = nodeQueue.front().first;
571 auto * node2 = nodeQueue.front().second;
572 nodeQueue.pop();
573
574 for (size_t j = 0; j < node1->ninputs(); ++j)
575 {
576 auto * origin1 = node1->input(j)->origin();
577 auto * origin2 = node2->input(j)->origin();
578
579 ASSERT_TRUE(origin1 && origin2) << "CompareRegions: Input origin is null at index " << j;
580
581 if (auto * next1 = TryGetOwnerNode<Node>(*origin1))
582 {
584 ASSERT_NE(next2, nullptr) << "CompareRegions: Input j Origin1 is a node but Origin2 is not";
585
586 if (!visited.count(next1))
587 {
588 visited.insert(next1);
589 nodeQueue.push({ next1, next2 });
592 }
593 }
594 else if (auto * arg1 = dynamic_cast<RegionArgument *>(origin1))
595 {
597 CompareTypes(*arg1->Type(), *arg2->Type());
598 }
599 else
600 {
601 JLM_UNREACHABLE("This should not happen");
602 }
603 }
604 }
605}
606
612void
614{
615 CompareRegions(module1.Rvsdg().GetRootRegion(), module2.Rvsdg().GetRootRegion());
616
617 // Compare root region exports
618 auto & rootRegion = module1.Rvsdg().GetRootRegion();
619 auto & rootRegion2 = module2.Rvsdg().GetRootRegion();
620 for (size_t i = 0; i < rootRegion.nresults(); ++i)
621 {
624
625 ASSERT_STREQ(exp1->Name().c_str(), exp2->Name().c_str())
626 << "CompareModules: Export name mismatch at index " << i;
627 }
628}
629
652void
654{
655 using namespace jlm::mlir;
656
659
660 std::unique_ptr<mlir::Block> rootBlock = std::make_unique<mlir::Block>();
661 rootBlock->push_back(omega);
662
663 auto roundTripModule = MlirToJlmConverter::CreateAndConvert(rootBlock);
664
665 ASSERT_NE(roundTripModule, nullptr)
666 << "TestRvsdgRoundtrip: MLIR-to-JLM conversion produced no module";
667
669 {
672 }
673
675}
676
677} // namespace
678
679// ============================================================================
680// Roundtrip tests from RVSDG graphs defined in jlm/llvm/TestRvsdgs.cpp
681// ============================================================================
682
683#define ROUNDTRIP_TEST(Name, Fixture) \
684 TEST(RvsdgRoundtripTests, Name) \
685 { \
686 Fixture test; \
687 TestRvsdgRoundtrip(test.module()); \
688 }
689
690// Roundtrip tests for graphs whose constant GEP indices the conversion reconstructs as duplicate
691// constant nodes, which must be merged before the sides are compared.
692#define ROUNDTRIP_TEST_CNE(Name, Fixture) \
693 TEST(RvsdgRoundtripTests, Name) \
694 { \
695 Fixture test; \
696 TestRvsdgRoundtrip(test.module(), true); \
697 }
698
730
731// The constant indices of their GEPs are reconstructed as nodes that duplicate ones already
732// present.
735
736// ============================================================================
737// Roundtrip tests from MLIR-specific RVSDG graphs defined in
738// jlm/mlir/TestRvsdgs.cpp
739// ============================================================================
740
755
756// NAllocaNodesTest is parameterized by the number of allocas, so it cannot use the
757// default-constructing ROUNDTRIP_TEST macro.
#define ROUNDTRIP_TEST_CNE(Name, Fixture)
#define ROUNDTRIP_TEST(Name, Fixture)
RVSDG module with one of each memory node type.
BitCastTest class.
Bits2PtrTest class.
Call operation class.
Definition call.hpp:251
CallTest1 class.
CallTest2 class.
Common Node Elimination Discovers simple nodes, region arguments and structural node outputs that are...
void Run(rvsdg::RvsdgModule &module, util::StatisticsCollector &statisticsCollector) override
Perform RVSDG transformation.
ConstantPointerNullTest class.
DeltaTest1 class.
DeltaTest2 class.
DeltaTest3 class.
EscapedMemoryTest1 class.
EscapedMemoryTest2 class.
EscapedMemoryTest3 class.
RVSDG module with a static function escaping through another function.
ExternalMemoryTest class.
RVSDG module with a call to free(NULL).
GammaTest2 class.
GammaTest class.
GetElementPtrTest class.
ImportTest class.
IndirectCallTest1 class.
IndirectCallTest2 class.
RVSDG module containing a static function that is called with the wrong number of arguments.
LinkedListTest class.
LoadFromUndefTest class.
LoadTest1 class.
LoadTest2 class.
RVSDG module with an arbitrary amount of alloca nodes.
StoreTest1 class.
StoreTest2 class.
StructType class.
Definition types.hpp:184
ThetaTest class.
ComparisonTest class.
ConstantDeltaTest class.
FloatBinaryTest class.
FloatConversionTest class.
GlobalArrayTest class.
IOBarrierTest class.
IntegerConversionTest class.
::mlir::rvsdg::OmegaNode ConvertModule(const llvm::LlvmRvsdgModule &rvsdgModule)
LoadNonVolatileTest class.
LoadVolatileTest class.
MemoryHoistBarrierTest class.
RootRegionNodesTest class.
StoreNonVolatileTest class.
StoreVolatileTest class.
WideMemoryNodesTest class.
Function type class.
Conditional operator / pattern matching.
Definition gamma.hpp:99
A phi node represents the fixpoint of mutually recursive definitions.
Definition Phi.hpp:46
Represents the argument of a region.
Definition region.hpp:41
Represent acyclic RVSDG subgraphs.
Definition region.hpp:213
#define JLM_UNREACHABLE(msg)
Definition common.hpp:43
Global memory state passed between functions.
static util::StatisticsCollector statisticsCollector
TEST(ControlOperationsTests, foldConstants)
NodeType * TryGetOwnerNode(const rvsdg::Input &input) noexcept
Checks if this is an input to a node of specified type.
Definition node.hpp:872
A variable routed out of all gamma regions as result.
Definition gamma.hpp:146
Description of a recursively defined variable.
Definition Phi.hpp:100
Description of a loop-carried variable.
Definition theta.hpp:50