lib/choir/src/core/value.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const Type = @import("type.zig").Type;
  3 
  4 pub const Value = struct {
  5     kind: Kind,
  6 
  7     type: Type,
  8 
  9     id: u32,
 10 
 11     first_use: ?*OpOperand = null,
 12 
 13     pub const Kind = union(enum) {
 14         block_argument: BlockArgumentInfo,
 15 
 16         op_result: OpResultInfo,
 17     };
 18 
 19     pub const BlockArgumentInfo = struct {
 20         owner: *anyopaque,
 21         arg_number: u32,
 22     };
 23 
 24     pub const OpResultInfo = struct {
 25         owner: *anyopaque,
 26         result_number: u32,
 27     };
 28 
 29     pub fn isBlockArgument(self: Value) bool {
 30         return self.kind == .block_argument;
 31     }
 32 
 33     pub fn isOpResult(self: Value) bool {
 34         return self.kind == .op_result;
 35     }
 36 
 37     pub fn getDefiningOp(self: Value) ?*anyopaque {
 38         return switch (self.kind) {
 39             .op_result => |info| info.owner,
 40             .block_argument => null,
 41         };
 42     }
 43 
 44     pub fn getOwnerBlock(self: Value) ?*anyopaque {
 45         return switch (self.kind) {
 46             .block_argument => |info| info.owner,
 47             .op_result => null,
 48         };
 49     }
 50 
 51     pub fn hasNoUses(self: Value) bool {
 52         return self.first_use == null;
 53     }
 54 
 55     pub fn hasOneUse(self: Value) bool {
 56         const first = self.first_use orelse return false;
 57         return first.next_use == null;
 58     }
 59 
 60     pub fn getNumUses(self: Value) usize {
 61         var count: usize = 0;
 62         var use = self.first_use;
 63         while (use) |u| {
 64             count += 1;
 65             use = u.next_use;
 66         }
 67         return count;
 68     }
 69 
 70     pub fn useIterator(self: *Value) UseIterator {
 71         return .{ .current = self.first_use };
 72     }
 73 
 74     pub fn addUse(self: *Value, operand: *OpOperand) void {
 75         operand.next_use = self.first_use;
 76         if (self.first_use) |first| {
 77             first.back = &operand.next_use;
 78         }
 79         self.first_use = operand;
 80         operand.back = &self.first_use;
 81     }
 82 
 83     pub fn dropAllUses(self: *Value) void {
 84         while (self.first_use) |op_operand| {
 85             op_operand.detach();
 86         }
 87     }
 88 
 89     pub fn replaceAllUsesWith(self: *Value, replacement: *Value) void {
 90         if (self == replacement) return;
 91         var uses = self.useIterator();
 92         while (uses.next()) |op_operand| {
 93             op_operand.setValue(replacement);
 94         }
 95     }
 96 
 97     pub fn format(self: Value, writer: *std.Io.Writer) std.Io.Writer.Error!void {
 98         try writer.print("%{d}", .{self.id});
 99     }
100 };
101 
102 pub const UseIterator = struct {
103     current: ?*OpOperand,
104 
105     pub fn next(self: *UseIterator) ?*OpOperand {
106         const current = self.current orelse return null;
107         self.current = current.next_use;
108         return current;
109     }
110 };
111 
112 test "Value.dropAllUses detaches every operand use" {
113     const storage = Type.DialectTypeStorage{
114         .name = "test.value",
115         .param_key = "",
116         .type_info = null,
117         .print_fn = null,
118         .unique_id = 1,
119     };
120     const ty = Type{ .type_id = .dialect_type, .impl = &storage };
121     var value_owner: u8 = 0;
122     var value = Value{
123         .kind = .{ .op_result = .{
124             .owner = &value_owner,
125             .result_number = 0,
126         } },
127         .type = ty,
128         .id = 1,
129     };
130 
131     var owner_a: u8 = 0;
132     var owner_b: u8 = 0;
133     var operand_a = OpOperand{
134         .value = &value,
135         .owner = &owner_a,
136         .operand_number = 0,
137         .operand_value_slot = null,
138         .next_use = null,
139     };
140     var operand_b = OpOperand{
141         .value = &value,
142         .owner = &owner_b,
143         .operand_number = 1,
144         .operand_value_slot = null,
145         .next_use = null,
146     };
147 
148     operand_a.attach();
149     operand_b.attach();
150     try std.testing.expectEqual(@as(usize, 2), value.getNumUses());
151 
152     value.dropAllUses();
153 
154     try std.testing.expect(value.hasNoUses());
155     try std.testing.expect(operand_a.next_use == null);
156     try std.testing.expect(operand_b.next_use == null);
157     try std.testing.expect(operand_a.back == null);
158     try std.testing.expect(operand_b.back == null);
159     try std.testing.expect(operand_a.value == &value);
160     try std.testing.expect(operand_b.value == &value);
161 }
162 
163 test "use chain detach unlinks middle uses and stays idempotent" {
164     const storage = Type.DialectTypeStorage{
165         .name = "test.value",
166         .param_key = "",
167         .type_info = null,
168         .print_fn = null,
169         .unique_id = 1,
170     };
171     const ty = Type{ .type_id = .dialect_type, .impl = &storage };
172     var value_owner: u8 = 0;
173     var value = Value{
174         .kind = .{ .op_result = .{
175             .owner = &value_owner,
176             .result_number = 0,
177         } },
178         .type = ty,
179         .id = 1,
180     };
181 
182     var owners: [3]u8 = .{ 0, 0, 0 };
183     var operands: [3]OpOperand = undefined;
184     for (&operands, 0..) |*operand, index| {
185         operand.* = .{
186             .value = &value,
187             .owner = &owners[index],
188             .operand_number = @intCast(index),
189             .operand_value_slot = null,
190             .next_use = null,
191         };
192         operand.attach();
193     }
194 
195     try std.testing.expectEqual(@as(usize, 3), value.getNumUses());
196     try std.testing.expectEqual(&operands[2], value.first_use.?);
197 
198     operands[1].detach();
199     try std.testing.expectEqual(@as(usize, 2), value.getNumUses());
200     try std.testing.expectEqual(&operands[0], operands[2].next_use.?);
201     try std.testing.expectEqual(&operands[2].next_use, operands[0].back.?);
202 
203     operands[1].detach();
204     try std.testing.expectEqual(@as(usize, 2), value.getNumUses());
205 
206     operands[2].detach();
207     try std.testing.expectEqual(&operands[0], value.first_use.?);
208     try std.testing.expectEqual(&value.first_use, operands[0].back.?);
209 
210     operands[0].detach();
211     try std.testing.expect(value.hasNoUses());
212 }
213 
214 pub const OpOperand = struct {
215     value: *Value,
216 
217     owner: *anyopaque,
218 
219     operand_number: u32,
220 
221     operand_value_slot: ?**Value,
222 
223     next_use: ?*OpOperand,
224 
225     back: ?*?*OpOperand = null,
226 
227     pub fn attach(self: *OpOperand) void {
228         self.value.addUse(self);
229     }
230 
231     pub fn detach(self: *OpOperand) void {
232         const back = self.back orelse return;
233         back.* = self.next_use;
234         if (self.next_use) |next| {
235             next.back = back;
236         }
237         self.back = null;
238         self.next_use = null;
239     }
240 
241     pub fn setValue(self: *OpOperand, new_value: *Value) void {
242         if (self.value == new_value) return;
243         self.detach();
244         self.value = new_value;
245         self.attach();
246         if (self.operand_value_slot) |slot| {
247             slot.* = new_value;
248         }
249     }
250 };