Jlm
Loading...
Searching...
No Matches
IOBarrierElimination.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2026 Nico Reißmann <nico.reissmann@gmail.com>
3 * See COPYING for terms of redistribution.
4 */
5
11#include <jlm/rvsdg/delta.hpp>
12#include <jlm/rvsdg/gamma.hpp>
13#include <jlm/rvsdg/lambda.hpp>
15#include <jlm/rvsdg/Phi.hpp>
17#include <jlm/rvsdg/theta.hpp>
19
20namespace jlm::llvm
21{
22
24{
25 const char * NormalizationTimerLabel_ = "NormalizationTime";
26 const char * MarkTimerLabel_ = "MarkTime";
27 const char * PropagateTimerLabel_ = "PropagateTime";
28 const char * EncodeTimerLabel_ = "EncodeTime";
29 const char * SweepTimerLabel_ = "SweepTime";
30
31public:
32 ~Statistics() override = default;
33
34 explicit Statistics(const util::FilePath & sourceFile)
35 : util::Statistics(Id::IOBarrierElimination, sourceFile)
36 {}
37
38 void
43
44 void
49
50 void
55
56 void
58 {
60 }
61
62 void
67
68 void
73
74 void
79
80 void
85
86 void
91
92 void
97
98 static std::unique_ptr<Statistics>
99 create(const util::FilePath & sourceFile)
100 {
101 return std::make_unique<Statistics>(sourceFile);
102 }
103};
104
106{
107public:
111 void
112 markDereferenceable(const rvsdg::Output & output, const size_t sizeInBytes)
113 {
114 if (const auto it = dereferenceableInputs_.find(&output); it == dereferenceableInputs_.end())
115 {
116 dereferenceableInputs_[&output] = sizeInBytes;
117 }
118 else
119 {
120 dereferenceableInputs_[&output] = std::max(it->second, sizeInBytes);
121 }
122 }
123
127 [[nodiscard]] size_t
129 {
130 const auto it = dereferenceableInputs_.find(&output);
131 if (it == dereferenceableInputs_.end())
132 return 0;
133
134 return it->second;
135 }
136
137 static std::unique_ptr<Context>
139 {
140 return std::make_unique<Context>();
141 }
142
143private:
144 std::unordered_map<const rvsdg::Output *, size_t> dereferenceableInputs_{};
145};
146
148
150 : Transformation("IOBarrierElimination")
151{}
152
153void
155 rvsdg::RvsdgModule & module,
157{
158 auto & rvsdg = module.Rvsdg();
159
161 auto statistics = Statistics::create(module.SourceFilePath().value());
162
163 statistics->startNormalizationStatistics();
164 normalizeMemoryHoistBarriers(rvsdg.GetRootRegion());
165 statistics->stopNormalizationStatistics();
166
167 statistics->startMarkStatistics();
168 markOutputs(rvsdg.GetRootRegion());
169 statistics->stopMarkStatistics();
170
171 statistics->startPropagateStatistics();
172 propagateSize(rvsdg);
173 statistics->stopPropagateStatistics();
174
175 statistics->startEncodeStatistics();
176 encodeSize(rvsdg);
177 statistics->stopEncodeStatistics();
178
179 statistics->startSweepStatistics();
180 sweepRegion(rvsdg.GetRootRegion());
181 statistics->stopSweepStatistics();
182
183 statisticsCollector.CollectDemandedStatistics(std::move(statistics));
184
185 // Discard internal state to free up memory after we are done
186 context_.reset();
187}
188
189static std::vector<rvsdg::SimpleNode *>
191{
192 std::vector<rvsdg::SimpleNode *> hoistBarrierNodes;
193 for (auto & user : output.Users())
194 {
195 if (auto [node, hoistBarrierOp] =
197 hoistBarrierOp)
198 {
199 hoistBarrierNodes.push_back(node);
200 }
201 }
202
203 return hoistBarrierNodes;
204}
205
206static std::optional<rvsdg::SimpleNode *>
208 const std::vector<rvsdg::SimpleNode *> & hoistBarrierNodes,
209 const std::variant<rvsdg::Node *, rvsdg::Region *> ioStateOwner)
210{
211 for (auto node : hoistBarrierNodes)
212 {
213 if (MemoryHoistBarrierOperation::getIOStateInput(*node).origin()->GetOwner() == ioStateOwner)
214 return node;
215 }
216
217 return std::nullopt;
218}
219
220static void
222 rvsdg::Output & output,
223 rvsdg::SimpleNode & memoryHoistBarrierNode)
224{
225 JLM_ASSERT(is<MemoryHoistBarrierOperation>(&memoryHoistBarrierNode));
226
227 output.divertUsersWhere(
228 *memoryHoistBarrierNode.output(0),
229 [&memoryHoistBarrierNode](const rvsdg::Input & user)
230 {
231 return &MemoryHoistBarrierOperation::getAddressInput(memoryHoistBarrierNode) != &user;
232 });
233}
234
235static std::optional<rvsdg::GammaNode::EntryVar>
237{
238 for (auto & entryVar : gammaNode.GetEntryVars())
239 {
240 if (rvsdg::is<IOStateType>(entryVar.input->Type()))
241 return entryVar;
242 }
243
244 return std::nullopt;
245}
246
247void
249{
250 for (auto & node : region.Nodes())
251 {
253 node,
254 [](rvsdg::StructuralNode & structuralNode)
255 {
256 for (auto & subregion : structuralNode.Subregions())
257 {
258 // Handle innermost regions first
260
261 // Normalize subregion arguments
262 for (auto & argument : subregion.Arguments())
263 {
264 if (is<PointerType>(argument->Type()))
265 {
266 auto ioBarrierNodes = collectMemoryHoistBarrierNodes(*argument);
267 if (auto ioBarrierNode = selectMemoryHoistBarrierNode(ioBarrierNodes, &subregion))
268 divertUsersToMemoryHoistBarrierNode(*argument, **ioBarrierNode);
269 }
270 }
271 }
272
273 // Normalize node outputs
274 for (auto & output : structuralNode.Outputs())
275 {
276 if (is<PointerType>(output.Type()))
277 {
278 auto ioBarrierNodes = collectMemoryHoistBarrierNodes(output);
279 if (auto ioBarrierNode =
280 selectMemoryHoistBarrierNode(ioBarrierNodes, &structuralNode))
281 divertUsersToMemoryHoistBarrierNode(output, **ioBarrierNode);
282 }
283 }
284 },
285 [](rvsdg::SimpleNode & simpleNode)
286 {
288 simpleNode.GetOperation(),
289 [&simpleNode](const LoadNonVolatileOperation &)
290 {
291 auto & loadedValue = LoadOperation::LoadedValueOutput(simpleNode);
292 if (is<PointerType>(loadedValue.Type()))
293 {
294 auto ioBarrierNodes = collectMemoryHoistBarrierNodes(loadedValue);
295 if (auto ioBarrierNode =
296 selectMemoryHoistBarrierNode(ioBarrierNodes, simpleNode.region()))
297 divertUsersToMemoryHoistBarrierNode(loadedValue, **ioBarrierNode);
298 }
299 });
300 },
301 []()
302 {
303 throw std::logic_error("Unexpected node type");
304 });
305 }
306}
307
308size_t
309IOBarrierElimination::getDereferenceableSize(const rvsdg::GammaNode::EntryVar & entryVar) const
310{
311 // We only care about pointer entry variables
312 if (!rvsdg::is<PointerType>(entryVar.input->Type()))
313 return 0;
314
315 size_t size = std::numeric_limits<std::size_t>::max();
316 for (const auto * argument : entryVar.branchArgument)
317 {
318 if (const size_t numUsers = argument->nusers(); numUsers == 0)
319 {
320 // If we have no users, nothing can be marked
321 return 0;
322 }
323
324 if (auto argumentSize = context_->getDereferenceableSize(*argument); argumentSize > 0)
325 {
326 // The user is marked. Let's take the minimum marked size between this argument and all
327 // other arguments.
328 size = std::min(size, argumentSize);
329 }
330 else
331 {
332 // Let's see whether we can find an appropriate MemoryHoistBarrierOperation node
333 const rvsdg::SimpleNode * hoistBarrierNode = nullptr;
334 for (auto & user : argument->Users())
335 {
336 auto [simpleNode, hoistBarrierOp] =
338 if (hoistBarrierOp)
339 {
340 // We do have a MemoryHoistBarrierOperation node
341 auto & ioStateOperand =
342 *MemoryHoistBarrierOperation::getIOStateInput(*simpleNode).origin();
343 if (rvsdg::TryGetOwnerRegion(ioStateOperand) == argument->region())
344 {
345 // We only want a MemoryHoistBarrierOperation node whose IO state is connected to the
346 // argument of the gamma subregion. This ensures that there is no other non-returning
347 // node, such as a call to abort() etc., that would prohibit the connected memory
348 // operation to be executed.
349 hoistBarrierNode = simpleNode;
350 break;
351 }
352 }
353 }
354 if (!hoistBarrierNode)
355 {
356 // We do not find an appropriate MemoryHoistBarrierOperation node. Nothing can be marked.
357 return 0;
358 }
359
360 argumentSize = context_->getDereferenceableSize(
361 MemoryHoistBarrierOperation::getAddressOutput(*hoistBarrierNode));
362 if (argumentSize == 0)
363 {
364 // The user of the MemoryHoistBarrierOperation node is not marked either. We are done for
365 // good.
366 return 0;
367 }
368
369 // The user is marked. Let's take the minimum marked size between this argument and all
370 // other arguments.
371 size = std::min(size, argumentSize);
372 }
373 }
374
375 return size;
376}
377
378void
379IOBarrierElimination::markOutputs(rvsdg::Region & region)
380{
381 for (auto & node : region.Nodes())
382 {
384 node,
385 [this](rvsdg::PhiNode & phiNode)
386 {
387 markOutputs(*phiNode.subregion());
388 },
389 [this](rvsdg::LambdaNode & lambdaNode)
390 {
391 // Mark lambda arguments
392 for (const auto argument : lambdaNode.GetFunctionArguments())
393 {
394 if (rvsdg::is<PointerType>(argument->Type()))
395 {
396 context_->markDereferenceable(*argument, 0);
397 }
398 }
399
400 // Mark lambda context variables
401 for (const auto [_, inner] : lambdaNode.GetContextVars())
402 {
403 if (rvsdg::is<PointerType>(inner->Type()))
404 {
405 // FIXME: We can do better here
406 context_->markDereferenceable(*inner, 0);
407 }
408 }
409
410 markOutputs(*lambdaNode.subregion());
411 },
412 [](rvsdg::DeltaNode &)
413 {
414 // Nothing needs to be done
415 },
416 [this](rvsdg::ThetaNode & thetaNode)
417 {
418 markOutputs(*thetaNode.subregion());
419 },
420 [this](rvsdg::GammaNode & gammaNode)
421 {
422 // Handle innermost regions first
423 for (auto & subregion : gammaNode.Subregions())
424 {
425 markOutputs(subregion);
426 }
427
428 if (auto ioStateEntryVar = getIOStateEntryVar(gammaNode))
429 {
430 for (auto & entryVar : gammaNode.GetEntryVars())
431 {
432 if (const auto size = getDereferenceableSize(entryVar); size > 0)
433 {
434 // All gamma node arguments of this entry variable are marked. This means that
435 // on every path through this gamma node, the pointer variable is at least
436 // dereferenced by the returned size. Consequently, we can create a
437 // MemoryHoistBarrierOperation node for this entry variable out here and mark it.
438 auto & mhbNode = MemoryHoistBarrierOperation::createNode(
439 *entryVar.input->origin(),
440 *ioStateEntryVar->input->origin(),
441 0);
442 auto & mhbAddressOutput = MemoryHoistBarrierOperation::getAddressOutput(mhbNode);
443 entryVar.input->divert_to(&mhbAddressOutput);
444 context_->markDereferenceable(mhbAddressOutput, size);
445 }
446 }
447 }
448 },
449 [this](rvsdg::SimpleNode & simpleNode)
450 {
452 simpleNode.GetOperation(),
453 [this, &simpleNode](const LoadNonVolatileOperation & loadOperation)
454 {
455 const auto & addressOperand = *LoadOperation::AddressInput(simpleNode).origin();
456 const auto sizeInBytes = GetTypeStoreSize(*loadOperation.GetLoadedType());
457 context_->markDereferenceable(addressOperand, sizeInBytes);
458 },
459 [this, &simpleNode](const StoreNonVolatileOperation & storeOperation)
460 {
461 const auto & addressOperand = *StoreOperation::AddressInput(simpleNode).origin();
462 const auto sizeInBytes = GetTypeStoreSize(storeOperation.GetStoredType());
463 context_->markDereferenceable(addressOperand, sizeInBytes);
464 });
465 },
466 []()
467 {
468 throw std::logic_error("Unhandled node type");
469 });
470 }
471}
472
473void
474IOBarrierElimination::propagateSize(rvsdg::Graph & graph)
475{
476 std::function<void(rvsdg::Region &)> propagate = [&](rvsdg::Region & region)
477 {
478 for (auto node : rvsdg::TopDownTraverser(&region))
479 {
481 *node,
482 [&](rvsdg::PhiNode & phiNode)
483 {
484 propagate(*phiNode.subregion());
485 },
486 [&](rvsdg::LambdaNode & lambdaNode)
487 {
488 propagate(*lambdaNode.subregion());
489 },
490 [&](rvsdg::GammaNode & gammaNode)
491 {
492 for (auto & [input, arguments] : gammaNode.GetEntryVars())
493 {
494 if (!is<PointerType>(input->Type()))
495 continue;
496
497 if (const auto size = context_->getDereferenceableSize(*input->origin()); size > 0)
498 {
499 for (const auto & argument : arguments)
500 {
501 context_->markDereferenceable(*argument, size);
502 }
503 }
504 }
505
506 for (auto & subregion : gammaNode.Subregions())
507 propagate(subregion);
508
509 for (auto & [results, output] : gammaNode.GetExitVars())
510 {
511 if (!is<PointerType>(output->Type()))
512 continue;
513
514 size_t sizeInBytes = std::numeric_limits<std::size_t>::max();
515 for (const auto & result : results)
516 {
517 sizeInBytes =
518 std::min(sizeInBytes, context_->getDereferenceableSize(*result->origin()));
519 if (sizeInBytes == 0)
520 {
521 break;
522 }
523 }
524 if (sizeInBytes > 0)
525 context_->markDereferenceable(*output, sizeInBytes);
526 }
527 },
528 [&](rvsdg::ThetaNode & thetaNode)
529 {
530 // FIXME: This fix-point algorithm could be improved in terms of performance.
531
532 std::unordered_map<rvsdg::Output *, size_t> loopVarPreSizes;
533
534 // Mark loop variables in subregion
535 for (const auto & loopVar : thetaNode.GetLoopVars())
536 {
537 if (!is<PointerType>(loopVar.input->Type()))
538 continue;
539
540 if (const auto inputSize = context_->getDereferenceableSize(*loopVar.input->origin());
541 inputSize > 0)
542 {
543 loopVarPreSizes[loopVar.pre] = inputSize;
544 context_->markDereferenceable(*loopVar.pre, inputSize);
545 }
546 }
547
548 // Propagate information through loop until fix-point is reached
549 bool repeat = false;
550 do
551 {
552 repeat = false;
553 propagate(*thetaNode.subregion());
554
555 for (const auto & loopVar : thetaNode.GetLoopVars())
556 {
557 if (!is<PointerType>(loopVar.input->Type()))
558 continue;
559
560 const auto preSize = loopVarPreSizes[loopVar.pre];
561 const auto postSize = context_->getDereferenceableSize(*loopVar.post->origin());
562 if (preSize != postSize)
563 {
564 loopVarPreSizes[loopVar.pre] = postSize;
565 context_->markDereferenceable(*loopVar.pre, std::min(preSize, postSize));
566 repeat = true;
567 }
568 }
569 } while (repeat);
570
571 // Mark loop outputs
572 for (const auto & loopVar : thetaNode.GetLoopVars())
573 {
574 if (!is<PointerType>(loopVar.output->Type()))
575 continue;
576
577 if (const auto postSize = context_->getDereferenceableSize(*loopVar.post->origin());
578 postSize > 0)
579 {
580 context_->markDereferenceable(*loopVar.output, postSize);
581 }
582 }
583 },
584 [&](rvsdg::DeltaNode &)
585 {
586 // Nothing needs to be done
587 },
588 [&](rvsdg::SimpleNode & simpleNode)
589 {
591 simpleNode.GetOperation(),
592 [this, &simpleNode](const MemoryHoistBarrierOperation &)
593 {
594 const auto & barredInput =
595 MemoryHoistBarrierOperation::getAddressInput(simpleNode);
596 if (!is<PointerType>(barredInput.Type()))
597 return;
598
599 if (const auto size = context_->getDereferenceableSize(*barredInput.origin());
600 size > 0)
601 context_->markDereferenceable(*simpleNode.output(0), size);
602 });
603 },
604 []()
605 {
606 throw std::logic_error(
607 "Unhandled node type encountered during dereferenceable propagation.");
608 });
609 }
610 };
611
612 propagate(graph.GetRootRegion());
613}
614
615void
616IOBarrierElimination::encodeSize(rvsdg::Graph & graph)
617{
618 std::function<void(rvsdg::Region &)> encode = [&](rvsdg::Region & region)
619 {
620 for (auto node : rvsdg::TopDownTraverser(&region))
621 {
623 *node,
624 [&](rvsdg::PhiNode & phiNode)
625 {
626 encode(*phiNode.subregion());
627 },
628 [&](rvsdg::LambdaNode & lambdaNode)
629 {
630 encode(*lambdaNode.subregion());
631 },
632 [&](rvsdg::GammaNode & gammaNode)
633 {
634 for (auto & subregion : gammaNode.Subregions())
635 encode(subregion);
636 },
637 [&](rvsdg::ThetaNode & thetaNode)
638 {
639 encode(*thetaNode.subregion());
640 },
641 [&](rvsdg::DeltaNode &)
642 {
643 // Nothing needs to be done
644 },
645 [&](rvsdg::SimpleNode & simpleNode)
646 {
648 simpleNode.GetOperation(),
649 [this, &simpleNode](const MemoryHoistBarrierOperation & memoryHoistBarrier)
650 {
651 auto & addressOperand =
652 *MemoryHoistBarrierOperation::getAddressInput(simpleNode).origin();
653 const auto mhbSize = memoryHoistBarrier.getDereferenceableSize();
654
655 if (const auto size = context_->getDereferenceableSize(addressOperand);
656 size != mhbSize)
657 {
658 auto & ioStateOperand =
659 *MemoryHoistBarrierOperation::getIOStateInput(simpleNode).origin();
660 auto & mhbNode = MemoryHoistBarrierOperation::createNode(
661 addressOperand,
662 ioStateOperand,
663 // Ensure that we do not lose information. Always take the max value.
664 std::max(size, mhbSize));
665 MemoryHoistBarrierOperation::getAddressOutput(simpleNode)
666 .divert_users(&MemoryHoistBarrierOperation::getAddressOutput(mhbNode));
667 }
668 });
669 },
670 []()
671 {
672 throw std::logic_error("Unhandled node type encountered during encoding.");
673 });
674 }
675
676 region.prune(false);
677 };
678
679 encode(graph.GetRootRegion());
680}
681
682void
683IOBarrierElimination::sweepRegion(rvsdg::Region & region)
684{
685 for (auto & node : region.Nodes())
686 {
688 node,
689 [this](const rvsdg::PhiNode & phiNode)
690 {
691 sweepRegion(*phiNode.subregion());
692 },
693 [this](const rvsdg::LambdaNode & lambdaNode)
694 {
695 sweepRegion(*lambdaNode.subregion());
696 },
697 [](const rvsdg::DeltaNode &)
698 {
699 // Nothing needs to be done
700 },
701 [this](rvsdg::GammaNode & gammaNode)
702 {
703 for (auto & subregion : gammaNode.Subregions())
704 {
705 sweepRegion(subregion);
706 }
707 },
708 [this](const rvsdg::ThetaNode & thetaNode)
709 {
710 sweepRegion(*thetaNode.subregion());
711 },
712 [this](const rvsdg::SimpleNode & simpleNode)
713 {
714 if (const auto loadOperation =
715 dynamic_cast<const LoadNonVolatileOperation *>(&simpleNode.GetOperation()))
716 {
717 auto & loadAddress = LoadOperation::AddressInput(simpleNode);
718 auto [hoistBarrierNode, hoistBarrierOp] =
720 *loadAddress.origin());
721 if (!hoistBarrierOp)
722 return;
723
724 auto & barredAddressInput =
725 MemoryHoistBarrierOperation::getAddressInput(*hoistBarrierNode);
726 const auto barredAddressSize =
727 context_->getDereferenceableSize(*barredAddressInput.origin());
728 if (barredAddressSize == 0)
729 return;
730
731 if (const auto storeSize = GetTypeStoreSize(*loadOperation->GetLoadedType());
732 barredAddressSize < storeSize)
733 return;
734
735 loadAddress.divert_to(barredAddressInput.origin());
736 }
737 },
738 []()
739 {
740 throw std::logic_error("Unsupported node type");
741 });
742 }
743
744 region.prune(false);
745}
746}
util::HashSet< rvsdg::Output * > arguments
std::unordered_map< const rvsdg::Output *, size_t > dereferenceableInputs_
size_t getDereferenceableSize(const rvsdg::Output &output) const
void markDereferenceable(const rvsdg::Output &output, const size_t sizeInBytes)
static std::unique_ptr< Context > create()
Statistics(const util::FilePath &sourceFile)
static std::unique_ptr< Statistics > create(const util::FilePath &sourceFile)
void propagateSize(rvsdg::Graph &graph)
void Run(rvsdg::RvsdgModule &module, util::StatisticsCollector &statisticsCollector) override
Perform RVSDG transformation.
static void normalizeMemoryHoistBarriers(rvsdg::Region &region)
void markOutputs(rvsdg::Region &region)
void sweepRegion(rvsdg::Region &region)
std::unique_ptr< Context > context_
static rvsdg::Input & getIOStateInput(const rvsdg::Node &node) noexcept
Conditional operator / pattern matching.
Definition gamma.hpp:99
std::vector< EntryVar > GetEntryVars() const
Gets all entry variables for this gamma.
Definition gamma.cpp:305
Region & GetRootRegion() const noexcept
Definition graph.hpp:99
Output * origin() const noexcept
Definition node.hpp:58
const std::shared_ptr< const rvsdg::Type > & Type() const noexcept
Definition node.hpp:67
OutputIteratorRange Outputs() noexcept
Definition node.hpp:657
UsersRange Users()
Definition node.hpp:354
size_t divertUsersWhere(Output &newOrigin, const F &match)
Definition node.hpp:321
std::variant< Node *, Region * > GetOwner() const noexcept
Definition node.hpp:378
A phi node represents the fixpoint of mutually recursive definitions.
Definition Phi.hpp:46
rvsdg::Region * subregion() const noexcept
Definition Phi.hpp:320
Represent acyclic RVSDG subgraphs.
Definition region.hpp:213
void prune(bool recursive)
Definition region.cpp:326
NodeRange Nodes() noexcept
Definition region.hpp:375
const std::optional< util::FilePath > & SourceFilePath() const noexcept
NodeOutput * output(size_t index) const noexcept
SubregionIteratorRange Subregions()
void CollectDemandedStatistics(std::unique_ptr< Statistics > statistics)
Statistics Interface.
util::Timer & GetTimer(const std::string &name)
util::Timer & AddTimer(std::string name)
void start() noexcept
Definition time.hpp:54
void stop() noexcept
Definition time.hpp:67
#define JLM_ASSERT(x)
Definition common.hpp:16
Global memory state passed between functions.
static util::StatisticsCollector statisticsCollector
static std::optional< rvsdg::GammaNode::EntryVar > getIOStateEntryVar(const rvsdg::GammaNode &gammaNode)
static void divertUsersToMemoryHoistBarrierNode(rvsdg::Output &output, rvsdg::SimpleNode &memoryHoistBarrierNode)
size_t GetTypeStoreSize(const rvsdg::Type &type)
Definition types.cpp:386
static std::optional< rvsdg::SimpleNode * > selectMemoryHoistBarrierNode(const std::vector< rvsdg::SimpleNode * > &hoistBarrierNodes, const std::variant< rvsdg::Node *, rvsdg::Region * > ioStateOwner)
static std::vector< rvsdg::SimpleNode * > collectMemoryHoistBarrierNodes(rvsdg::Output &output)
void MatchTypeWithDefault(T &obj, const Fns &... fns)
Pattern match over subclass type of given object with default handler.
void MatchType(T &obj, const Fns &... fns)
Pattern match over subclass type of given object.
Region * TryGetOwnerRegion(const rvsdg::Input &input) noexcept
Definition node.hpp:1021
NodeType * TryGetOwnerNode(const rvsdg::Input &input) noexcept
Checks if this is an input to a node of specified type.
Definition node.hpp:872
detail::TopDownTraverserGeneric< false > TopDownTraverser
Traverser for visiting every node in a region in a top down order.
A variable routed into all gamma regions.
Definition gamma.hpp:131
rvsdg::Input * input
Variable at entry point (input to gamma node).
Definition gamma.hpp:135
std::vector< rvsdg::Output * > branchArgument
Variable inside each of the branch regions (argument per subregion).
Definition gamma.hpp:139