lib/choir/src/core/operation/lifecycle.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const core = @import("../root.zig");
  3 const context_mod = @import("../context/root.zig");
  4 const Context = context_mod.Context;
  5 const Value = core.Value;
  6 const OpOperand = core.OpOperand;
  7 const Block = core.Block;
  8 const Region = core.Region;
  9 const Mapping = core.Mapping;
 10 const cfg = core.cfg;
 11 const operation_storage = @import("storage.zig");
 12 
 13 pub fn Methods(comptime Operation: type) type {
 14     return struct {
 15         const State: type = Operation.State;
 16         const CloneOptions: type = Operation.CloneOptions;
 17         const StoragePlan = Operation.StoragePlan;
 18         const OperandStorage = operation_storage.Pair(*Value, OpOperand);
 19 
 20         pub fn create(context: *Context, state: State) !*Operation {
 21             if (state.properties_attr != null and state.properties_ref != null) {
 22                 return error.DuplicateOperationPropertiesPayload;
 23             }
 24 
 25             const allocator = context_mod.operationAllocator(context);
 26             const storage_allocator = context_mod.fixedOperationAllocator(context);
 27 
 28             const total_regions = std.math.add(
 29                 usize,
 30                 state.num_regions,
 31                 state.region_bodies.len,
 32             ) catch return error.CapacityOverflow;
 33             const registered_info = state.name.getRegisteredInfo() orelse context.lookupOperation(state.name.name);
 34             const properties_model = if (registered_info) |info| info.getPropertiesModel() else null;
 35             const storage_limits = StoragePlan.Limits{
 36                 .operands = state.operands.len,
 37                 .results = state.result_types.len,
 38                 .regions = total_regions,
 39                 .successors = state.successors.len,
 40                 .properties = if (properties_model) |model| model.size else 0,
 41                 .properties_alignment = if (properties_model) |model| model.alignment else .@"1",
 42             };
 43             var allocation = try StoragePlan.init(storage_allocator, storage_limits);
 44             const regions = allocation.regions;
 45             const op = regions.header;
 46             var op_initialized = false;
 47             errdefer if (op_initialized) {
 48                 destroy(op);
 49             } else {
 50                 allocation.storage.deinit(storage_allocator);
 51             };
 52 
 53             @memcpy(regions.operand_values, state.operands);
 54             @memcpy(regions.result_types, state.result_types);
 55 
 56             op.* = Operation{
 57                 .allocator = allocator,
 58                 .storage = allocation.storage,
 59                 .operand_storage = null,
 60                 .lifecycle_state = .{
 61                     .creation_id = 0,
 62                     .successor_storage_owned = false,
 63                 },
 64                 .context = context,
 65                 .name = state.name,
 66                 .location = state.location,
 67                 .operands = operation_storage.List(OpOperand).init(regions.operands),
 68                 .operand_values = regions.operand_values,
 69                 .results = operation_storage.List(Value).init(regions.results),
 70                 .result_types = regions.result_types,
 71                 .raw_dictionary_attrs = .{},
 72                 .properties = .{},
 73                 .regions = operation_storage.List(Region).init(regions.regions),
 74                 .successors = operation_storage.List(*Block).init(regions.successors),
 75                 .parent_block = null,
 76                 .prev_op = null,
 77                 .next_op = null,
 78                 .order = 0,
 79                 .tracking_prev = null,
 80                 .tracking_next = null,
 81             };
 82             op_initialized = true;
 83 
 84             if (properties_model) |model| {
 85                 try op.properties.init(allocator, model, regions.properties);
 86             }
 87 
 88             for (state.operands, 0..) |operand, i| {
 89                 op.operands.appendAssumeCapacity(.{
 90                     .value = operand,
 91                     .owner = op,
 92                     .operand_number = @intCast(i),
 93                     .operand_value_slot = &op.operand_values[i],
 94                     .next_use = null,
 95                 });
 96                 op.operands.items[i].attach();
 97             }
 98 
 99             for (state.result_types, 0..) |result_type, i| {
100                 const result = Value{
101                     .kind = .{ .op_result = .{
102                         .owner = op,
103                         .result_number = @intCast(i),
104                     } },
105                     .type = result_type,
106                     .id = 0,
107                 };
108                 op.results.appendAssumeCapacity(result);
109             }
110 
111             for (state.attributes) |attr| {
112                 try op.setAttr(attr.name, attr.value);
113             }
114 
115             for (state.raw_attributes) |attr| {
116                 _ = try op.raw_dictionary_attrs.set(allocator, attr.name, attr.value);
117             }
118 
119             for (0..state.num_regions) |_| {
120                 var region = Region.init(allocator);
121                 region.parent = @ptrCast(op);
122                 op.regions.appendAssumeCapacity(region);
123             }
124 
125             for (state.region_bodies) |body| {
126                 var region = Region.init(allocator);
127                 region.parent = @ptrCast(op);
128                 op.regions.appendAssumeCapacity(region);
129                 try op.regions.items[op.regions.items.len - 1].takeBody(body);
130             }
131 
132             op.successors.appendSliceAssumeCapacity(state.successors);
133 
134             if (state.properties_ref) |properties| {
135                 try op.copyProperties(properties);
136             }
137 
138             if (state.properties_attr) |attr| {
139                 try op.setPropertiesFromAttr(attr);
140             }
141 
142             op.storage.activate();
143             return op;
144         }
145 
146         pub fn cloneWithoutRegions(self: *const Operation) !*Operation {
147             var mapping = Mapping.init(self.allocator);
148             defer mapping.deinit();
149             return try cloneWithoutRegionsMapped(self, &mapping, .{});
150         }
151 
152         pub fn cloneWithoutRegionsMapped(
153             self: *const Operation,
154             mapping: *Mapping,
155             options: CloneOptions,
156         ) !*Operation {
157             var state = try State.initFromOperationHeader(self);
158 
159             var remapped_operands: []*Value = &.{};
160             defer if (remapped_operands.len > 0) self.allocator.free(remapped_operands);
161             if (options.clone_operands) {
162                 if (self.operand_values.len > 0) {
163                     remapped_operands = try self.allocator.alloc(*Value, self.operand_values.len);
164                     for (self.operand_values, 0..) |operand, i| {
165                         remapped_operands[i] = mapping.lookupOrDefaultValue(operand);
166                     }
167                     state.addOperands(remapped_operands);
168                 }
169             } else {
170                 state.addOperands(&.{});
171             }
172 
173             var remapped_successors: []*Block = &.{};
174             defer if (remapped_successors.len > 0) self.allocator.free(remapped_successors);
175             if (self.successors.items.len > 0) {
176                 remapped_successors = try self.allocator.alloc(*Block, self.successors.items.len);
177                 for (self.successors.items, 0..) |successor, i| {
178                     remapped_successors[i] = mapping.lookupOrDefaultBlock(successor);
179                 }
180                 state.addSuccessors(remapped_successors);
181             }
182 
183             const clone_op = try self.context.createOperation(state);
184             errdefer erase(clone_op);
185 
186             try mapping.mapOperation(self, clone_op);
187 
188             for (self.results.items, 0..) |*result, i| {
189                 try mapping.mapValue(result, clone_op.getResult(i).?);
190             }
191 
192             return clone_op;
193         }
194 
195         pub fn clone(self: *const Operation) !*Operation {
196             var mapping = Mapping.init(self.allocator);
197             defer mapping.deinit();
198 
199             const clone_op = try cloneWithoutRegionsMapped(self, &mapping, .{});
200             errdefer erase(clone_op);
201 
202             for (self.regions.items, 0..) |*region, i| {
203                 try region.cloneInto(&clone_op.regions.items[i], &mapping);
204             }
205 
206             return clone_op;
207         }
208 
209         pub fn dropAllReferences(self: *Operation) void {
210             dropAllOperandReferences(self);
211             dropAllSuccessorReferences(self);
212             for (self.regions.items) |*region| {
213                 region.dropAllReferences();
214             }
215         }
216 
217         fn dropAllOperandReferences(self: *Operation) void {
218             for (self.operands.items) |*operand| {
219                 operand.detach();
220             }
221         }
222 
223         fn dropAllSuccessorReferences(self: *Operation) void {
224             if (self.parent_block) |pred| {
225                 cfg.detach(pred, self);
226             }
227             self.successors.clearRetainingCapacity();
228         }
229 
230         pub fn hasNoDefinedValueUses(self: *Operation) bool {
231             if (!self.hasNoUses()) return false;
232             for (self.regions.items) |*region| {
233                 if (!region.hasNoDefinedValueUses()) return false;
234             }
235             return true;
236         }
237 
238         pub fn dropAllDefinedValueUses(self: *Operation) void {
239             for (self.results.items) |*result| {
240                 result.dropAllUses();
241             }
242             for (self.regions.items) |*region| {
243                 region.dropAllDefinedValueUses();
244             }
245         }
246 
247         pub fn removeFromBlock(self: *Operation) void {
248             const block = self.parent_block orelse return;
249             block.detachOperation(self);
250         }
251 
252         pub fn moveBefore(self: *Operation, before: *Operation) !void {
253             if (self == before) return;
254             if (self.parent_block == null) return error.OperationMoveDetached;
255             const block = before.parent_block orelse return error.OperationInsertBeforeDetached;
256             try cfg.prepare(block, self.successors.items);
257             removeFromBlock(self);
258             try block.insertBefore(self, before);
259         }
260 
261         pub fn moveToEnd(self: *Operation, block: *Block) !void {
262             if (self.parent_block == block and self.next_op == null) return;
263             if (self.parent_block == null) return error.OperationMoveDetached;
264             try cfg.prepare(block, self.successors.items);
265             removeFromBlock(self);
266             try block.addOperation(self);
267         }
268 
269         pub fn replaceOperands(self: *Operation, operands: []const *Value) !void {
270             var replacement: ?OperandStorage.Allocation = null;
271             if (operands.len > self.operands.capacity) {
272                 replacement = try OperandStorage.init(self.allocator, operands.len);
273             }
274 
275             const previous_storage = self.operand_storage;
276             dropAllOperandReferences(self);
277             if (replacement) |next| {
278                 self.operand_storage = next.handle;
279                 self.operand_values = next.first;
280                 self.operands = operation_storage.List(OpOperand).init(next.second);
281             } else {
282                 self.operand_values = self.operand_values.ptr[0..operands.len];
283                 self.operands.items = self.operands.items.ptr[0..0];
284             }
285             std.mem.copyForwards(*Value, self.operand_values, operands);
286             for (operands, 0..) |operand, i| {
287                 self.operands.appendAssumeCapacity(.{
288                     .value = operand,
289                     .owner = self,
290                     .operand_number = @intCast(i),
291                     .operand_value_slot = &self.operand_values[i],
292                     .next_use = null,
293                 });
294                 self.operands.items[i].attach();
295             }
296             if (replacement != null) {
297                 if (previous_storage) |handle| {
298                     var previous = handle;
299                     previous.deinit(self.allocator);
300                 }
301             }
302         }
303 
304         pub fn deinit(self: *Operation) void {
305             std.debug.assert(self.hasNoUses());
306             dropAllOperandReferences(self);
307             dropAllSuccessorReferences(self);
308             if (self.operand_storage) |handle| {
309                 var storage = handle;
310                 storage.deinit(self.allocator);
311                 self.operand_storage = null;
312             }
313             self.properties.deinit(self.allocator);
314             self.raw_dictionary_attrs.deinit(self.allocator);
315             for (self.regions.items) |*region| {
316                 region.deinit();
317             }
318             if (self.lifecycle_state.successor_storage_owned) {
319                 self.allocator.free(self.successors.items.ptr[0..self.successors.capacity]);
320                 self.lifecycle_state.successor_storage_owned = false;
321             }
322         }
323 
324         pub fn destroy(self: *Operation) void {
325             const storage_allocator = context_mod.fixedOperationAllocator(self.context);
326             var storage = self.storage;
327             deinit(self);
328             storage.deinit(storage_allocator);
329         }
330 
331         pub fn setOperandValue(self: *Operation, index: usize, new_value: *Value) void {
332             if (index >= self.operands.items.len) return;
333             const operand = &self.operands.items[index];
334             if (operand.value == new_value) return;
335             operand.detach();
336             operand.value = new_value;
337             operand.attach();
338             if (index < self.operand_values.len) {
339                 self.operand_values[index] = new_value;
340             }
341         }
342 
343         pub fn setSuccessors(self: *Operation, successors: []const *Block) !void {
344             var owned_successors: ?[]*Block = null;
345             defer if (owned_successors) |owned| self.allocator.free(owned);
346             const aliases_predecessors = successorsAliasPredecessors(
347                 self,
348                 successors,
349             );
350             const stable_successors: []const *Block = if (aliases_predecessors) stable: {
351                 const owned = try self.allocator.dupe(*Block, successors);
352                 owned_successors = owned;
353                 break :stable owned;
354             } else successors;
355 
356             try ensureSuccessorCapacity(self, stable_successors.len);
357             if (self.parent_block) |pred| {
358                 try cfg.prepare(pred, stable_successors);
359                 cfg.detachReplaced(pred, self, stable_successors);
360             }
361 
362             const next = self.successors.items.ptr[0..stable_successors.len];
363             std.mem.copyForwards(*Block, next, stable_successors);
364             self.successors.items = next;
365 
366             if (self.parent_block) |pred| {
367                 cfg.attachAssumeCapacity(pred, self.successors.items);
368             }
369         }
370 
371         fn successorsAliasPredecessors(
372             self: *const Operation,
373             successors: []const *Block,
374         ) bool {
375             for (self.successors.items) |target| {
376                 if (blockPointerSlicesOverlap(
377                     successors,
378                     target.predecessors.items,
379                 )) return true;
380             }
381             for (successors) |target| {
382                 if (blockPointerSlicesOverlap(
383                     successors,
384                     target.predecessors.items,
385                 )) return true;
386             }
387             return false;
388         }
389 
390         fn blockPointerSlicesOverlap(
391             left: []const *Block,
392             right: []const *Block,
393         ) bool {
394             if (left.len == 0 or right.len == 0) return false;
395             const element_size = @sizeOf(*Block);
396             const left_size = std.math.mul(
397                 usize,
398                 left.len,
399                 element_size,
400             ) catch return true;
401             const right_size = std.math.mul(
402                 usize,
403                 right.len,
404                 element_size,
405             ) catch return true;
406             const left_start = @intFromPtr(left.ptr);
407             const right_start = @intFromPtr(right.ptr);
408             const left_end = std.math.add(
409                 usize,
410                 left_start,
411                 left_size,
412             ) catch return true;
413             const right_end = std.math.add(
414                 usize,
415                 right_start,
416                 right_size,
417             ) catch return true;
418             return left_start < right_end and right_start < left_end;
419         }
420 
421         fn ensureSuccessorCapacity(self: *Operation, required: usize) !void {
422             if (required <= self.successors.capacity) return;
423             const storage = try self.allocator.alloc(*Block, required);
424             const previous_items = self.successors.items;
425             const previous_capacity = self.successors.capacity;
426             const previous_owned = self.lifecycle_state.successor_storage_owned;
427             @memcpy(storage[0..previous_items.len], previous_items);
428             self.successors = .{
429                 .items = storage[0..previous_items.len],
430                 .capacity = storage.len,
431             };
432             self.lifecycle_state.successor_storage_owned = true;
433             if (previous_owned) self.allocator.free(previous_items.ptr[0..previous_capacity]);
434         }
435 
436         pub fn erase(self: *Operation) void {
437             destroyNestedOperations(self);
438             self.context.untrackOperation(self);
439             removeFromBlock(self);
440             destroy(self);
441         }
442 
443         fn destroyNestedOperations(self: *Operation) void {
444             for (self.regions.items) |*region| {
445                 var block = region.blocks.tail;
446                 while (block) |current_block| {
447                     const prev_block = current_block.prev;
448                     var current: ?*Operation = @ptrCast(@alignCast(current_block.operations.tail));
449                     while (current) |nested_op| {
450                         const prev = nested_op.prev_op;
451                         destroyNestedOperations(nested_op);
452                         nested_op.context.untrackOperation(nested_op);
453                         removeFromBlock(nested_op);
454                         destroy(nested_op);
455                         current = prev;
456                     }
457                     block = prev_block;
458                 }
459             }
460         }
461     };
462 }