lib/choir/src/core/operation/model.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const core = @import("../root.zig");
3 const Value = core.Value;
4 const OpOperand = core.OpOperand;
5 const Type = core.Type;
6 const Attribute = core.Attribute;
7 const NamedAttribute = core.NamedAttribute;
8 const NamedAttributeList = core.NamedAttributeList;
9 const Location = core.Location;
10 const Block = core.Block;
11 const Region = core.Region;
12 const diagnostics = @import("../../diagnostics/root.zig");
13 const operation_attributes = @import("attributes.zig");
14 const operation_lifecycle = @import("lifecycle.zig");
15 const operation_name = @import("name.zig");
16 const operation_properties = @import("properties.zig");
17 const operation_registered = @import("registration.zig");
18 const operation_state = @import("state.zig");
19 const operation_storage = @import("storage.zig");
20 const context_mod = @import("../context/root.zig");
21
22 pub const Context = context_mod.Context;
23
24 const LifecycleState = packed struct(u32) {
25 creation_id: u31,
26 successor_storage_owned: bool,
27 };
28
29 pub const Operation = struct {
30 allocator: std.mem.Allocator,
31
32 storage: operation_storage.Storage,
33
34 operand_storage: ?operation_storage.Handle,
35
36 context: *Context,
37
38 name: OperationName,
39
40 location: Location,
41
42 operands: operation_storage.List(OpOperand),
43
44 operand_values: []*Value,
45
46 results: operation_storage.List(Value),
47
48 result_types: []const Type,
49
50 raw_dictionary_attrs: NamedAttributeList,
51
52 properties: PropertyStorage,
53
54 regions: operation_storage.List(Region),
55
56 successors: operation_storage.List(*Block),
57
58 parent_block: ?*Block,
59
60 prev_op: ?*Operation,
61 next_op: ?*Operation,
62
63 order: u32,
64 lifecycle_state: LifecycleState,
65
66 tracking_prev: ?*Operation,
67 tracking_next: ?*Operation,
68
69 pub const WalkOrder = enum {
70 pre_order,
71 post_order,
72 };
73
74 pub const WalkResult = enum {
75 advance,
76 skip,
77 interrupt,
78
79 pub fn wasSkipped(self: WalkResult) bool {
80 return self == .skip;
81 }
82
83 pub fn wasInterrupted(self: WalkResult) bool {
84 return self == .interrupt;
85 }
86 };
87
88 pub const WalkOptions = struct {
89 order: WalkOrder = .post_order,
90 };
91
92 pub const PropertyRef: type = operation_properties.Ref;
93 pub const PropertyStorage: type = operation_properties.Storage(Operation);
94 pub const StoragePlan: type = operation_storage.Plan(
95 Operation,
96 *Value,
97 Type,
98 OpOperand,
99 Value,
100 Region,
101 *Block,
102 );
103 const leaf_property_storage_capacity = StoragePlan.Capacity.derive(.{
104 .operands = 0,
105 .results = 1,
106 .regions = 0,
107 .successors = 0,
108 .properties = @sizeOf(?Attribute),
109 .properties_alignment = .fromByteUnits(@alignOf(?Attribute)),
110 }) catch unreachable;
111 const nullary_storage_capacity = StoragePlan.Capacity.derive(.{
112 .operands = 0,
113 .results = 1,
114 .regions = 0,
115 .successors = 0,
116 .properties = 0,
117 .properties_alignment = .@"1",
118 }) catch unreachable;
119 const unary_storage_capacity = StoragePlan.Capacity.derive(.{
120 .operands = 1,
121 .results = 1,
122 .regions = 0,
123 .successors = 0,
124 .properties = 0,
125 .properties_alignment = .@"1",
126 }) catch unreachable;
127 const binary_storage_capacity = StoragePlan.Capacity.derive(.{
128 .operands = 2,
129 .results = 1,
130 .regions = 0,
131 .successors = 0,
132 .properties = 0,
133 .properties_alignment = .@"1",
134 }) catch unreachable;
135 pub const StorageAllocator: type = operation_storage.PoolAllocator(
136 leaf_property_storage_capacity,
137 binary_storage_capacity,
138 nullary_storage_capacity,
139 unary_storage_capacity,
140 );
141 pub const OperationName: type = operation_name.OperationName;
142 pub const CloneOptions = struct {
143 clone_operands: bool = true,
144 };
145 pub const State: type = operation_state.State(Operation);
146 const lifecycle = operation_lifecycle.Methods(Operation);
147 pub const create = lifecycle.create;
148 pub const cloneWithoutRegions = lifecycle.cloneWithoutRegions;
149 pub const cloneWithoutRegionsMapped = lifecycle.cloneWithoutRegionsMapped;
150 pub const clone = lifecycle.clone;
151 pub const dropAllReferences = lifecycle.dropAllReferences;
152 pub const hasNoDefinedValueUses = lifecycle.hasNoDefinedValueUses;
153 pub const dropAllDefinedValueUses = lifecycle.dropAllDefinedValueUses;
154 pub const removeFromBlock = lifecycle.removeFromBlock;
155 pub const moveBefore = lifecycle.moveBefore;
156 pub const moveToEnd = lifecycle.moveToEnd;
157 pub const replaceOperands = lifecycle.replaceOperands;
158 pub const deinit = lifecycle.deinit;
159 pub const destroy = lifecycle.destroy;
160 pub const setOperandValue = lifecycle.setOperandValue;
161 pub const setSuccessors = lifecycle.setSuccessors;
162 pub const erase = lifecycle.erase;
163 const registered = operation_registered.Methods(Operation);
164 pub const getRegisteredInfo = registered.getRegisteredInfo;
165 pub const getInherentAttributeNames = registered.getInherentAttributeNames;
166 pub const hasInherentAttributeName = registered.hasInherentAttributeName;
167 pub const isDiscardableAttrName = registered.isDiscardableAttrName;
168 pub const isRegistered = registered.isRegistered;
169 pub const getInterface = registered.getInterface;
170 pub const interface_handle = registered.InterfaceHandle;
171 pub const interface = registered.interface;
172 pub const hasInterface = registered.hasInterface;
173 pub const getTraits = registered.getTraits;
174 pub const hasTrait = registered.hasTrait;
175 pub const hasTraitId = registered.hasTraitId;
176 pub const hasTraitName = registered.hasTraitName;
177 const attributes = operation_attributes.Methods(Operation);
178 pub const AttributeIterator: type = attributes.AttributeIterator;
179 pub const DiscardableAttrIterator: type = attributes.DiscardableAttrIterator;
180 pub const SetDiscardableAttrError: type = attributes.SetDiscardableAttrError;
181 pub const getAttrs = attributes.getAttrs;
182 pub const getNumAttrs = attributes.getNumAttrs;
183 pub const getRawDictionaryAttrs = attributes.getRawDictionaryAttrs;
184 pub const getDiscardableAttrs = attributes.getDiscardableAttrs;
185 pub const countDiscardableAttrs = attributes.countDiscardableAttrs;
186 pub const getDiscardableAttr = attributes.getDiscardableAttr;
187 pub const getDiscardableAttrAs = attributes.getDiscardableAttrAs;
188 pub const setDiscardableAttr = attributes.setDiscardableAttr;
189 pub const removeDiscardableAttr = attributes.removeDiscardableAttr;
190 pub const getAttr = attributes.getAttr;
191 pub const getAttrAs = attributes.getAttrAs;
192 pub const setAttr = attributes.setAttr;
193 pub const removeAttr = attributes.removeAttr;
194 pub const getPropertiesAsAttr = attributes.getPropertiesAsAttr;
195 pub const getPropertiesRef = attributes.getPropertiesRef;
196 pub const setPropertiesFromAttr = attributes.setPropertiesFromAttr;
197 pub const copyProperties = attributes.copyProperties;
198
199 pub fn walk(
200 self: *Operation,
201 options: WalkOptions,
202 context: anytype,
203 callback: anytype,
204 ) anyerror!WalkResult {
205 return walkOperation(self, options, context, callback);
206 }
207
208 fn walkOperation(
209 op: *Operation,
210 options: WalkOptions,
211 context: anytype,
212 callback: anytype,
213 ) anyerror!WalkResult {
214 if (options.order == .pre_order) {
215 const result = try invokeWalkCallback(context, callback, op);
216 if (result.wasInterrupted()) return .interrupt;
217 if (result.wasSkipped()) return .advance;
218 }
219
220 for (op.regions.items) |*region| {
221 const result = try region.walkOperations(options, context, callback);
222 if (result.wasInterrupted()) return .interrupt;
223 }
224
225 if (options.order == .post_order) {
226 return try invokeWalkCallback(context, callback, op);
227 }
228
229 return .advance;
230 }
231
232 fn invokeWalkCallback(context: anytype, callback: anytype, op: *Operation) anyerror!WalkResult {
233 const result = callback(context, op);
234 return try normalizeWalkResult(result);
235 }
236
237 fn normalizeWalkResult(result: anytype) anyerror!WalkResult {
238 const Result = @TypeOf(result);
239 return switch (@typeInfo(Result)) {
240 .error_union => |info| blk: {
241 const payload = try result;
242 if (info.payload == void) break :blk .advance;
243 break :blk payload;
244 },
245 .void => .advance,
246 else => result,
247 };
248 }
249
250 pub fn getName(self: Operation) OperationName {
251 return self.name;
252 }
253
254 pub fn getLoc(self: Operation) Location {
255 return self.location;
256 }
257
258 pub fn createdBefore(self: Operation, boundary: u31) bool {
259 return self.lifecycle_state.creation_id < boundary;
260 }
261
262 pub fn setCreationId(self: *Operation, creation_id: u31) void {
263 self.lifecycle_state.creation_id = creation_id;
264 }
265
266 pub fn getNumOperands(self: Operation) usize {
267 return self.operands.items.len;
268 }
269
270 pub fn getOperand(self: Operation, index: usize) ?*Value {
271 if (index >= self.operand_values.len) return null;
272 return self.operand_values[index];
273 }
274
275 pub fn getOpOperand(self: *Operation, index: usize) ?*OpOperand {
276 if (index >= self.operands.items.len) return null;
277 return &self.operands.items[index];
278 }
279
280 pub fn getNumResults(self: Operation) usize {
281 return self.result_types.len;
282 }
283
284 pub fn getResult(self: *Operation, index: usize) ?*Value {
285 if (index >= self.results.items.len) return null;
286 return &self.results.items[index];
287 }
288
289 pub fn getOperandValues(self: Operation) []const *Value {
290 return self.operand_values;
291 }
292
293 pub fn getResultTypes(self: Operation) []const Type {
294 return self.result_types;
295 }
296
297 pub fn getNumRegions(self: Operation) usize {
298 return self.regions.items.len;
299 }
300
301 pub fn getRegion(self: *Operation, index: usize) ?*Region {
302 if (index >= self.regions.items.len) return null;
303 return &self.regions.items[index];
304 }
305
306 pub fn getNumSuccessors(self: Operation) usize {
307 return self.successors.items.len;
308 }
309
310 pub fn getSuccessor(self: Operation, index: usize) ?*Block {
311 if (index >= self.successors.items.len) return null;
312 return self.successors.items[index];
313 }
314
315 pub fn getBlock(self: Operation) ?*Block {
316 return self.parent_block;
317 }
318
319 pub fn getParentRegion(self: *const Operation) ?*Region {
320 const block = self.parent_block orelse return null;
321 return block.getParentRegion();
322 }
323
324 pub fn getParentOp(self: *const Operation) ?*Operation {
325 const region = self.getParentRegion() orelse return null;
326 return region.getParentOperation();
327 }
328
329 pub fn isProperAncestor(self: *const Operation, other: *const Operation) bool {
330 var current = other.getParentOp();
331 while (current) |op| {
332 if (op == self) return true;
333 current = op.getParentOp();
334 }
335 return false;
336 }
337
338 pub fn isAncestor(self: *const Operation, other: *const Operation) bool {
339 return self == other or self.isProperAncestor(other);
340 }
341
342 pub fn isBeforeInBlock(self: *const Operation, other: *const Operation) bool {
343 const block = self.parent_block orelse return false;
344 if (other.parent_block != block) return false;
345 if (self == other) return false;
346
347 return block.operationPrecedes(self, other);
348 }
349
350 pub fn getContext(self: *const Operation) *Context {
351 return self.context;
352 }
353
354 pub fn emitDiagnostic(
355 self: *Operation,
356 severity: diagnostics.Severity,
357 message: []const u8,
358 ) Context.InFlightDiagnostic {
359 return self.context.emitDiagnostic(diagnostics.operationDiagnostic(self, severity, message));
360 }
361
362 pub fn emitError(self: *Operation, message: []const u8) Context.InFlightDiagnostic {
363 return self.emitDiagnostic(.err, message);
364 }
365
366 pub fn emitWarning(self: *Operation, message: []const u8) Context.InFlightDiagnostic {
367 return self.emitDiagnostic(.warning, message);
368 }
369
370 pub fn emitRemark(self: *Operation, message: []const u8) Context.InFlightDiagnostic {
371 return self.emitDiagnostic(.remark, message);
372 }
373
374 pub fn emitOpError(self: *Operation, message: []const u8) !Context.InFlightDiagnostic {
375 const full_message = try std.fmt.allocPrint(
376 context_mod.diagnosticPayloadAllocator(self.context),
377 "'{s}' op {s}",
378 .{ self.name.name, message },
379 );
380 var diagnostic = self.emitError(full_message);
381 diagnostic.ownMessage(full_message);
382 return diagnostic;
383 }
384
385 pub fn hasNoUses(self: Operation) bool {
386 for (self.results.items) |result| {
387 if (!result.hasNoUses()) {
388 return false;
389 }
390 }
391 return true;
392 }
393
394 pub fn hasOneUse(self: Operation) bool {
395 if (self.results.items.len == 0) return false;
396 for (self.results.items) |result| {
397 if (!result.hasOneUse()) {
398 return false;
399 }
400 }
401 return true;
402 }
403
404 pub fn format(self: Operation, writer: *std.Io.Writer) std.Io.Writer.Error!void {
405 if (self.results.items.len > 0) {
406 for (self.results.items, 0..) |result, i| {
407 if (i > 0) try writer.writeAll(", ");
408 try writer.print("{f}", .{result});
409 }
410 try writer.writeAll(" = ");
411 }
412
413 try writer.print("{f}", .{self.name});
414
415 if (self.operands.items.len > 0) {
416 try writer.writeAll("(");
417 for (self.operands.items, 0..) |operand, i| {
418 if (i > 0) try writer.writeAll(", ");
419 try writer.print("{f}", .{operand.value.*});
420 }
421 try writer.writeAll(")");
422 } else {
423 try writer.writeAll("()");
424 }
425
426 try self.formatAttrs(writer);
427
428 if (self.results.items.len > 0) {
429 try writer.writeAll(" : ");
430 for (self.results.items, 0..) |result, i| {
431 if (i > 0) try writer.writeAll(", ");
432 try writer.print("{f}", .{result.type});
433 }
434 }
435
436 for (self.regions.items) |region| {
437 try writer.writeAll(" ");
438 try writer.print("{f}", .{region});
439 }
440 }
441
442 fn formatAttrs(self: *const Operation, writer: *std.Io.Writer) std.Io.Writer.Error!void {
443 var attrs = self.getAttrs();
444 var first = true;
445 while (attrs.next()) |attr| {
446 try formatAttr(writer, &first, attr);
447 }
448
449 if (!first) try writer.writeAll("}");
450 }
451
452 fn formatAttr(
453 writer: *std.Io.Writer,
454 first: *bool,
455 attr: NamedAttribute,
456 ) std.Io.Writer.Error!void {
457 if (first.*) {
458 try writer.writeAll(" {");
459 first.* = false;
460 } else {
461 try writer.writeAll(", ");
462 }
463 try writer.print("{f}", .{attr});
464 }
465 };
466
467 test "operation lifecycle identity retains the 64-bit header footprint" {
468 const testing = std.testing;
469
470 try testing.expectEqual(@as(usize, 4), @sizeOf(LifecycleState));
471 if (@sizeOf(usize) == 8) {
472 try testing.expectEqual(@as(usize, 568), @sizeOf(Operation));
473 try testing.expectEqual(@as(usize, 560), @offsetOf(Operation, "order"));
474 }
475 }
476 test "common operation storage classes match profiled layouts" {
477 try std.testing.expectEqual(
478 @as(usize, 672),
479 Operation.leaf_property_storage_capacity.total_bytes,
480 );
481 try std.testing.expectEqual(@as(usize, 640), Operation.nullary_storage_capacity.total_bytes);
482 try std.testing.expectEqual(@as(usize, 696), Operation.unary_storage_capacity.total_bytes);
483 try std.testing.expectEqual(@as(usize, 752), Operation.binary_storage_capacity.total_bytes);
484 try std.testing.expectEqual(
485 std.mem.Alignment.@"8",
486 Operation.leaf_property_storage_capacity.allocation_alignment,
487 );
488 try std.testing.expectEqual(
489 std.mem.Alignment.@"8",
490 Operation.nullary_storage_capacity.allocation_alignment,
491 );
492 try std.testing.expectEqual(
493 std.mem.Alignment.@"8",
494 Operation.unary_storage_capacity.allocation_alignment,
495 );
496 try std.testing.expectEqual(
497 std.mem.Alignment.@"8",
498 Operation.binary_storage_capacity.allocation_alignment,
499 );
500 try std.testing.expectEqual(@as(usize, 21_520), Operation.StorageAllocator.first_chunk_bytes);
501 try std.testing.expectEqual(@as(usize, 24_080), Operation.StorageAllocator.second_chunk_bytes);
502 try std.testing.expectEqual(@as(usize, 20_496), Operation.StorageAllocator.third_chunk_bytes);
503 try std.testing.expectEqual(@as(usize, 22_288), Operation.StorageAllocator.fourth_chunk_bytes);
504 }