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 };