@@ -115,19 +115,29 @@ static void groupByDialectPerByte(T range) {
115
115
IRNumberingState::IRNumberingState (Operation *op,
116
116
const BytecodeWriterConfig &config)
117
117
: config(config) {
118
- // Compute a global operation ID numbering according to the pre-order walk of
119
- // the IR. This is used as reference to construct use-list orders.
120
- unsigned operationID = 0 ;
121
- op->walk <WalkOrder::PreOrder>(
122
- [&](Operation *op) { operationIDs.try_emplace (op, operationID++); });
118
+ computeGlobalNumberingState (op);
123
119
124
120
// Number the root operation.
125
121
number (*op);
126
122
127
- // Push all of the regions of the root operation onto the worklist.
123
+ // A worklist of region contexts to number and the next value id before that
124
+ // region.
128
125
SmallVector<std::pair<Region *, unsigned >, 8 > numberContext;
129
- for (Region ®ion : op->getRegions ())
130
- numberContext.emplace_back (®ion, nextValueID);
126
+
127
+ // Functor to push the regions of the given operation onto the numbering
128
+ // context.
129
+ auto addOpRegionsToNumber = [&](Operation *op) {
130
+ MutableArrayRef<Region> regions = op->getRegions ();
131
+ if (regions.empty ())
132
+ return ;
133
+
134
+ // Isolated regions don't share value numbers with their parent, so we can
135
+ // start numbering these regions at zero.
136
+ unsigned opFirstValueID = isIsolatedFromAbove (op) ? 0 : nextValueID;
137
+ for (Region ®ion : regions)
138
+ numberContext.emplace_back (®ion, opFirstValueID);
139
+ };
140
+ addOpRegionsToNumber (op);
131
141
132
142
// Iteratively process each of the nested regions.
133
143
while (!numberContext.empty ()) {
@@ -136,14 +146,8 @@ IRNumberingState::IRNumberingState(Operation *op,
136
146
number (*region);
137
147
138
148
// Traverse into nested regions.
139
- for (Operation &op : region->getOps ()) {
140
- // Isolated regions don't share value numbers with their parent, so we can
141
- // start numbering these regions at zero.
142
- unsigned opFirstValueID =
143
- op.hasTrait <OpTrait::IsIsolatedFromAbove>() ? 0 : nextValueID;
144
- for (Region ®ion : op.getRegions ())
145
- numberContext.emplace_back (®ion, opFirstValueID);
146
- }
149
+ for (Operation &op : region->getOps ())
150
+ addOpRegionsToNumber (&op);
147
151
}
148
152
149
153
// Number each of the dialects. For now this is just in the order they were
@@ -178,6 +182,116 @@ IRNumberingState::IRNumberingState(Operation *op,
178
182
finalizeDialectResourceNumberings (op);
179
183
}
180
184
185
+ void IRNumberingState::computeGlobalNumberingState (Operation *rootOp) {
186
+ // A simple state struct tracking data used when walking operations.
187
+ struct StackState {
188
+ // / The operation currently being walked.
189
+ Operation *op;
190
+
191
+ // / The numbering of the operation.
192
+ OperationNumbering *numbering;
193
+
194
+ // / A flag indicating if the current state or one of its parents has
195
+ // / unresolved isolation status. This is tracked separately from the
196
+ // / isIsolatedFromAbove bit on `numbering` because we need to be able to
197
+ // / handle the given case:
198
+ // / top.op {
199
+ // / %value = ...
200
+ // / middle.op {
201
+ // / %value2 = ...
202
+ // / inner.op {
203
+ // / // Here we mark `inner.op` as not isolated. Note `middle.op`
204
+ // / // isn't known not isolated yet.
205
+ // / use.op %value2
206
+ // /
207
+ // / // Here inner.op is already known to be non-isolated, but
208
+ // / // `middle.op` is now also discovered to be non-isolated.
209
+ // / use.op %value
210
+ // / }
211
+ // / }
212
+ // / }
213
+ bool hasUnresolvedIsolation;
214
+ };
215
+
216
+ // Compute a global operation ID numbering according to the pre-order walk of
217
+ // the IR. This is used as reference to construct use-list orders.
218
+ unsigned operationID = 0 ;
219
+
220
+ // Walk each of the operations within the IR, tracking a stack of operations
221
+ // as we recurse into nested regions. This walk method hooks in at two stages
222
+ // during the walk:
223
+ //
224
+ // BeforeAllRegions:
225
+ // Here we generate a numbering for the operation and push it onto the
226
+ // stack if it has regions. We also compute the isolation status of parent
227
+ // regions at this stage. This is done by checking the parent regions of
228
+ // operands used by the operation, and marking each region between the
229
+ // the operand region and the current as not isolated. See
230
+ // StackState::hasUnresolvedIsolation above for an example.
231
+ //
232
+ // AfterAllRegions:
233
+ // Here we pop the operation from the stack, and if it hasn't been marked
234
+ // as non-isolated, we mark it as so. A non-isolated use would have been
235
+ // found while walking the regions, so it is safe to mark the operation at
236
+ // this point.
237
+ //
238
+ SmallVector<StackState> opStack;
239
+ rootOp->walk ([&](Operation *op, const WalkStage &stage) {
240
+ // After visiting all nested regions, we pop the operation from the stack.
241
+ if (stage.isAfterAllRegions ()) {
242
+ // If no non-isolated uses were found, we can safely mark this operation
243
+ // as isolated from above.
244
+ OperationNumbering *numbering = opStack.pop_back_val ().numbering ;
245
+ if (!numbering->isIsolatedFromAbove .has_value ())
246
+ numbering->isIsolatedFromAbove = true ;
247
+ return ;
248
+ }
249
+
250
+ // When visiting before nested regions, we process "IsolatedFromAbove"
251
+ // checks and compute the number for this operation.
252
+ if (!stage.isBeforeAllRegions ())
253
+ return ;
254
+ // Update the isolation status of parent regions if any have yet to be
255
+ // resolved.
256
+ if (!opStack.empty () && opStack.back ().hasUnresolvedIsolation ) {
257
+ Region *parentRegion = op->getParentRegion ();
258
+ for (Value operand : op->getOperands ()) {
259
+ Region *operandRegion = operand.getParentRegion ();
260
+ if (operandRegion == parentRegion)
261
+ continue ;
262
+ // We've found a use of an operand outside of the current region,
263
+ // walk the operation stack searching for the parent operation,
264
+ // marking every region on the way as not isolated.
265
+ Operation *operandContainerOp = operandRegion->getParentOp ();
266
+ auto it = std::find_if (
267
+ opStack.rbegin (), opStack.rend (), [=](const StackState &it) {
268
+ // We only need to mark up to the container region, or the first
269
+ // that has an unresolved status.
270
+ return !it.hasUnresolvedIsolation || it.op == operandContainerOp;
271
+ });
272
+ assert (it != opStack.rend () && " expected to find the container" );
273
+ for (auto &state : llvm::make_range (opStack.rbegin (), it)) {
274
+ // If we stopped at a region that knows its isolation status, we can
275
+ // stop updating the isolation status for the parent regions.
276
+ state.hasUnresolvedIsolation = it->hasUnresolvedIsolation ;
277
+ state.numbering ->isIsolatedFromAbove = false ;
278
+ }
279
+ }
280
+ }
281
+
282
+ // Compute the number for this op and push it onto the stack.
283
+ auto *numbering =
284
+ new (opAllocator.Allocate ()) OperationNumbering (operationID++);
285
+ if (op->hasTrait <OpTrait::IsIsolatedFromAbove>())
286
+ numbering->isIsolatedFromAbove = true ;
287
+ operations.try_emplace (op, numbering);
288
+ if (op->getNumRegions ()) {
289
+ opStack.emplace_back (StackState{
290
+ op, numbering, !numbering->isIsolatedFromAbove .has_value ()});
291
+ }
292
+ });
293
+ }
294
+
181
295
void IRNumberingState::number (Attribute attr) {
182
296
auto it = attrs.insert ({attr, nullptr });
183
297
if (!it.second ) {
0 commit comments