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 }