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 }