Jlm
Loading...
Searching...
No Matches
RegionPredicateTrace.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2026 Helge Bahmann <hcb@chaoticmind.net>
3 * See COPYING for terms of redistribution.
4 */
5
7
9#include <jlm/rvsdg/gamma.hpp>
12#include <jlm/rvsdg/node.hpp>
13#include <jlm/rvsdg/region.hpp>
15#include <jlm/rvsdg/theta.hpp>
16#include <jlm/rvsdg/Trace.hpp>
17#include <jlm/util/common.hpp>
18
19#include <algorithm>
20#include <unordered_map>
21#include <utility>
22
23namespace jlm::rvsdg
24{
25
26// Observe changes to region that may invalidate the cached computation
27// of predicate assignments / satisfiability constraints.
29{
30public:
31 ~Observer() override
32 {}
33
34 Observer(const Region & region, RegionPredicateTrace * tracer)
35 : RegionObserver(region),
36 tracer_(tracer)
37 {}
38
39 void
40 onNodeCreate(Node * node) override
41 {}
42
43 void
44 onNodeDestroy(Node * node) override
45 {
46 // If a structural node is destroyed, then we may now
47 // refer to region that does not exist any longer.
48 // Just invalidate.
50 *node,
51 [&](const StructuralNode &)
52 {
53 tracer_->Clear();
54 });
55 }
56
57 void
58 onInputCreate(Input * input) override
59 {}
60
61 void
62 onInputChange(Input * input, Output * /* old_origin */, Output * /* new_origin */) override
63 {
64 // This is really the only operation we care about: One edge has been
65 // changed.
66 // We can constrain this to changes of control edges -- no
67 // recomputation needed otherwise.
68 if (std::dynamic_pointer_cast<const ControlType>(input->Type()))
69 {
70 tracer_->Clear();
71 }
72 }
73
74 void
75 onInputDestroy(Input * input) override
76 {}
77
78private:
80};
81
84
87
88void
90{
91 // This is the cache invalidation signal: Some control
92 // edge assignment has changed. Just to be safe,
93 // invalidate all computations.
94 predAssignment_.clear();
95 predSat_.clear();
96
97 // Note: cannot clear observers here (might be within
98 // observer callback), so we will keep observing all
99 // regions that have been registered at least once.
100 // That is a slight over-approximation, but since we
101 // constrain to observing only "control" defs/uses,
102 // any change should rarely trigger, if at all.
103}
104
105void
107{
108 if (observers_.find(&region) == observers_.end())
109 {
110 observers_.emplace(&region, std::make_unique<Observer>(region, this));
111 }
112}
113
114// This function recurses through the "definition tree" of
115// predicate outputs / inputs. It records observations per region.
119 Input & input,
120 std::unordered_map<Input *, PredicateValueRange> & visitedInputs,
121 const ControlType & type)
122{
123 if (auto it = visitedInputs.find(&input); it != visitedInputs.end())
124 return it->second;
125
126 auto range = Compute(regionPredRange, input, visitedInputs, type);
127
128 // If the input is a region result, add its value range to the region
130 {
131 auto it = regionPredRange.find(input.region());
132
133 if (it != regionPredRange.end())
134 {
135 it->second.UpdateUnion(range);
136 }
137 else
138 {
139 regionPredRange.emplace(input.region(), range);
140 ObserveRegion(*input.region());
141 }
142 }
143
144 auto [it, inserted] = visitedInputs.emplace(&input, std::move(range));
146
147 return it->second;
148}
149
150// Second part of the recursion, helper to the function above:
151// performs actual recursion, and computes (but without recording,
152// which is done by the controller function ComputeAndRecord above).
156 Input & input,
157 std::unordered_map<Input *, PredicateValueRange> & visitedInputs,
158 const ControlType & type)
159{
160 // Given a predicate use site, record the predicate definition
161 // value(s) that occur in this region, or passed as unchanged
162 // values in this region.
163
164 // Formal "definition site" of this predicate.
165 auto origin = input.origin();
166 if (auto node = TryGetOwnerNode<Node>(*origin))
167 {
169 *node,
170 [&](const rvsdg::SimpleNode & node) -> PredicateValueRange
171 {
172 // Is this a definite value assignment in this region?
173 // Then record and terminate the recursion here.
175 node.GetOperation(),
176 [&](const ControlConstantOperation & op)
177 {
178 return PredicateValueRange::CreateSingleValue(op.value());
179 },
180 [&]()
181 {
182 return PredicateValueRange::CreateUnknown(type);
183 });
184 },
185 [&](const rvsdg::GammaNode & node) -> PredicateValueRange
186 {
187 // Is this predicate defined as output of gamma?
188 // Then accumulate all values obtainable from the
189 // different gamma branches into this region.
190 auto exitVar = node.MapOutputExitVar(*origin);
191
193 for (auto res : exitVar.branchResult)
194 {
196 }
197
198 return range;
199 },
200 [&](const rvsdg::ThetaNode & node) -> PredicateValueRange
201 {
202 // For theta, check if it is a pass-through -- use
203 // the value passed through, if applicable, or
204 // declare "indeterminate value".
205 auto loopVar = node.MapOutputLoopVar(*origin);
206 if (loopVar.post->origin() == loopVar.pre)
207 {
209 }
210 else
211 {
213 }
214 },
215 [&]()
216 {
218 });
219 }
220 else if (auto node = TryGetRegionParentNode<Node>(*origin))
221 {
222 // The predicate value is "defined" as input into this region.
223 // Trace out of this region, and record possible values
224 // entering this region.
226 *node,
227 [&](const rvsdg::GammaNode & node) -> PredicateValueRange
228 {
229 return MatchVariant(
230 node.MapBranchArgument(*origin),
231 [&](const GammaNode::EntryVar & entry)
232 {
233 return ComputeAndRecord(regionPredRange, *entry.input, visitedInputs, type);
234 },
235 [&](const GammaNode::MatchVar &)
236 {
237 return PredicateValueRange::CreateUnknown(type);
238 });
239 },
241 {
242 auto loopVar = node.MapPreLoopVar(*origin);
243 if (loopVar.post->origin() == loopVar.pre)
244 {
246 }
247 else
248 {
250 }
251 },
252 [&]() -> PredicateValueRange
253 {
255 });
256 }
257 else
258 {
260 }
261}
262
265{
266 // Check for control type, ignore if wrong type.
267 auto controlType = std::dynamic_pointer_cast<const ControlType>(predUse.Type());
268 if (!controlType)
269 {
271 }
272
273 auto i = predAssignment_.find(&predUse);
274 if (i == predAssignment_.end())
275 {
276 // Recursively trace from the predicate use site to its
277 // definition sites in different regions. Record predicate
278 // assignments per region.
280 std::unordered_map<Input *, PredicateValueRange> visitedInputs;
282 i = predAssignment_.emplace(&predUse, std::move(range)).first;
283 }
284
285 const RegionPredRange & regionRange = i->second;
286 auto j = regionRange.find(&region);
287
288 return j != regionRange.end() ? j->second : PredicateValueRange::CreateUnknown(*controlType);
289}
290
293{
294 ObserveRegion(region);
295 auto i = predSat_.find(&region);
296 if (i == predSat_.end())
297 {
298 if (region.node())
299 {
300 // Recursively check all regions that this region is nested in.
301 // Accumulate all predicates.
303
304 // If this region is owned by a gamma node itself, then it is
305 // entered conditionally based on the predicate.
306 MatchType(
307 *region.node(),
308 [&](const rvsdg::GammaNode & node)
309 {
310 req.push_back(std::make_pair(node.predicate(), region.index()));
311 });
312 i = predSat_.emplace(&region, std::move(req)).first;
313 }
314 else
315 {
316 i = predSat_.emplace(&region, PredicateSatRequired{}).first;
317 }
318 }
319
320 return i->second;
321}
322
323bool
325{
326 // Compute "required" predicates + values to enter this region.
327 for (auto [pred, value] : GetRegionSatRequired(targetRegion))
328 {
329 // Check which predicate values the origin region would
330 // necessarily assign.
332 if (!assigned.AllowsValue(value))
333 {
334 // Unsatisfiable, coming from "originRegion", we can never enter
335 // "targetRegion".
336 return false;
337 }
338 }
339
340 return true;
341}
342
344
347{
350 {
351 return it->second;
352 }
353
355 auto [it, inserted] = predicateValueRanges_.emplace(&tracedOutput, std::move(range));
357 return it->second;
358}
359
362{
363 auto & controlType = *util::assertedCast<const ControlType>(output.Type().get());
364
365 // Control constants provide a known value for the predicate
367 {
369 }
370
371 // handle gamma outputs by taking the union of the possible values of each subregion
372 if (auto gamma = rvsdg::TryGetOwnerNode<rvsdg::GammaNode>(output))
373 {
374 auto exitVar = gamma->MapOutputExitVar(output);
375
377 for (auto result : exitVar.branchResult)
378 {
379 range.UpdateUnion(getPossibleValues(*result->origin()));
380 }
381
382 return range;
383 }
384
385 // This function is only called on traced outputs, so it never stops at a gamma argument
387
388 // handle theta outputs by continuing from the loop var post
389 if (auto theta = rvsdg::TryGetOwnerNode<rvsdg::ThetaNode>(output))
390 {
391 auto loopVar = theta->MapOutputLoopVar(output);
392 return getPossibleValues(*loopVar.post->origin());
393 }
394
395 // Theta arguments belonging to invariant loop variables have already been traced.
396 // Other theta arguments would require following back-edges.
397
398 // Otherwise we are unable to provide a set
400}
401
402bool
404 Output & output,
405 size_t value,
406 std::vector<Region *> & impossibleRegions)
407{
409
410 // If the output is never able to provide the required value, its region can not be an origin
411 if (!satisfiable)
412 {
413 auto originRegion = output.region();
414
415 // Multiple origins from the same region will never be considered,
416 // so we are sure we never add duplicate regions to the list
417 JLM_ASSERT(std::count(impossibleRegions.begin(), impossibleRegions.end(), originRegion) == 0);
418
420 }
421
422 return satisfiable;
423}
424
425bool
427 rvsdg::Output & output,
428 size_t value,
429 std::vector<Region *> & impossibleRegions)
430{
431 // handle gamma outputs by recursively propagating the requirement to each subregion
432 if (auto gamma = rvsdg::TryGetOwnerNode<rvsdg::GammaNode>(output))
433 {
434 auto exitVar = gamma->MapOutputExitVar(output);
435
436 bool anyReachable = false;
437 for (auto result : exitVar.branchResult)
438 {
439 anyReachable |= markRequiredPredicateValue(*result->origin(), value, impossibleRegions);
440 }
441
442 // If none of the gamma subregions can provide the required value,
443 // its parent region is also not able to satisfy the requirement.
444 return anyReachable;
445 }
446
447 // handle theta outputs by making the same requirement inside the theta
448 if (auto theta = rvsdg::TryGetOwnerNode<rvsdg::ThetaNode>(output))
449 {
450 auto loopVar = theta->MapOutputLoopVar(output);
451
452 // This call can mark certain subregions within the theta region as impossible origins.
453 // If it also returns false, it means the theta will never provide the required value,
454 // which also makes the surrounding region an impossible origin.
457 return canLoopSatisfyValue;
458 }
459
460 // The predicate output is not the output of a structural node.
461 // It can still be the input of a structural node, but we can not continue
462 // calling setRequiredPredicateValue out of the structural nodes.
463 // The input may for example be used in only some subregions of a gamma,
464 // or only the first iteration of a theta.
465 // We can therefore not be sure that the value is actually required.
466 // Also, RVSDG (pretty much) never routes ControlType values into structural nodes.
467
468 // Use regular tracing to see if we are able to determine a fixed value for the output.
469 auto & possibleValues = getPossibleValues(output);
470 return possibleValues.AllowsValue(value);
471}
472
473bool
475{
476 auto key = std::make_pair(&output, value);
477
478 // If the (output, value) pair has already been processed, use the cached result
479 auto [it, inserted] = impossibleOriginRegions_.insert({ key, {} });
480
481 if (inserted)
482 {
483 // The requirement has not been processed, find regions that cannot satisfy it
484 markRequiredPredicateValue(output, value, it->second);
485 }
486
487 // Go through all regions that have been marked as unable to satisfy the requirement,
488 // and check if any of them are the origin region, or one of its ancestors.
489 for (auto & impossibleOrigin : it->second)
490 {
492 return false;
493 }
494
495 return true;
496}
497
498bool
502{
503 // find the common ancestor of the origin and target regions
506
507 // While traversing, add all ancestors of the origin region to this set
510
512 {
513 const auto targetDepth = targetAncestor->getDepth();
514 const auto originDepth = originAncestor->getDepth();
515
516 // Move one region up along the target region ancestors
518 {
519 targetAncestor = targetAncestor->node()->region();
520 }
521
522 // Move one region up along the origin region ancestors
524 {
525 // If the origin ancestor region we are leaving is a theta subregion,
526 // we can add the fact that the theta predicate must be 0 in order to leave the region
527 auto node = originAncestor->node();
528 if (auto theta = dynamic_cast<rvsdg::ThetaNode *>(node))
529 {
530 if (!canCurrentOriginSatisfyRequirement(*theta->predicate()->origin(), 0))
531 return false;
532 }
533
534 // move one region up and add it to the set of origin region ancestors
535 originAncestor = node->region();
537 }
538 }
539 // Lowest common ancestor found
541 const auto commonAncestor = targetAncestor;
542
543 // Go through the ancestors of the target region and check if any of them have requirements
544 // that can not be satisfied by the origin region or one of its ancestors
547 {
548 // when the target region is in a gamma subregion,
549 // the origin must be able to provide the correct gamma predicate value
550 auto node = targetAncestor->node();
551 if (auto gamma = dynamic_cast<rvsdg::GammaNode *>(node))
552 {
554 *gamma->predicate()->origin(),
555 targetAncestor->index()))
556 return false;
557 }
558 targetAncestor = node->region();
559 }
560
561 // No proof of unreachability was found
562 return true;
563}
564
565void
571
572}
bool markRequiredPredicateValueInternal(Output &output, size_t value, std::vector< Region * > &impossibleOrigins)
bool markRequiredPredicateValue(Output &output, size_t value, std::vector< Region * > &impossibleOrigins)
std::unordered_map< std::pair< Output *, size_t >, std::vector< Region * >, util::Hash< std::pair< Output *, size_t > > > impossibleOriginRegions_
std::unordered_map< Output *, PredicateValueRange > predicateValueRanges_
PredicateValueRange getPossibleValuesInternal(Output &output)
bool canCurrentOriginSatisfyRequirement(Output &output, size_t value)
bool isReachableFromRegion(Region &targetRegion, Region &originRegion)
PredicateValueRange & getPossibleValues(Output &output)
Conditional operator / pattern matching.
Definition gamma.hpp:99
std::variant< MatchVar, EntryVar > MapBranchArgument(const rvsdg::Output &output) const
Maps branch subregion entry argument to its role (pattern match or entry variable).
Definition gamma.cpp:330
Output * origin() const noexcept
Definition node.hpp:58
const std::shared_ptr< const rvsdg::Type > & Type() const noexcept
Definition node.hpp:67
Region * region() const noexcept
Definition node.cpp:83
rvsdg::Region * region() const noexcept
Definition node.hpp:761
rvsdg::Region * region() const noexcept
Definition node.cpp:151
const std::shared_ptr< const rvsdg::Type > & Type() const noexcept
Definition node.hpp:366
Value range for a predicate.
static PredicateValueRange CreateEmpty(const ControlType &type)
Constructs empty value range (unsatisfiable predicate range).
static PredicateValueRange CreateSingleValue(const ControlValueRepresentation &value)
Definite value range (exactly one value possible).
static PredicateValueRange CreateUnknown(const ControlType &type)
Constructs full value range (every value possible).
Proxy object to observe changes to a region.
Definition region.hpp:886
Observer(const Region &region, RegionPredicateTrace *tracer)
void onInputChange(Input *input, Output *, Output *) override
Traces region reachability by predicate assertions.
PredicateValueRange GetRegionPredicateAssignConstraints(Region &region, Input &predUse)
Computes value range for a predicate when exiting a region.
PredicateSatRequired GetRegionSatRequired(Region &region)
Computes required predicate assignments for region.
std::unordered_map< Region *, PredicateSatRequired > predSat_
bool CheckPredicatesSatisfiable(Region &originRegion, Region &targetRegion)
Checks for dynamic reachability between two regions.
PredicateValueRange Compute(RegionPredRange &regionPredRange, Input &input, std::unordered_map< Input *, PredicateValueRange > &visitedInputs, const ControlType &type)
const PredicateValueRange & ComputeAndRecord(RegionPredRange &regionPredRange, Input &input, std::unordered_map< Input *, PredicateValueRange > &visitedInputs, const ControlType &type)
std::unordered_map< Region *, PredicateValueRange > RegionPredRange
std::unordered_map< Input *, RegionPredRange > predAssignment_
std::unordered_map< Region *, std::unique_ptr< Observer > > observers_
Represent acyclic RVSDG subgraphs.
Definition region.hpp:213
rvsdg::StructuralNode * node() const noexcept
Definition region.hpp:301
const SimpleOperation & GetOperation() const noexcept override
#define JLM_ASSERT(x)
Definition common.hpp:16
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.
decltype(auto) MatchVariant(T &&obj, Fns &&... fns)
Pattern match over variant.
Output & traceOutputIntraProcedurally(Output &output, bool mayEnterSubregions)
Definition Trace.cpp:442
NodeType * TryGetOwnerNode(const rvsdg::Input &input) noexcept
Checks if this is an input to a node of specified type.
Definition node.hpp:872
std::vector< std::pair< Input *, std::size_t > > PredicateSatRequired
Describes which predicates need to be satisfied.
A variable routed into all gamma regions.
Definition gamma.hpp:131
The match/discriminator variable of this gamma node.
Definition gamma.hpp:116