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
8#include <jlm/rvsdg/gamma.hpp>
10#include <jlm/rvsdg/theta.hpp>
11
12#include <unordered_map>
13
14namespace jlm::rvsdg
15{
16
17// Observe changes to region that may invalidate the cached computation
18// of predicate assignments / satisfiability constraints.
20{
21public:
22 ~Observer() override
23 {}
24
25 Observer(const Region & region, RegionPredicateTrace * tracer)
26 : RegionObserver(region),
27 tracer_(tracer)
28 {}
29
30 void
31 onNodeCreate(Node * node) override
32 {}
33
34 void
35 onNodeDestroy(Node * node) override
36 {
37 // If a structural node is destroyed, then we may now
38 // refer to region that does not exist any longer.
39 // Just invalidate.
41 *node,
42 [&](const StructuralNode &)
43 {
44 tracer_->Clear();
45 });
46 }
47
48 void
49 onInputCreate(Input * input) override
50 {}
51
52 void
53 onInputChange(Input * input, Output * /* old_origin */, Output * /* new_origin */) override
54 {
55 // This is really the only operation we care about: One edge has been
56 // changed.
57 // We can constrain this to changes of control edges -- no
58 // recomputation needed otherwise.
59 if (std::dynamic_pointer_cast<const ControlType>(input->Type()))
60 {
61 tracer_->Clear();
62 }
63 }
64
65 void
66 onInputDestroy(Input * input) override
67 {}
68
69private:
71};
72
75
78
79void
81{
82 // This is the cache invalidation signal: Some control
83 // edge assignment has changed. Just to be safe,
84 // invalidate all computations.
85 predAssignment_.clear();
86 predSat_.clear();
87
88 // Note: cannot clear observers here (might be within
89 // observer callback), so we will keep observing all
90 // regions that have been registered at least once.
91 // That is a slight over-approximation, but since we
92 // constrain to observing only "control" defs/uses,
93 // any change should rarely trigger, if at all.
94}
95
96void
98{
99 if (observers_.find(&region) == observers_.end())
100 {
101 observers_.emplace(&region, std::make_unique<Observer>(region, this));
102 }
103}
104
105// This function recurses through the "definition tree" of
106// predicate outputs / inputs. It records observations per region.
110 Input & input,
111 std::unordered_map<Input *, PredicateValueRange> & visitedInputs,
112 const ControlType & type)
113{
114 if (auto it = visitedInputs.find(&input); it != visitedInputs.end())
115 return it->second;
116
117 auto range = Compute(regionPredRange, input, visitedInputs, type);
118
119 // If the input is a region result, add its value range to the region
121 {
122 auto it = regionPredRange.find(input.region());
123
124 if (it != regionPredRange.end())
125 {
126 it->second.UpdateUnion(range);
127 }
128 else
129 {
130 regionPredRange.emplace(input.region(), range);
131 ObserveRegion(*input.region());
132 }
133 }
134
135 auto [it, inserted] = visitedInputs.emplace(&input, std::move(range));
137
138 return it->second;
139}
140
141// Second part of the recursion, helper to the function above:
142// performs actual recursion, and computes (but without recording,
143// which is done by the controller function ComputeAndRecord above).
147 Input & input,
148 std::unordered_map<Input *, PredicateValueRange> & visitedInputs,
149 const ControlType & type)
150{
151 // Given a predicate use site, record the predicate definition
152 // value(s) that occur in this region, or passed as unchanged
153 // values in this region.
154
155 // Formal "definition site" of this predicate.
156 auto origin = input.origin();
157 if (auto node = TryGetOwnerNode<Node>(*origin))
158 {
160 *node,
161 [&](const rvsdg::SimpleNode & node) -> PredicateValueRange
162 {
163 // Is this a definite value assignment in this region?
164 // Then record and terminate the recursion here.
166 node.GetOperation(),
167 [&](const ControlConstantOperation & op)
168 {
169 return PredicateValueRange::CreateSingleValue(op.value());
170 },
171 [&]()
172 {
173 return PredicateValueRange::CreateUnknown(type);
174 });
175 },
176 [&](const rvsdg::GammaNode & node) -> PredicateValueRange
177 {
178 // Is this predicate defined as output of gamma?
179 // Then accumulate all values obtainable from the
180 // different gamma branches into this region.
181 auto exitVar = node.MapOutputExitVar(*origin);
182
184 for (auto res : exitVar.branchResult)
185 {
187 }
188
189 return range;
190 },
191 [&](const rvsdg::ThetaNode & node) -> PredicateValueRange
192 {
193 // For theta, check if it is a pass-through -- use
194 // the value passed through, if applicable, or
195 // declare "indeterminate value".
196 auto loopVar = node.MapOutputLoopVar(*origin);
197 if (loopVar.post->origin() == loopVar.pre)
198 {
200 }
201 else
202 {
204 }
205 },
206 [&]()
207 {
209 });
210 }
211 else if (auto node = TryGetRegionParentNode<Node>(*origin))
212 {
213 // The predicate value is "defined" as input into this region.
214 // Trace out of this region, and record possible values
215 // entering this region.
217 *node,
218 [&](const rvsdg::GammaNode & node) -> PredicateValueRange
219 {
220 auto argVar = node.MapBranchArgument(*origin);
221
222 if (auto entry = std::get_if<GammaNode::EntryVar>(&argVar))
223 {
224 return ComputeAndRecord(regionPredRange, *entry->input, visitedInputs, type);
225 }
226 else
227 {
229 }
230 },
232 {
233 auto loopVar = node.MapPreLoopVar(*origin);
234 if (loopVar.post->origin() == loopVar.pre)
235 {
237 }
238 else
239 {
241 }
242 },
243 [&]() -> PredicateValueRange
244 {
246 });
247 }
248 else
249 {
251 }
252}
253
256{
257 // Check for control type, ignore if wrong type.
258 auto controlType = std::dynamic_pointer_cast<const ControlType>(predUse.Type());
259 if (!controlType)
260 {
262 }
263
264 auto i = predAssignment_.find(&predUse);
265 if (i == predAssignment_.end())
266 {
267 // Recursively trace from the predicate use site to its
268 // definition sites in different regions. Record predicate
269 // assignments per region.
271 std::unordered_map<Input *, PredicateValueRange> visitedInputs;
273 i = predAssignment_.emplace(&predUse, std::move(range)).first;
274 }
275
276 const RegionPredRange & regionRange = i->second;
277 auto j = regionRange.find(&region);
278
279 return j != regionRange.end() ? j->second : PredicateValueRange::CreateUnknown(*controlType);
280}
281
284{
285 ObserveRegion(region);
286 auto i = predSat_.find(&region);
287 if (i == predSat_.end())
288 {
289 if (region.node())
290 {
291 // Recursively check all regions that this region is nested in.
292 // Accumulate all predicates.
294
295 // If this region is owned by a gamma node itself, then it is
296 // entered conditionally based on the predicate.
297 MatchType(
298 *region.node(),
299 [&](const rvsdg::GammaNode & node)
300 {
301 req.push_back(std::make_pair(node.predicate(), region.index()));
302 });
303 i = predSat_.emplace(&region, std::move(req)).first;
304 }
305 else
306 {
307 i = predSat_.emplace(&region, PredicateSatRequired{}).first;
308 }
309 }
310
311 return i->second;
312}
313
314bool
316{
317 // Compute "required" predicates + values to enter this region.
318 for (auto [pred, value] : GetRegionSatRequired(targetRegion))
319 {
320 // Check which predicate values the origin region would
321 // necessarily assign.
323 if (!assigned.AllowsValue(value))
324 {
325 // Unsatisfiable, coming from "originRegion", we can never enter
326 // "targetRegion".
327 return false;
328 }
329 }
330
331 return true;
332}
333
334}
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
Value range for a predicate.
static PredicateValueRange CreateEmpty(const ControlType &type)
Constructs empty value range (unsatisfiable predicate range).
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.
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.