lib/choir/src/core/symbols.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const alloc_arena = @import("alloc_arena");
3 const attribute = @import("attribute.zig");
4 const Attribute = attribute.Attribute;
5 const NamedAttribute = attribute.NamedAttribute;
6 const Operation = @import("operation/root.zig").Operation;
7 const Region = @import("region.zig").Region;
8 const interfaces = @import("interfaces/root.zig");
9
10 const AttributeReplacement = struct {
11 attr: ?Attribute = null,
12 replaced: usize = 0,
13 };
14
15 const SymbolTableSemanticError = error{
16 DuplicateSymbol,
17 InvalidSymbol,
18 InvalidSymbolTable,
19 InvalidSymbolDeclaration,
20 };
21
22 pub const SymbolTableError = SymbolTableSemanticError || std.mem.Allocator.Error;
23
24 pub const SymbolUse = struct {
25 user: *Operation,
26 attr_name: []const u8,
27 symbol_ref: *const Attribute.SymbolRefAttr,
28 };
29
30 pub const SymbolUseRange = struct {
31 uses: std.ArrayList(SymbolUse) = .empty,
32
33 pub fn deinit(self: *SymbolUseRange, allocator: std.mem.Allocator) void {
34 self.uses.deinit(allocator);
35 self.* = .{};
36 }
37
38 pub fn items(self: *const SymbolUseRange) []const SymbolUse {
39 return self.uses.items;
40 }
41
42 pub fn empty(self: *const SymbolUseRange) bool {
43 return self.uses.items.len == 0;
44 }
45
46 fn append(self: *SymbolUseRange, allocator: std.mem.Allocator, use: SymbolUse) !void {
47 try self.uses.append(allocator, use);
48 }
49 };
50
51 pub const SymbolTable = struct {
52 allocator: std.mem.Allocator,
53 symbols: std.StringHashMapUnmanaged(*Operation),
54
55 pub const symbol_attr_names = struct {
56 pub const sym_name = "sym_name";
57 pub const sym_visibility = "sym_visibility";
58 };
59
60 pub const Visibility = enum {
61 public,
62 private,
63 nested,
64
65 pub fn fromString(value: []const u8) ?Visibility {
66 if (std.mem.eql(u8, value, "public")) return .public;
67 if (std.mem.eql(u8, value, "private")) return .private;
68 if (std.mem.eql(u8, value, "nested")) return .nested;
69 return null;
70 }
71
72 pub fn toString(self: Visibility) []const u8 {
73 return @tagName(self);
74 }
75 };
76
77 pub const Collection = struct {
78 allocator: std.mem.Allocator,
79 tables: std.AutoArrayHashMapUnmanaged(*Operation, SymbolTable) = .empty,
80
81 pub fn init(allocator: std.mem.Allocator) Collection {
82 return .{ .allocator = allocator };
83 }
84
85 pub fn deinit(self: *Collection) void {
86 for (self.tables.values()) |*table| {
87 table.deinit();
88 }
89 self.tables.deinit(self.allocator);
90 self.tables = .empty;
91 }
92
93 pub fn getSymbolTable(self: *Collection, op: *Operation) SymbolTableError!*SymbolTable {
94 if (!isSymbolTableOperation(op)) return error.InvalidSymbolTable;
95
96 const entry = try self.tables.getOrPut(self.allocator, op);
97 if (!entry.found_existing) {
98 entry.value_ptr.* = SymbolTable.init(self.allocator);
99 entry.value_ptr.buildFromOperation(op) catch |err| {
100 entry.value_ptr.deinit();
101 _ = self.tables.swapRemove(op);
102 return err;
103 };
104 }
105 return entry.value_ptr;
106 }
107
108 pub fn invalidateSymbolTable(self: *Collection, op: *Operation) void {
109 if (self.tables.getPtr(op)) |table| {
110 table.deinit();
111 _ = self.tables.swapRemove(op);
112 }
113 }
114
115 pub fn lookupSymbolIn(
116 self: *Collection,
117 op: *Operation,
118 name: []const u8,
119 ) SymbolTableError!?*Operation {
120 if (!isSymbolTableOperation(op)) return null;
121 const table = try self.getSymbolTable(op);
122 return table.lookup(name);
123 }
124
125 pub fn lookupSymbolRefIn(
126 self: *Collection,
127 op: *Operation,
128 symbol_ref: *const Attribute.SymbolRefAttr,
129 ) SymbolTableError!?*Operation {
130 var resolved = try self.lookupSymbolIn(op, symbol_ref.getRootReference()) orelse return null;
131 for (symbol_ref.getNestedReferences()) |nested_ref| {
132 resolved = try self.lookupSymbolIn(resolved, nested_ref) orelse return null;
133 if (getSymbolVisibility(resolved) == .private) return null;
134 }
135 return resolved;
136 }
137
138 pub fn lookupNearestSymbolFrom(
139 self: *Collection,
140 from: *Operation,
141 name: []const u8,
142 ) SymbolTableError!?*Operation {
143 const table_op = getNearestSymbolTable(from) orelse return null;
144 return self.lookupSymbolIn(table_op, name);
145 }
146
147 pub fn lookupNearestSymbolRefFrom(
148 self: *Collection,
149 from: *Operation,
150 symbol_ref: *const Attribute.SymbolRefAttr,
151 ) SymbolTableError!?*Operation {
152 const table_op = getNearestSymbolTable(from) orelse return null;
153 return self.lookupSymbolRefIn(table_op, symbol_ref);
154 }
155 };
156
157 pub fn init(allocator: std.mem.Allocator) SymbolTable {
158 return .{
159 .allocator = allocator,
160 .symbols = .{},
161 };
162 }
163
164 pub fn deinit(self: *SymbolTable) void {
165 self.symbols.deinit(self.allocator);
166 }
167
168 pub fn clearRetainingCapacity(self: *SymbolTable) void {
169 self.symbols.clearRetainingCapacity();
170 }
171
172 pub fn lookup(self: *const SymbolTable, name: []const u8) ?*Operation {
173 return self.symbols.get(name);
174 }
175
176 pub fn iterator(self: *SymbolTable) std.StringHashMapUnmanaged(*Operation).Iterator {
177 return self.symbols.iterator();
178 }
179
180 pub fn buildFromOperation(self: *SymbolTable, op: *Operation) SymbolTableError!void {
181 self.clearRetainingCapacity();
182 if (!isSymbolTableOperation(op)) return error.InvalidSymbolTable;
183 if (op.regions.items.len != 1) return error.InvalidSymbolTable;
184 const region = &op.regions.items[0];
185 if (region.blocks.size != 1) return error.InvalidSymbolTable;
186 try self.appendFromRegion(region);
187 }
188
189 fn appendFromRegion(self: *SymbolTable, region: *Region) SymbolTableError!void {
190 const block = region.blocks.front() orelse return;
191 var ops = block.getOperations();
192 while (ops.next()) |op| {
193 if (getSymbolName(op)) |name| {
194 try self.addSymbol(name, op);
195 }
196 }
197 }
198
199 fn addSymbol(self: *SymbolTable, name: []const u8, op: *Operation) SymbolTableError!void {
200 const entry = try self.symbols.getOrPut(self.allocator, name);
201 if (entry.found_existing) return error.DuplicateSymbol;
202 entry.value_ptr.* = op;
203 }
204
205 pub fn isSymbolTableOperation(op: *Operation) bool {
206 return op.getTraits().is_symbol_table;
207 }
208
209 pub fn isSymbolOperation(op: *Operation) bool {
210 return op.interface(interfaces.SymbolOpInterface) != null;
211 }
212
213 pub fn verifyOperation(op: *Operation) anyerror!void {
214 var symbol_tables = Collection.init(op.allocator);
215 defer symbol_tables.deinit();
216 _ = try symbol_tables.getSymbolTable(op);
217 try verifySymbols(op);
218 try verifySymbolUsesInSymbolTable(&symbol_tables, op);
219 }
220
221 fn verifySymbols(op: *Operation) SymbolTableError!void {
222 if (!isSymbolTableOperation(op)) return error.InvalidSymbolTable;
223 if (op.regions.items.len != 1) return error.InvalidSymbolTable;
224 const region = &op.regions.items[0];
225 if (region.blocks.size != 1) return error.InvalidSymbolTable;
226 const block = region.blocks.front() orelse return;
227 var ops = block.getOperations();
228 while (ops.next()) |candidate| {
229 if (!isSymbolOperation(candidate)) continue;
230 if (isDeclaration(candidate) and getSymbolVisibility(candidate) == .public) {
231 return error.InvalidSymbolDeclaration;
232 }
233 }
234 }
235
236 pub fn lookupSymbolIn(op: *Operation, name: []const u8) ?*Operation {
237 if (!isSymbolTableOperation(op)) return null;
238 if (op.regions.items.len == 0) return null;
239 const region = &op.regions.items[0];
240 const block = region.blocks.front() orelse return null;
241 var ops = block.getOperations();
242 while (ops.next()) |candidate| {
243 if (getSymbolName(candidate)) |symbol_name| {
244 if (std.mem.eql(u8, symbol_name, name)) return candidate;
245 }
246 }
247 return null;
248 }
249
250 pub fn lookupSymbolRefIn(op: *Operation, symbol_ref: *const Attribute.SymbolRefAttr) ?*Operation {
251 var resolved = lookupSymbolIn(op, symbol_ref.getRootReference()) orelse return null;
252 for (symbol_ref.getNestedReferences()) |nested_ref| {
253 resolved = lookupSymbolIn(resolved, nested_ref) orelse return null;
254 if (getSymbolVisibility(resolved) == .private) return null;
255 }
256 return resolved;
257 }
258
259 pub fn getNearestSymbolTable(from: *Operation) ?*Operation {
260 var current: ?*Operation = from;
261 while (current) |op| {
262 if (isSymbolTableOperation(op)) return op;
263 current = op.getParentOp();
264 }
265 return null;
266 }
267
268 pub fn lookupNearestSymbolFrom(from: *Operation, name: []const u8) ?*Operation {
269 const table_op = getNearestSymbolTable(from) orelse return null;
270 return lookupSymbolIn(table_op, name);
271 }
272
273 pub fn lookupNearestSymbolRefFrom(from: *Operation, symbol_ref: *const Attribute.SymbolRefAttr) ?*Operation {
274 const table_op = getNearestSymbolTable(from) orelse return null;
275 return lookupSymbolRefIn(table_op, symbol_ref);
276 }
277
278 pub fn collectSymbolUses(
279 allocator: std.mem.Allocator,
280 from: *Operation,
281 ) anyerror!SymbolUseRange {
282 var range = SymbolUseRange{};
283 errdefer range.deinit(allocator);
284 try appendSymbolUsesFromOperation(allocator, &range, from, null);
285 return range;
286 }
287
288 pub fn collectSymbolUsesInRegion(
289 allocator: std.mem.Allocator,
290 from: *Region,
291 ) anyerror!SymbolUseRange {
292 var range = SymbolUseRange{};
293 errdefer range.deinit(allocator);
294 try appendSymbolUsesFromRegion(allocator, &range, from, null);
295 return range;
296 }
297
298 pub fn collectSymbolUsesInSymbolTable(
299 allocator: std.mem.Allocator,
300 op: *Operation,
301 ) anyerror!SymbolUseRange {
302 if (!isSymbolTableOperation(op)) return error.InvalidSymbolTable;
303 var range = SymbolUseRange{};
304 errdefer range.deinit(allocator);
305 for (op.regions.items) |*region| {
306 try appendSymbolUsesFromRegion(allocator, &range, region, null);
307 }
308 return range;
309 }
310
311 pub fn collectSymbolUsesOf(
312 allocator: std.mem.Allocator,
313 from: *Operation,
314 symbol_ref: *const Attribute.SymbolRefAttr,
315 ) anyerror!SymbolUseRange {
316 var range = SymbolUseRange{};
317 errdefer range.deinit(allocator);
318 try appendSymbolUsesFromOperation(allocator, &range, from, symbol_ref);
319 return range;
320 }
321
322 pub fn collectSymbolUsesOfInRegion(
323 allocator: std.mem.Allocator,
324 from: *Region,
325 symbol_ref: *const Attribute.SymbolRefAttr,
326 ) anyerror!SymbolUseRange {
327 var range = SymbolUseRange{};
328 errdefer range.deinit(allocator);
329 try appendSymbolUsesFromRegion(allocator, &range, from, symbol_ref);
330 return range;
331 }
332
333 pub fn collectSymbolUsesOfInSymbolTable(
334 allocator: std.mem.Allocator,
335 op: *Operation,
336 symbol_ref: *const Attribute.SymbolRefAttr,
337 ) anyerror!SymbolUseRange {
338 if (!isSymbolTableOperation(op)) return error.InvalidSymbolTable;
339 var range = SymbolUseRange{};
340 errdefer range.deinit(allocator);
341 for (op.regions.items) |*region| {
342 try appendSymbolUsesFromRegion(allocator, &range, region, symbol_ref);
343 }
344 return range;
345 }
346
347 pub fn symbolKnownUseEmpty(
348 allocator: std.mem.Allocator,
349 from: *Operation,
350 symbol_ref: *const Attribute.SymbolRefAttr,
351 ) anyerror!bool {
352 var uses = try collectSymbolUsesOf(allocator, from, symbol_ref);
353 defer uses.deinit(allocator);
354 return uses.empty();
355 }
356
357 pub fn symbolKnownUseEmptyInRegion(
358 allocator: std.mem.Allocator,
359 from: *Region,
360 symbol_ref: *const Attribute.SymbolRefAttr,
361 ) anyerror!bool {
362 var uses = try collectSymbolUsesOfInRegion(allocator, from, symbol_ref);
363 defer uses.deinit(allocator);
364 return uses.empty();
365 }
366
367 pub fn symbolKnownUseEmptyInSymbolTable(
368 allocator: std.mem.Allocator,
369 op: *Operation,
370 symbol_ref: *const Attribute.SymbolRefAttr,
371 ) anyerror!bool {
372 var uses = try collectSymbolUsesOfInSymbolTable(allocator, op, symbol_ref);
373 defer uses.deinit(allocator);
374 return uses.empty();
375 }
376
377 pub fn replaceAllSymbolUsesInRegion(
378 allocator: std.mem.Allocator,
379 from: *Region,
380 old_ref: *const Attribute.SymbolRefAttr,
381 new_leaf: []const u8,
382 ) anyerror!usize {
383 return replaceSymbolUsesInRegion(allocator, from, old_ref, new_leaf);
384 }
385
386 pub fn replaceAllSymbolUsesInSymbolTable(
387 allocator: std.mem.Allocator,
388 op: *Operation,
389 old_ref: *const Attribute.SymbolRefAttr,
390 new_leaf: []const u8,
391 ) anyerror!usize {
392 if (!isSymbolTableOperation(op)) return error.InvalidSymbolTable;
393 var replaced: usize = 0;
394 for (op.regions.items) |*region| {
395 replaced += try replaceSymbolUsesInRegion(allocator, region, old_ref, new_leaf);
396 }
397 return replaced;
398 }
399
400 pub fn renameSymbolInSymbolTable(
401 allocator: std.mem.Allocator,
402 op: *Operation,
403 symbol: *Operation,
404 new_name: []const u8,
405 ) anyerror!usize {
406 if (!isSymbolTableOperation(op)) return error.InvalidSymbolTable;
407 if (symbol.getParentOp() != op) return error.InvalidSymbolTable;
408 const old_name = getSymbolName(symbol) orelse return error.InvalidSymbol;
409 if (std.mem.eql(u8, old_name, new_name)) return 0;
410 if (lookupSymbolIn(op, new_name)) |existing| {
411 if (existing != symbol) return error.DuplicateSymbol;
412 }
413
414 var symbol_tables = Collection.init(allocator);
415 defer symbol_tables.deinit();
416 var users = try SymbolUserMap.init(allocator, &symbol_tables, op);
417 defer users.deinit();
418 const replaced = try users.replaceAllUsesWith(symbol, new_name);
419 try setSymbolName(symbol, new_name);
420 symbol_tables.invalidateSymbolTable(op);
421 return replaced;
422 }
423
424 pub fn getSymbolName(op: *Operation) ?[]const u8 {
425 const iface = op.interface(interfaces.SymbolOpInterface) orelse return null;
426 return iface.call(.getSymbolName, .{});
427 }
428
429 pub fn setSymbolName(op: *Operation, name: []const u8) anyerror!void {
430 const iface = op.interface(interfaces.SymbolOpInterface) orelse return error.InvalidSymbol;
431 try iface.call(.setSymbolName, .{name});
432 }
433
434 pub fn isDeclaration(op: *Operation) bool {
435 const iface = op.interface(interfaces.SymbolOpInterface) orelse return false;
436 return iface.call(.isDeclaration, .{});
437 }
438
439 pub fn getSymbolVisibility(op: *const Operation) Visibility {
440 const string_attr = op.getAttrAs(Attribute.StringAttr, symbol_attr_names.sym_visibility) orelse return .public;
441 return Visibility.fromString(string_attr.getValue()) orelse .public;
442 }
443
444 pub fn setSymbolVisibility(op: *Operation, visibility: Visibility) anyerror!void {
445 if (!isSymbolOperation(op)) return error.InvalidSymbol;
446 if (visibility == .public) {
447 if (isDeclaration(op)) return error.InvalidSymbolDeclaration;
448 _ = op.removeAttr(symbol_attr_names.sym_visibility);
449 return;
450 }
451 try op.setAttr(
452 symbol_attr_names.sym_visibility,
453 try op.getContext().getStringAttr(visibility.toString()),
454 );
455 }
456
457 fn appendSymbolRefsFromOperation(
458 allocator: std.mem.Allocator,
459 range: *SymbolUseRange,
460 op: *Operation,
461 target_ref: ?*const Attribute.SymbolRefAttr,
462 ) anyerror!void {
463 var attrs = op.getAttrs();
464 while (attrs.next()) |attr| {
465 try appendSymbolRefsFromAttribute(allocator, range, op, attr.name, attr.value, target_ref);
466 }
467 }
468
469 fn appendSymbolRefsFromAttribute(
470 allocator: std.mem.Allocator,
471 range: *SymbolUseRange,
472 op: *Operation,
473 attr_name: []const u8,
474 attr: Attribute,
475 target_ref: ?*const Attribute.SymbolRefAttr,
476 ) anyerror!void {
477 if (getSymbolRefAttr(attr)) |symbol_ref| {
478 if (target_ref) |target| {
479 if (!isReferencePrefixOf(target, symbol_ref)) return;
480 }
481 try range.append(allocator, .{
482 .user = op,
483 .attr_name = attr_name,
484 .symbol_ref = symbol_ref,
485 });
486 return;
487 }
488
489 const array_attr = getArrayAttr(attr) orelse return;
490 for (array_attr.values) |child| {
491 try appendSymbolRefsFromAttribute(allocator, range, op, attr_name, child, target_ref);
492 }
493 }
494
495 fn appendSymbolUsesFromOperation(
496 allocator: std.mem.Allocator,
497 range: *SymbolUseRange,
498 op: *Operation,
499 target_ref: ?*const Attribute.SymbolRefAttr,
500 ) anyerror!void {
501 var state = SymbolUseWalkState{
502 .allocator = allocator,
503 .range = range,
504 .target_ref = target_ref,
505 };
506 _ = try op.walk(.{ .order = .pre_order }, &state, SymbolUseWalkState.visit);
507 }
508
509 fn verifySymbolUsesInSymbolTable(symbol_tables: *Collection, op: *Operation) anyerror!void {
510 if (!isSymbolTableOperation(op)) return error.InvalidSymbolTable;
511 for (op.regions.items) |*region| {
512 try verifySymbolUsesInRegion(symbol_tables, region);
513 }
514 }
515
516 fn verifySymbolUsesFromOperation(symbol_tables: *Collection, op: *Operation) anyerror!void {
517 var state = VerifySymbolUsesWalkState{ .symbol_tables = symbol_tables };
518 _ = try op.walk(.{ .order = .pre_order }, &state, VerifySymbolUsesWalkState.visit);
519 }
520
521 fn verifySymbolUsesOnOperation(symbol_tables: *Collection, op: *Operation) anyerror!void {
522 if (op.interface(interfaces.SymbolUserOpInterface)) |iface| {
523 try iface.call(.verifySymbolUses, .{symbol_tables});
524 }
525 var attrs = op.getDiscardableAttrs();
526 while (attrs.next()) |attr| {
527 if (attr.value.interface(interfaces.SymbolUserAttrInterface)) |iface| {
528 try iface.call(.verifySymbolUses, .{ op, symbol_tables });
529 }
530 }
531 }
532
533 fn verifySymbolUsesInRegion(symbol_tables: *Collection, region: *Region) anyerror!void {
534 var state = VerifySymbolUsesWalkState{ .symbol_tables = symbol_tables };
535 _ = try region.walkOperations(.{ .order = .pre_order }, &state, VerifySymbolUsesWalkState.visit);
536 }
537
538 fn appendSymbolUsesFromRegion(
539 allocator: std.mem.Allocator,
540 range: *SymbolUseRange,
541 region: *Region,
542 target_ref: ?*const Attribute.SymbolRefAttr,
543 ) anyerror!void {
544 var state = SymbolUseWalkState{
545 .allocator = allocator,
546 .range = range,
547 .target_ref = target_ref,
548 };
549 _ = try region.walkOperations(.{ .order = .pre_order }, &state, SymbolUseWalkState.visit);
550 }
551
552 fn replaceSymbolUsesOnOperation(
553 allocator: std.mem.Allocator,
554 op: *Operation,
555 old_ref: *const Attribute.SymbolRefAttr,
556 new_leaf: []const u8,
557 ) anyerror!usize {
558 var replaced: usize = 0;
559 var inline_snapshot: [4]NamedAttribute = undefined;
560 const attr_count = op.getNumAttrs();
561 const attr_snapshot = if (attr_count <= inline_snapshot.len)
562 inline_snapshot[0..attr_count]
563 else
564 try allocator.alloc(NamedAttribute, attr_count);
565 defer if (attr_count > inline_snapshot.len) allocator.free(attr_snapshot);
566 var attrs = op.getAttrs();
567 var attr_index: usize = 0;
568 while (attrs.next()) |attr| : (attr_index += 1) {
569 if (attr_index >= attr_snapshot.len) return error.OperationAttributesChanged;
570 attr_snapshot[attr_index] = attr;
571 }
572 if (attr_index != attr_snapshot.len) return error.OperationAttributesChanged;
573 for (attr_snapshot) |attr| {
574 const result = try replaceSymbolUsesInAttribute(allocator, op, old_ref, attr.value, new_leaf);
575 const replacement = result.attr orelse continue;
576 try op.setAttr(attr.name, replacement);
577 replaced += result.replaced;
578 }
579 return replaced;
580 }
581
582 fn replaceSymbolUsesInAttribute(
583 allocator: std.mem.Allocator,
584 op: *Operation,
585 old_ref: *const Attribute.SymbolRefAttr,
586 attr: Attribute,
587 new_leaf: []const u8,
588 ) anyerror!AttributeReplacement {
589 if (getSymbolRefAttr(attr)) |symbol_ref| {
590 const replacement = try replacementSymbolRefAttr(allocator, op, old_ref, symbol_ref, new_leaf) orelse return .{};
591 return .{ .attr = replacement, .replaced = 1 };
592 }
593
594 const array_attr = getArrayAttr(attr) orelse return .{};
595 var changed = false;
596 var replaced: usize = 0;
597 const values = try allocator.alloc(Attribute, array_attr.values.len);
598 defer allocator.free(values);
599 for (array_attr.values, 0..) |child, index| {
600 const result = try replaceSymbolUsesInAttribute(allocator, op, old_ref, child, new_leaf);
601 values[index] = result.attr orelse child;
602 if (result.attr != null) changed = true;
603 replaced += result.replaced;
604 }
605 if (!changed) return .{};
606 return .{
607 .attr = try op.getContext().getArrayAttr(values),
608 .replaced = replaced,
609 };
610 }
611
612 fn replaceSymbolUsesInOperation(
613 allocator: std.mem.Allocator,
614 op: *Operation,
615 old_ref: *const Attribute.SymbolRefAttr,
616 new_leaf: []const u8,
617 ) anyerror!usize {
618 var state = ReplaceSymbolUsesWalkState{
619 .allocator = allocator,
620 .old_ref = old_ref,
621 .new_leaf = new_leaf,
622 };
623 _ = try op.walk(.{ .order = .pre_order }, &state, ReplaceSymbolUsesWalkState.visit);
624 return state.replaced;
625 }
626
627 fn replaceSymbolUsesInRegion(
628 allocator: std.mem.Allocator,
629 region: *Region,
630 old_ref: *const Attribute.SymbolRefAttr,
631 new_leaf: []const u8,
632 ) anyerror!usize {
633 var state = ReplaceSymbolUsesWalkState{
634 .allocator = allocator,
635 .old_ref = old_ref,
636 .new_leaf = new_leaf,
637 };
638 _ = try region.walkOperations(.{ .order = .pre_order }, &state, ReplaceSymbolUsesWalkState.visit);
639 return state.replaced;
640 }
641
642 fn replacementSymbolRefAttr(
643 allocator: std.mem.Allocator,
644 op: *Operation,
645 old_ref: *const Attribute.SymbolRefAttr,
646 current_ref: *const Attribute.SymbolRefAttr,
647 new_leaf: []const u8,
648 ) anyerror!?Attribute {
649 if (!isReferencePrefixOf(old_ref, current_ref)) return null;
650 if (current_ref.getNestedReferences().len == 0) {
651 return try op.getContext().getFlatSymbolRefAttr(new_leaf);
652 }
653 if (old_ref.getNestedReferences().len == 0) {
654 return try op.getContext().getSymbolRefAttr(new_leaf, current_ref.getNestedReferences());
655 }
656
657 const nested = try allocator.dupe([]const u8, current_ref.getNestedReferences());
658 defer allocator.free(nested);
659 nested[old_ref.getNestedReferences().len - 1] = new_leaf;
660 return try op.getContext().getSymbolRefAttr(current_ref.getRootReference(), nested);
661 }
662
663 fn symbolUsePrefix(use: SymbolUse, nested_len: usize) anyerror!SymbolUse {
664 const attr = if (nested_len == 0)
665 try use.user.getContext().getFlatSymbolRefAttr(use.symbol_ref.getRootReference())
666 else
667 try use.user.getContext().getSymbolRefAttr(
668 use.symbol_ref.getRootReference(),
669 use.symbol_ref.getNestedReferences()[0..nested_len],
670 );
671 const symbol_ref = attr.cast(Attribute.SymbolRefAttr).?;
672 return .{
673 .user = use.user,
674 .attr_name = use.attr_name,
675 .symbol_ref = symbol_ref,
676 };
677 }
678
679 fn sameUse(a: SymbolUse, b: SymbolUse) bool {
680 return a.user == b.user and
681 std.mem.eql(u8, a.attr_name, b.attr_name) and
682 symbolRefsEqual(a.symbol_ref, b.symbol_ref);
683 }
684
685 fn symbolRefsEqual(a: *const Attribute.SymbolRefAttr, b: *const Attribute.SymbolRefAttr) bool {
686 if (a == b) return true;
687 if (!std.mem.eql(u8, a.getRootReference(), b.getRootReference())) return false;
688 const a_nested = a.getNestedReferences();
689 const b_nested = b.getNestedReferences();
690 if (a_nested.len != b_nested.len) return false;
691 for (a_nested, b_nested) |a_ref, b_ref| {
692 if (!std.mem.eql(u8, a_ref, b_ref)) return false;
693 }
694 return true;
695 }
696
697 fn isReferencePrefixOf(prefix: *const Attribute.SymbolRefAttr, symbol_ref: *const Attribute.SymbolRefAttr) bool {
698 if (!std.mem.eql(u8, prefix.getRootReference(), symbol_ref.getRootReference())) return false;
699 const prefix_nested = prefix.getNestedReferences();
700 const ref_nested = symbol_ref.getNestedReferences();
701 if (prefix_nested.len > ref_nested.len) return false;
702 for (prefix_nested, 0..) |nested, index| {
703 if (!std.mem.eql(u8, nested, ref_nested[index])) return false;
704 }
705 return true;
706 }
707
708 fn getSymbolRefAttr(attr: Attribute) ?*const Attribute.SymbolRefAttr {
709 if (!std.mem.eql(u8, attr.abstract.name, attribute.builtin_attr_names.symbol_ref)) return null;
710 return attr.cast(Attribute.SymbolRefAttr);
711 }
712
713 fn getArrayAttr(attr: Attribute) ?*const Attribute.ArrayAttr {
714 if (!std.mem.eql(u8, attr.abstract.name, attribute.builtin_attr_names.array)) return null;
715 return attr.cast(Attribute.ArrayAttr);
716 }
717 };
718
719 const SymbolUserSet = struct {
720 users: std.ArrayListUnmanaged(*Operation) = .empty,
721 uses: std.ArrayListUnmanaged(SymbolUse) = .empty,
722
723 fn deinit(self: *SymbolUserSet, allocator: std.mem.Allocator) void {
724 self.users.deinit(allocator);
725 self.uses.deinit(allocator);
726 self.* = .{};
727 }
728
729 fn append(self: *SymbolUserSet, allocator: std.mem.Allocator, use: SymbolUse) !void {
730 var found_user = false;
731 for (self.users.items) |user| {
732 if (user == use.user) {
733 found_user = true;
734 break;
735 }
736 }
737 if (!found_user) try self.users.append(allocator, use.user);
738
739 for (self.uses.items) |existing| {
740 if (SymbolTable.sameUse(existing, use)) return;
741 }
742 try self.uses.append(allocator, use);
743 }
744
745 fn appendAll(
746 self: *SymbolUserSet,
747 allocator: std.mem.Allocator,
748 other: *const SymbolUserSet,
749 ) !void {
750 for (other.uses.items) |use| {
751 try self.append(allocator, use);
752 }
753 }
754 };
755
756 pub const SymbolUserMap = struct {
757 allocator: std.mem.Allocator,
758 symbol_tables: *SymbolTable.Collection,
759 symbol_users: std.AutoArrayHashMapUnmanaged(*Operation, SymbolUserSet) = .empty,
760
761 pub fn init(
762 allocator: std.mem.Allocator,
763 symbol_tables: *SymbolTable.Collection,
764 symbol_table_op: *Operation,
765 ) anyerror!SymbolUserMap {
766 if (!SymbolTable.isSymbolTableOperation(symbol_table_op)) return error.InvalidSymbolTable;
767 var self = SymbolUserMap{
768 .allocator = allocator,
769 .symbol_tables = symbol_tables,
770 };
771 errdefer self.deinit();
772 try self.appendFromSymbolTable(symbol_table_op);
773 return self;
774 }
775
776 pub fn deinit(self: *SymbolUserMap) void {
777 for (self.symbol_users.values()) |*users| {
778 users.deinit(self.allocator);
779 }
780 self.symbol_users.deinit(self.allocator);
781 self.symbol_users = .empty;
782 }
783
784 pub fn getUsers(self: *SymbolUserMap, symbol: *Operation) []const *Operation {
785 if (self.symbol_users.getPtr(symbol)) |users| {
786 return users.users.items;
787 }
788 return &.{};
789 }
790
791 pub fn useEmpty(self: *SymbolUserMap, symbol: *Operation) bool {
792 return self.getUsers(symbol).len == 0;
793 }
794
795 pub fn replaceAllUsesWith(
796 self: *SymbolUserMap,
797 symbol: *Operation,
798 new_name: []const u8,
799 ) anyerror!usize {
800 const old_name = SymbolTable.getSymbolName(symbol) orelse return error.InvalidSymbol;
801 if (std.mem.eql(u8, old_name, new_name)) return 0;
802
803 const users = self.symbol_users.getPtr(symbol) orelse return 0;
804 var replaced: usize = 0;
805 for (users.uses.items) |*use| {
806 const replacement = try SymbolTable.replacementSymbolRefAttr(self.allocator, use.user, use.symbol_ref, use.symbol_ref, new_name) orelse continue;
807 const replacement_ref = replacement.cast(Attribute.SymbolRefAttr).?;
808 replaced += try SymbolTable.replaceSymbolUsesOnOperation(self.allocator, use.user, use.symbol_ref, new_name);
809 use.symbol_ref = replacement_ref;
810 }
811
812 const new_symbol = blk: {
813 const parent = symbol.getParentOp() orelse break :blk null;
814 break :blk try self.symbol_tables.lookupSymbolIn(parent, new_name);
815 };
816 try self.moveUsers(symbol, new_symbol);
817 return replaced;
818 }
819
820 fn appendFromSymbolTable(self: *SymbolUserMap, symbol_table_op: *Operation) anyerror!void {
821 var uses = try SymbolTable.collectSymbolUsesInSymbolTable(self.allocator, symbol_table_op);
822 defer uses.deinit(self.allocator);
823
824 for (uses.items()) |use| {
825 try self.appendResolvedUse(symbol_table_op, use);
826 }
827
828 try self.appendNestedSymbolTables(symbol_table_op);
829 }
830
831 fn appendNestedSymbolTables(self: *SymbolUserMap, op: *Operation) anyerror!void {
832 var state = NestedSymbolTableWalkState{ .user_map = self };
833 for (op.regions.items) |*region| {
834 _ = try region.walkOperations(.{ .order = .pre_order }, &state, NestedSymbolTableWalkState.visit);
835 }
836 }
837
838 fn appendResolvedUse(
839 self: *SymbolUserMap,
840 symbol_table_op: *Operation,
841 use: SymbolUse,
842 ) anyerror!void {
843 var resolved = try self.symbol_tables.lookupSymbolIn(symbol_table_op, use.symbol_ref.getRootReference()) orelse return;
844 try self.appendSymbolUse(resolved, try SymbolTable.symbolUsePrefix(use, 0));
845
846 for (use.symbol_ref.getNestedReferences(), 0..) |nested_ref, index| {
847 resolved = try self.symbol_tables.lookupSymbolIn(resolved, nested_ref) orelse return;
848 if (SymbolTable.getSymbolVisibility(resolved) == .private) return;
849 try self.appendSymbolUse(resolved, try SymbolTable.symbolUsePrefix(use, index + 1));
850 }
851 }
852
853 fn appendSymbolUse(self: *SymbolUserMap, symbol: *Operation, use: SymbolUse) !void {
854 const entry = try self.symbol_users.getOrPut(self.allocator, symbol);
855 if (!entry.found_existing) entry.value_ptr.* = .{};
856 try entry.value_ptr.append(self.allocator, use);
857 }
858
859 fn moveUsers(self: *SymbolUserMap, old_symbol: *Operation, new_symbol: ?*Operation) !void {
860 var old_users = self.symbol_users.getPtr(old_symbol).?.*;
861 _ = self.symbol_users.swapRemove(old_symbol);
862 var transferred = false;
863 errdefer if (!transferred) old_users.deinit(self.allocator);
864
865 if (new_symbol) |target| {
866 if (self.symbol_users.getPtr(target)) |target_users| {
867 try target_users.appendAll(self.allocator, &old_users);
868 old_users.deinit(self.allocator);
869 transferred = true;
870 return;
871 }
872
873 const entry = try self.symbol_users.getOrPut(self.allocator, target);
874 entry.value_ptr.* = old_users;
875 transferred = true;
876 return;
877 }
878
879 old_users.deinit(self.allocator);
880 transferred = true;
881 }
882 };
883
884 const NestedSymbolTableWalkState = struct {
885 user_map: *SymbolUserMap,
886
887 fn visit(self: *@This(), op: *Operation) anyerror!Operation.WalkResult {
888 if (!SymbolTable.isSymbolTableOperation(op)) return .advance;
889 try self.user_map.appendFromSymbolTable(op);
890 return .skip;
891 }
892 };
893
894 const SymbolUseWalkState = struct {
895 allocator: std.mem.Allocator,
896 range: *SymbolUseRange,
897 target_ref: ?*const Attribute.SymbolRefAttr,
898
899 fn visit(self: *@This(), op: *Operation) anyerror!Operation.WalkResult {
900 try SymbolTable.appendSymbolRefsFromOperation(self.allocator, self.range, op, self.target_ref);
901 if (SymbolTable.isSymbolTableOperation(op)) return .skip;
902 return .advance;
903 }
904 };
905
906 const VerifySymbolUsesWalkState = struct {
907 symbol_tables: *SymbolTable.Collection,
908
909 fn visit(self: *@This(), op: *Operation) anyerror!Operation.WalkResult {
910 try SymbolTable.verifySymbolUsesOnOperation(self.symbol_tables, op);
911 if (SymbolTable.isSymbolTableOperation(op)) return .skip;
912 return .advance;
913 }
914 };
915
916 const ReplaceSymbolUsesWalkState = struct {
917 allocator: std.mem.Allocator,
918 old_ref: *const Attribute.SymbolRefAttr,
919 new_leaf: []const u8,
920 replaced: usize = 0,
921
922 fn visit(self: *@This(), op: *Operation) anyerror!Operation.WalkResult {
923 self.replaced += try SymbolTable.replaceSymbolUsesOnOperation(self.allocator, op, self.old_ref, self.new_leaf);
924 if (SymbolTable.isSymbolTableOperation(op)) return .skip;
925 return .advance;
926 }
927 };
928
929 const TestModuleSymbol = struct {
930 fn getSymbolName(op_ptr: *const anyopaque) ?[]const u8 {
931 const op: *const Operation = @ptrCast(@alignCast(op_ptr));
932 const attr = op.getAttr("sym_name") orelse return null;
933 if (!std.mem.eql(u8, attr.abstract.name, "test.string")) return null;
934 const dialect_attr = attr.cast(Attribute.DialectAttr) orelse return null;
935 return dialect_attr.payload;
936 }
937
938 fn setSymbolName(op_ptr: *const anyopaque, name: []const u8) anyerror!void {
939 const op: *Operation = @ptrCast(@alignCast(@constCast(op_ptr)));
940 try op.setAttr("sym_name", try op.getContext().getDialectAttr("test.string", name));
941 }
942
943 fn isDeclaration(_: *const anyopaque) bool {
944 return false;
945 }
946
947 const vtable = interfaces.SymbolOpInterface.VTable{
948 .getSymbolName = getSymbolName,
949 .setSymbolName = setSymbolName,
950 .isDeclaration = isDeclaration,
951 };
952 };
953
954 const TestSymbolUserAttribute = struct {
955 fn verifySymbolUses(
956 attr_ptr: *const anyopaque,
957 op: *Operation,
958 symbol_tables: *SymbolTable.Collection,
959 ) anyerror!void {
960 const attr: *const Attribute.DialectAttr = @ptrCast(@alignCast(attr_ptr));
961 _ = try symbol_tables.lookupNearestSymbolFrom(op, attr.payload) orelse return error.UnresolvedAttrSymbol;
962 }
963 };
964
965 const ReadOnlySymbolRefProperties = struct {
966 value: ?Attribute = null,
967
968 fn from(storage: *anyopaque) *@This() {
969 return @ptrCast(@alignCast(storage));
970 }
971
972 fn fromConst(storage: *const anyopaque) *const @This() {
973 return @ptrCast(@alignCast(storage));
974 }
975
976 fn init(storage: *anyopaque, _: std.mem.Allocator) anyerror!void {
977 from(storage).* = .{};
978 }
979
980 fn deinit(_: *anyopaque, _: std.mem.Allocator) void {}
981
982 fn get(_: *const Operation, storage: *const anyopaque, name: []const u8) ?Attribute {
983 if (!std.mem.eql(u8, name, "callee")) return null;
984 return fromConst(storage).value;
985 }
986
987 fn getProperties(_: *const Operation, storage: *const anyopaque) ?Attribute {
988 return fromConst(storage).value;
989 }
990
991 fn setProperties(_: *Operation, storage: *anyopaque, attr: Attribute) anyerror!void {
992 from(storage).value = attr;
993 }
994
995 fn copyProperties(dest: *anyopaque, source: *const anyopaque) anyerror!void {
996 from(dest).* = fromConst(source).*;
997 }
998
999 const model = interfaces.OperationPropertiesModel{
1000 .name = "test.read_only_symbol_ref.properties",
1001 .size = @sizeOf(@This()),
1002 .alignment = std.mem.Alignment.fromByteUnits(@alignOf(@This())),
1003 .init = init,
1004 .deinit = deinit,
1005 .getInherentAttr = get,
1006 .getPropertiesAsAttr = getProperties,
1007 .setPropertiesFromAttr = setProperties,
1008 .copyProperties = copyProperties,
1009 };
1010 };
1011
1012 test "SymbolTable collects symbol ops in a region" {
1013 const testing = std.testing;
1014 var arena = alloc_arena.Arena.init(std.testing.allocator);
1015 defer arena.deinit();
1016 const allocator = arena.allocator();
1017
1018 const test_dialect = @import("../dialects/fixture/root.zig");
1019 const Location = @import("location.zig").Location;
1020
1021 const Context = @import("context/root.zig").Context;
1022 var ctx = try Context.init(allocator, Context.Limits.testing);
1023 defer ctx.deinit(allocator);
1024 try ctx.allowUnregistered();
1025 try test_dialect.registerTestDialect(&ctx);
1026
1027 const loc = Location.getUnknown();
1028 const module = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1029 const block = module.getBodyBlock();
1030
1031 const f1 = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "foo", &.{});
1032 const f2 = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "bar", &.{});
1033 try block.addOperation(f1.op);
1034 try block.addOperation(f2.op);
1035
1036 var table = SymbolTable.init(allocator);
1037 defer table.deinit();
1038
1039 try table.buildFromOperation(module.op);
1040
1041 try testing.expect(table.lookup("foo") == f1.op);
1042 try testing.expect(table.lookup("bar") == f2.op);
1043 }
1044
1045 test "SymbolTable ignores sym_name attributes on non-symbol operations" {
1046 const testing = std.testing;
1047 var arena = alloc_arena.Arena.init(std.testing.allocator);
1048 defer arena.deinit();
1049 const allocator = arena.allocator();
1050
1051 const test_dialect = @import("../dialects/fixture/root.zig");
1052 const Location = @import("location.zig").Location;
1053
1054 const Context = @import("context/root.zig").Context;
1055 var ctx = try Context.init(allocator, Context.Limits.testing);
1056 defer ctx.deinit(allocator);
1057 try ctx.allowUnregistered();
1058 try test_dialect.registerTestDialect(&ctx);
1059
1060 const loc = Location.getUnknown();
1061 const module = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1062 const block = module.getBodyBlock();
1063
1064 const symbol = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "target", &.{});
1065 try block.addOperation(symbol.op);
1066
1067 const plain = try ctx.createOperation(Operation.State.init("test.plain", loc));
1068 try plain.setAttr(SymbolTable.symbol_attr_names.sym_name, try ctx.getStringAttr("target"));
1069 try block.addOperation(plain);
1070
1071 var table = SymbolTable.init(allocator);
1072 defer table.deinit();
1073
1074 try table.buildFromOperation(module.op);
1075
1076 try testing.expect(table.lookup("target") == symbol.op);
1077 try testing.expect(!SymbolTable.isSymbolOperation(plain));
1078 try testing.expect(SymbolTable.getSymbolName(plain) == null);
1079 try testing.expectError(error.InvalidSymbol, SymbolTable.setSymbolName(plain, "renamed"));
1080 try testing.expectError(error.InvalidSymbol, SymbolTable.setSymbolVisibility(plain, .private));
1081 }
1082
1083 test "SymbolTable rejects duplicate symbols" {
1084 const testing = std.testing;
1085 var arena = alloc_arena.Arena.init(std.testing.allocator);
1086 defer arena.deinit();
1087 const allocator = arena.allocator();
1088
1089 const test_dialect = @import("../dialects/fixture/root.zig");
1090 const Location = @import("location.zig").Location;
1091
1092 const Context = @import("context/root.zig").Context;
1093 var ctx = try Context.init(allocator, Context.Limits.testing);
1094 defer ctx.deinit(allocator);
1095 try ctx.allowUnregistered();
1096 try test_dialect.registerTestDialect(&ctx);
1097
1098 const loc = Location.getUnknown();
1099 const module = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1100 const block = module.getBodyBlock();
1101
1102 const f1 = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "dup", &.{});
1103 const f2 = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "dup", &.{});
1104 try block.addOperation(f1.op);
1105 try block.addOperation(f2.op);
1106
1107 var table = SymbolTable.init(allocator);
1108 defer table.deinit();
1109
1110 try testing.expectError(error.DuplicateSymbol, table.buildFromOperation(module.op));
1111 try testing.expectError(error.DuplicateSymbol, @import("verify.zig").verifyOperation(module.op, @import("verify.zig").default_options));
1112 }
1113
1114 test "SymbolTable rejects public declarations" {
1115 const testing = std.testing;
1116 var arena = alloc_arena.Arena.init(std.testing.allocator);
1117 defer arena.deinit();
1118 const allocator = arena.allocator();
1119
1120 const dialects = @import("../dialects/root.zig");
1121 const core_dialects = @import("root.zig").dialects;
1122 const Location = @import("location.zig").Location;
1123
1124 const Context = @import("context/root.zig").Context;
1125 var ctx = try Context.init(allocator, Context.Limits.testing);
1126 defer ctx.deinit(allocator);
1127 try ctx.allowUnregistered();
1128 try core_dialects.loadDialectSpec(&ctx, dialects.BuiltinDialect.spec);
1129 try core_dialects.loadDialectSpec(&ctx, dialects.FuncDialect.spec);
1130
1131 const loc = Location.getUnknown();
1132 const module = try dialects.BuiltinDialect.ModuleOp.create(&ctx, loc);
1133 const block = module.getBodyBlock();
1134 const declaration = try dialects.FuncDialect.FuncOp.createDeclaration(&ctx, loc, "external", &.{}, &.{});
1135 try testing.expect(SymbolTable.isDeclaration(declaration.op));
1136 try testing.expectEqual(SymbolTable.Visibility.private, SymbolTable.getSymbolVisibility(declaration.op));
1137 try testing.expectError(error.InvalidSymbolDeclaration, SymbolTable.setSymbolVisibility(declaration.op, .public));
1138 _ = declaration.op.removeAttr(SymbolTable.symbol_attr_names.sym_visibility);
1139 try testing.expectEqual(SymbolTable.Visibility.public, SymbolTable.getSymbolVisibility(declaration.op));
1140 try block.addOperation(declaration.op);
1141
1142 try testing.expectError(
1143 error.InvalidSymbolDeclaration,
1144 @import("verify.zig").verifyOperation(module.op, @import("verify.zig").default_options),
1145 );
1146 }
1147
1148 test "lookupNearestSymbolFrom stays within nearest symbol table" {
1149 const testing = std.testing;
1150 var arena = alloc_arena.Arena.init(std.testing.allocator);
1151 defer arena.deinit();
1152 const allocator = arena.allocator();
1153
1154 const test_dialect = @import("../dialects/fixture/root.zig");
1155 const Location = @import("location.zig").Location;
1156
1157 const Context = @import("context/root.zig").Context;
1158 var ctx = try Context.init(allocator, Context.Limits.testing);
1159 defer ctx.deinit(allocator);
1160 try ctx.allowUnregistered();
1161 try test_dialect.registerTestDialect(&ctx);
1162
1163 const loc = Location.getUnknown();
1164 const outer = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1165 const outer_block = outer.getBodyBlock();
1166 const outer_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "outer", &.{});
1167 try outer_block.addOperation(outer_func.op);
1168
1169 const inner = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1170 try outer_block.addOperation(inner.op);
1171 const inner_block = inner.getBodyBlock();
1172 const inner_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "inner", &.{});
1173 try inner_block.addOperation(inner_func.op);
1174
1175 try testing.expect(SymbolTable.lookupNearestSymbolFrom(inner_func.op, "inner") == inner_func.op);
1176 try testing.expect(SymbolTable.lookupNearestSymbolFrom(inner_func.op, "outer") == null);
1177 }
1178
1179 test "SymbolTable.Collection caches tables until invalidated" {
1180 const testing = std.testing;
1181 var arena = alloc_arena.Arena.init(std.testing.allocator);
1182 defer arena.deinit();
1183 const allocator = arena.allocator();
1184
1185 const dialects = @import("../dialects/root.zig");
1186 const core_dialects = @import("root.zig").dialects;
1187 const Context = @import("context/root.zig").Context;
1188 const Location = @import("location.zig").Location;
1189
1190 var ctx = try Context.init(allocator, Context.Limits.testing);
1191 defer ctx.deinit(allocator);
1192 try ctx.allowUnregistered();
1193 try core_dialects.loadDialectSpec(&ctx, dialects.BuiltinDialect.spec);
1194 try core_dialects.loadDialectSpec(&ctx, dialects.FuncDialect.spec);
1195
1196 const loc = Location.getUnknown();
1197 const module = try dialects.BuiltinDialect.ModuleOp.create(&ctx, loc);
1198 const body = module.getBodyBlock();
1199 const target = try dialects.FuncDialect.FuncOp.createDeclaration(&ctx, loc, "target", &.{}, &.{});
1200 try body.addOperation(target.op);
1201 const caller = try dialects.FuncDialect.FuncOp.create(&ctx, loc, "caller", &.{}, &.{});
1202 try body.addOperation(caller.op);
1203
1204 var collection = SymbolTable.Collection.init(testing.allocator);
1205 defer collection.deinit();
1206
1207 try testing.expect(try collection.lookupSymbolIn(module.op, "target") == target.op);
1208 try testing.expect(try collection.lookupNearestSymbolFrom(caller.op, "target") == target.op);
1209 try testing.expect(try collection.lookupSymbolIn(module.op, "late") == null);
1210
1211 const late = try dialects.FuncDialect.FuncOp.createDeclaration(&ctx, loc, "late", &.{}, &.{});
1212 try body.addOperation(late.op);
1213
1214 try testing.expect(try collection.lookupSymbolIn(module.op, "late") == null);
1215 collection.invalidateSymbolTable(module.op);
1216 try testing.expect(try collection.lookupSymbolIn(module.op, "late") == late.op);
1217 try testing.expect(try collection.lookupNearestSymbolFrom(caller.op, "late") == late.op);
1218 }
1219
1220 test "SymbolTable.Collection resolves nested symbol references" {
1221 const testing = std.testing;
1222 var arena = alloc_arena.Arena.init(std.testing.allocator);
1223 defer arena.deinit();
1224 const allocator = arena.allocator();
1225
1226 const test_dialect = @import("../dialects/fixture/root.zig");
1227 const Context = @import("context/root.zig").Context;
1228 const Location = @import("location.zig").Location;
1229
1230 var ctx = try Context.init(allocator, Context.Limits.testing);
1231 defer ctx.deinit(allocator);
1232 try ctx.allowUnregistered();
1233 try test_dialect.registerTestDialect(&ctx);
1234
1235 try ctx.registerOperationInterfaceExternal(
1236 "test.module",
1237 interfaces.SymbolOpInterface.entry(&TestModuleSymbol.vtable),
1238 );
1239
1240 const loc = Location.getUnknown();
1241 const outer = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1242 const inner = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1243 try inner.op.setAttr("sym_name", try test_dialect.TestDialect.getStringAttr(&ctx, "nested"));
1244 try outer.getBodyBlock().addOperation(inner.op);
1245
1246 const leaf = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "leaf", &.{});
1247 try inner.getBodyBlock().addOperation(leaf.op);
1248
1249 const nested_attr = try ctx.getSymbolRefAttr("nested", &.{"leaf"});
1250 const nested_ref = nested_attr.cast(Attribute.SymbolRefAttr).?;
1251 var collection = SymbolTable.Collection.init(testing.allocator);
1252 defer collection.deinit();
1253
1254 try testing.expect(try collection.lookupSymbolRefIn(outer.op, nested_ref) == leaf.op);
1255 try SymbolTable.setSymbolVisibility(leaf.op, .private);
1256 try testing.expect(try collection.lookupSymbolRefIn(outer.op, nested_ref) == null);
1257 try SymbolTable.setSymbolVisibility(leaf.op, .nested);
1258 try testing.expect(try collection.lookupSymbolRefIn(outer.op, nested_ref) == leaf.op);
1259 }
1260
1261 test "SymbolTable.UserMap replaces known flat symbol users" {
1262 const testing = std.testing;
1263 var arena = alloc_arena.Arena.init(std.testing.allocator);
1264 defer arena.deinit();
1265 const allocator = arena.allocator();
1266
1267 const dialects = @import("../dialects/root.zig");
1268 const core_dialects = @import("root.zig").dialects;
1269 const Context = @import("context/root.zig").Context;
1270 const Location = @import("location.zig").Location;
1271
1272 var ctx = try Context.init(allocator, Context.Limits.testing);
1273 defer ctx.deinit(allocator);
1274 try ctx.allowUnregistered();
1275 try core_dialects.loadDialectSpec(&ctx, dialects.BuiltinDialect.spec);
1276 try core_dialects.loadDialectSpec(&ctx, dialects.arith.spec);
1277 try core_dialects.loadDialectSpec(&ctx, dialects.FuncDialect.spec);
1278
1279 const loc = Location.getUnknown();
1280 const i32_type = try dialects.ArithDialect.getI32Type(&ctx);
1281
1282 const module = try dialects.BuiltinDialect.ModuleOp.create(&ctx, loc);
1283 const body = module.getBodyBlock();
1284 const target = try dialects.FuncDialect.FuncOp.createDeclaration(&ctx, loc, "target", &.{i32_type}, &.{i32_type});
1285 const existing = try dialects.FuncDialect.FuncOp.createDeclaration(&ctx, loc, "existing", &.{i32_type}, &.{i32_type});
1286 try body.addOperation(target.op);
1287 try body.addOperation(existing.op);
1288
1289 var caller = try dialects.FuncDialect.FuncOp.create(&ctx, loc, "caller", &.{i32_type}, &.{i32_type});
1290 try body.addOperation(caller.op);
1291 const call = try dialects.FuncDialect.CallOp.create(&ctx, loc, "target", &.{caller.getArgument(0)}, &.{i32_type});
1292 try caller.getEntryBlock().addOperation(call.op);
1293
1294 var collection = SymbolTable.Collection.init(testing.allocator);
1295 defer collection.deinit();
1296 var users = try SymbolUserMap.init(testing.allocator, &collection, module.op);
1297 defer users.deinit();
1298
1299 try testing.expectEqual(@as(usize, 1), users.getUsers(target.op).len);
1300 try testing.expect(users.getUsers(target.op)[0] == call.op);
1301 try testing.expect(users.useEmpty(existing.op));
1302
1303 const replaced = try users.replaceAllUsesWith(target.op, "existing");
1304
1305 try testing.expectEqual(@as(usize, 1), replaced);
1306 try testing.expectEqualStrings("existing", call.getCallee().?);
1307 try testing.expect(users.useEmpty(target.op));
1308 try testing.expectEqual(@as(usize, 1), users.getUsers(existing.op).len);
1309 try testing.expect(users.getUsers(existing.op)[0] == call.op);
1310 }
1311
1312 test "SymbolTable verifies discardable symbol user attributes" {
1313 const testing = std.testing;
1314 var arena = alloc_arena.Arena.init(std.testing.allocator);
1315 defer arena.deinit();
1316 const allocator = arena.allocator();
1317
1318 const test_dialect = @import("../dialects/fixture/root.zig");
1319 const Context = @import("context/root.zig").Context;
1320 const Location = @import("location.zig").Location;
1321
1322 var ctx = try Context.init(allocator, Context.Limits.testing);
1323 defer ctx.deinit(allocator);
1324 try ctx.allowUnregistered();
1325 try test_dialect.registerTestDialect(&ctx);
1326
1327 _ = try ctx.registerAttributeType("test.symbol_user_attr", &.{
1328 interfaces.SymbolUserAttrInterface.entryFor(TestSymbolUserAttribute.verifySymbolUses),
1329 });
1330
1331 const loc = Location.getUnknown();
1332 const module = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1333 const body = module.getBodyBlock();
1334 const target = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "target", &.{});
1335 try body.addOperation(target.op);
1336 const user = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "user", &.{});
1337 try body.addOperation(user.op);
1338
1339 try user.op.setDiscardableAttr("test.ref", try ctx.getDialectAttr("test.symbol_user_attr", "target"));
1340 try @import("verify.zig").verifyOperation(module.op, @import("verify.zig").default_options);
1341
1342 try user.op.setDiscardableAttr("test.ref", try ctx.getDialectAttr("test.symbol_user_attr", "missing"));
1343 try testing.expectError(
1344 error.UnresolvedAttrSymbol,
1345 @import("verify.zig").verifyOperation(module.op, @import("verify.zig").default_options),
1346 );
1347 }
1348
1349 test "SymbolTable.UserMap records and replaces nested symbol users" {
1350 const testing = std.testing;
1351 var arena = alloc_arena.Arena.init(std.testing.allocator);
1352 defer arena.deinit();
1353 const allocator = arena.allocator();
1354
1355 const test_dialect = @import("../dialects/fixture/root.zig");
1356 const Context = @import("context/root.zig").Context;
1357 const Location = @import("location.zig").Location;
1358
1359 var ctx = try Context.init(allocator, Context.Limits.testing);
1360 defer ctx.deinit(allocator);
1361 try ctx.allowUnregistered();
1362 try test_dialect.registerTestDialect(&ctx);
1363
1364 try ctx.registerOperationInterfaceExternal(
1365 "test.module",
1366 interfaces.SymbolOpInterface.entry(&TestModuleSymbol.vtable),
1367 );
1368
1369 const loc = Location.getUnknown();
1370 const outer = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1371 const inner = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1372 try inner.op.setAttr("sym_name", try test_dialect.TestDialect.getStringAttr(&ctx, "nested"));
1373 try outer.getBodyBlock().addOperation(inner.op);
1374
1375 const leaf = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "leaf", &.{});
1376 const new_leaf = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "new_leaf", &.{});
1377 try inner.getBodyBlock().addOperation(leaf.op);
1378 try inner.getBodyBlock().addOperation(new_leaf.op);
1379
1380 const user = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "user", &.{});
1381 try user.op.setAttr("nested_ref", try ctx.getSymbolRefAttr("nested", &.{"leaf"}));
1382 try outer.getBodyBlock().addOperation(user.op);
1383
1384 var collection = SymbolTable.Collection.init(testing.allocator);
1385 defer collection.deinit();
1386 var users = try SymbolUserMap.init(testing.allocator, &collection, outer.op);
1387 defer users.deinit();
1388
1389 try testing.expectEqual(@as(usize, 1), users.getUsers(inner.op).len);
1390 try testing.expect(users.getUsers(inner.op)[0] == user.op);
1391 try testing.expectEqual(@as(usize, 1), users.getUsers(leaf.op).len);
1392 try testing.expect(users.getUsers(leaf.op)[0] == user.op);
1393
1394 const replaced = try users.replaceAllUsesWith(leaf.op, "new_leaf");
1395
1396 try testing.expectEqual(@as(usize, 1), replaced);
1397 const updated = user.op.getAttrAs(Attribute.SymbolRefAttr, "nested_ref") orelse return error.TestExpectedAttribute;
1398 try testing.expectEqualStrings("nested", updated.getRootReference());
1399 try testing.expectEqual(@as(usize, 1), updated.getNestedReferences().len);
1400 try testing.expectEqualStrings("new_leaf", updated.getNestedReferences()[0]);
1401 try testing.expect(users.useEmpty(leaf.op));
1402 try testing.expectEqual(@as(usize, 1), users.getUsers(new_leaf.op).len);
1403 try testing.expect(users.getUsers(new_leaf.op)[0] == user.op);
1404 }
1405
1406 test "SymbolTable resolves nested symbol reference attributes" {
1407 const testing = std.testing;
1408 var arena = alloc_arena.Arena.init(std.testing.allocator);
1409 defer arena.deinit();
1410 const allocator = arena.allocator();
1411
1412 const test_dialect = @import("../dialects/fixture/root.zig");
1413 const Location = @import("location.zig").Location;
1414
1415 const Context = @import("context/root.zig").Context;
1416 var ctx = try Context.init(allocator, Context.Limits.testing);
1417 defer ctx.deinit(allocator);
1418 try ctx.allowUnregistered();
1419 try test_dialect.registerTestDialect(&ctx);
1420
1421 try ctx.registerOperationInterfaceExternal(
1422 "test.module",
1423 interfaces.SymbolOpInterface.entry(&TestModuleSymbol.vtable),
1424 );
1425
1426 const loc = Location.getUnknown();
1427 const outer = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1428 const outer_block = outer.getBodyBlock();
1429
1430 const inner = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1431 try inner.op.setAttr("sym_name", try test_dialect.TestDialect.getStringAttr(&ctx, "nested"));
1432 try outer_block.addOperation(inner.op);
1433
1434 const inner_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "leaf", &.{});
1435 try inner.getBodyBlock().addOperation(inner_func.op);
1436
1437 const nested_attr = try ctx.getSymbolRefAttr("nested", &.{"leaf"});
1438 const nested_ref = nested_attr.cast(Attribute.SymbolRefAttr).?;
1439 try testing.expect(SymbolTable.lookupSymbolRefIn(outer.op, nested_ref) == inner_func.op);
1440
1441 const flat_attr = try ctx.getFlatSymbolRefAttr("leaf");
1442 const flat_ref = flat_attr.cast(Attribute.SymbolRefAttr).?;
1443 try testing.expect(SymbolTable.lookupNearestSymbolRefFrom(inner_func.op, flat_ref) == inner_func.op);
1444 }
1445
1446 test "SymbolTable collects symbol uses without crossing nested symbol tables" {
1447 const testing = std.testing;
1448 var arena = alloc_arena.Arena.init(std.testing.allocator);
1449 defer arena.deinit();
1450 const allocator = arena.allocator();
1451
1452 const dialects = @import("../dialects/root.zig");
1453 const core_dialects = @import("root.zig").dialects;
1454 const Context = @import("context/root.zig").Context;
1455 const Location = @import("location.zig").Location;
1456
1457 var ctx = try Context.init(allocator, Context.Limits.testing);
1458 defer ctx.deinit(allocator);
1459 try ctx.allowUnregistered();
1460 try core_dialects.loadDialectSpec(&ctx, dialects.BuiltinDialect.spec);
1461 try core_dialects.loadDialectSpec(&ctx, dialects.arith.spec);
1462 try core_dialects.loadDialectSpec(&ctx, dialects.FuncDialect.spec);
1463
1464 const loc = Location.getUnknown();
1465 const i32_type = try dialects.ArithDialect.getI32Type(&ctx);
1466
1467 const outer = try dialects.BuiltinDialect.ModuleOp.create(&ctx, loc);
1468 const outer_block = outer.getBodyBlock();
1469 const target = try dialects.FuncDialect.FuncOp.createDeclaration(&ctx, loc, "target", &.{i32_type}, &.{i32_type});
1470 try outer_block.addOperation(target.op);
1471
1472 var caller = try dialects.FuncDialect.FuncOp.create(&ctx, loc, "caller", &.{i32_type}, &.{i32_type});
1473 try outer_block.addOperation(caller.op);
1474 const call = try dialects.FuncDialect.CallOp.create(&ctx, loc, "target", &.{caller.getArgument(0)}, &.{i32_type});
1475 try caller.getEntryBlock().addOperation(call.op);
1476
1477 const inner = try dialects.BuiltinDialect.ModuleOp.create(&ctx, loc);
1478 try outer_block.addOperation(inner.op);
1479 var inner_caller = try dialects.FuncDialect.FuncOp.create(&ctx, loc, "inner_caller", &.{i32_type}, &.{i32_type});
1480 try inner.getBodyBlock().addOperation(inner_caller.op);
1481 const inner_call = try dialects.FuncDialect.CallOp.create(&ctx, loc, "target", &.{inner_caller.getArgument(0)}, &.{i32_type});
1482 try inner_caller.getEntryBlock().addOperation(inner_call.op);
1483
1484 var uses = try SymbolTable.collectSymbolUsesInSymbolTable(testing.allocator, outer.op);
1485 defer uses.deinit(testing.allocator);
1486
1487 try testing.expectEqual(@as(usize, 1), uses.items().len);
1488 try testing.expect(uses.items()[0].user == call.op);
1489 try testing.expectEqualStrings("callee", uses.items()[0].attr_name);
1490 try testing.expectEqualStrings("target", uses.items()[0].symbol_ref.getLeafReference());
1491
1492 const target_ref_attr = try ctx.getFlatSymbolRefAttr("target");
1493 const target_ref = target_ref_attr.cast(Attribute.SymbolRefAttr).?;
1494 try testing.expect(!try SymbolTable.symbolKnownUseEmptyInSymbolTable(testing.allocator, outer.op, target_ref));
1495 }
1496
1497 test "SymbolTable replaces symbol uses without crossing nested symbol tables" {
1498 const testing = std.testing;
1499 var arena = alloc_arena.Arena.init(std.testing.allocator);
1500 defer arena.deinit();
1501 const allocator = arena.allocator();
1502
1503 const dialects = @import("../dialects/root.zig");
1504 const core_dialects = @import("root.zig").dialects;
1505 const Context = @import("context/root.zig").Context;
1506 const Location = @import("location.zig").Location;
1507
1508 var ctx = try Context.init(allocator, Context.Limits.testing);
1509 defer ctx.deinit(allocator);
1510 try ctx.allowUnregistered();
1511 try core_dialects.loadDialectSpec(&ctx, dialects.BuiltinDialect.spec);
1512 try core_dialects.loadDialectSpec(&ctx, dialects.arith.spec);
1513 try core_dialects.loadDialectSpec(&ctx, dialects.FuncDialect.spec);
1514
1515 const loc = Location.getUnknown();
1516 const i32_type = try dialects.ArithDialect.getI32Type(&ctx);
1517
1518 const outer = try dialects.BuiltinDialect.ModuleOp.create(&ctx, loc);
1519 const outer_block = outer.getBodyBlock();
1520
1521 var caller = try dialects.FuncDialect.FuncOp.create(&ctx, loc, "caller", &.{i32_type}, &.{i32_type});
1522 try outer_block.addOperation(caller.op);
1523 var call = try dialects.FuncDialect.CallOp.create(&ctx, loc, "target", &.{caller.getArgument(0)}, &.{i32_type});
1524 try caller.getEntryBlock().addOperation(call.op);
1525
1526 const inner = try dialects.BuiltinDialect.ModuleOp.create(&ctx, loc);
1527 try outer_block.addOperation(inner.op);
1528 var inner_caller = try dialects.FuncDialect.FuncOp.create(&ctx, loc, "inner_caller", &.{i32_type}, &.{i32_type});
1529 try inner.getBodyBlock().addOperation(inner_caller.op);
1530 var inner_call = try dialects.FuncDialect.CallOp.create(&ctx, loc, "target", &.{inner_caller.getArgument(0)}, &.{i32_type});
1531 try inner_caller.getEntryBlock().addOperation(inner_call.op);
1532
1533 const target_ref_attr = try ctx.getFlatSymbolRefAttr("target");
1534 const target_ref = target_ref_attr.cast(Attribute.SymbolRefAttr).?;
1535
1536 const replaced = try SymbolTable.replaceAllSymbolUsesInSymbolTable(testing.allocator, outer.op, target_ref, "renamed");
1537
1538 try testing.expectEqual(@as(usize, 1), replaced);
1539 try testing.expectEqualStrings("renamed", call.getCallee().?);
1540 try testing.expectEqualStrings("target", inner_call.getCallee().?);
1541 try testing.expect(try SymbolTable.symbolKnownUseEmptyInSymbolTable(testing.allocator, outer.op, target_ref));
1542 }
1543
1544 test "SymbolTable replaces nested symbol reference prefixes" {
1545 const testing = std.testing;
1546 var arena = alloc_arena.Arena.init(std.testing.allocator);
1547 defer arena.deinit();
1548 const allocator = arena.allocator();
1549
1550 const test_dialect = @import("../dialects/fixture/root.zig");
1551 const Context = @import("context/root.zig").Context;
1552 const Location = @import("location.zig").Location;
1553
1554 var ctx = try Context.init(allocator, Context.Limits.testing);
1555 defer ctx.deinit(allocator);
1556 try ctx.allowUnregistered();
1557 try test_dialect.registerTestDialect(&ctx);
1558
1559 const loc = Location.getUnknown();
1560 const module = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1561 const body = module.getBodyBlock();
1562 const user = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "user", &.{});
1563 try user.op.setAttr("nested_ref", try ctx.getSymbolRefAttr("module", &.{ "old", "leaf" }));
1564 try body.addOperation(user.op);
1565
1566 const old_attr = try ctx.getSymbolRefAttr("module", &.{"old"});
1567 const old_ref = old_attr.cast(Attribute.SymbolRefAttr).?;
1568 const replaced = try SymbolTable.replaceAllSymbolUsesInSymbolTable(testing.allocator, module.op, old_ref, "new");
1569
1570 try testing.expectEqual(@as(usize, 1), replaced);
1571 const updated = user.op.getAttrAs(Attribute.SymbolRefAttr, "nested_ref") orelse return error.TestExpectedAttribute;
1572 try testing.expectEqualStrings("module", updated.getRootReference());
1573 try testing.expectEqual(@as(usize, 2), updated.getNestedReferences().len);
1574 try testing.expectEqualStrings("new", updated.getNestedReferences()[0]);
1575 try testing.expectEqualStrings("leaf", updated.getNestedReferences()[1]);
1576 }
1577
1578 test "SymbolTable rejects replacement of read-only inherent symbol references" {
1579 const testing = std.testing;
1580 var arena = alloc_arena.Arena.init(testing.allocator);
1581 defer arena.deinit();
1582 const allocator = arena.allocator();
1583
1584 const dialects = @import("../dialects/root.zig");
1585 const core = @import("root.zig");
1586 const core_dialects = core.dialects;
1587 const Context = core.Context;
1588 const Location = core.Location;
1589
1590 var ctx = try Context.init(allocator, Context.Limits.testing);
1591 defer ctx.deinit(allocator);
1592 try ctx.allowUnregistered();
1593 try core_dialects.loadDialectSpec(&ctx, dialects.BuiltinDialect.spec);
1594 _ = try ctx.registerOperation("test.read_only_symbol_user", .{});
1595 try ctx.registerOperationInherentAttributeName("test.read_only_symbol_user", "callee");
1596 try ctx.registerOperationPropertiesModel(
1597 "test.read_only_symbol_user",
1598 ReadOnlySymbolRefProperties.model,
1599 );
1600
1601 const module = try dialects.BuiltinDialect.ModuleOp.create(&ctx, Location.getUnknown());
1602 const target_attr = try ctx.getFlatSymbolRefAttr("target");
1603 var state = Operation.State.init("test.read_only_symbol_user", Location.getUnknown());
1604 try state.setPropertiesAttr(target_attr);
1605 const user = try ctx.createOperation(state);
1606 try module.getBodyBlock().addOperation(user);
1607
1608 const target_ref = target_attr.cast(Attribute.SymbolRefAttr).?;
1609 try testing.expectError(
1610 error.ReadOnlyInherentAttribute,
1611 SymbolTable.replaceAllSymbolUsesInSymbolTable(
1612 testing.allocator,
1613 module.op,
1614 target_ref,
1615 "renamed",
1616 ),
1617 );
1618 const preserved = user.getAttrAs(Attribute.SymbolRefAttr, "callee") orelse
1619 return error.TestExpectedAttribute;
1620 try testing.expectEqualStrings("target", preserved.getRootReference());
1621 try testing.expect(user.raw_dictionary_attrs.get("callee") == null);
1622 }
1623
1624 test "SymbolTable collects and replaces array symbol reference attributes" {
1625 const testing = std.testing;
1626 var arena = alloc_arena.Arena.init(std.testing.allocator);
1627 defer arena.deinit();
1628 const allocator = arena.allocator();
1629
1630 const test_dialect = @import("../dialects/fixture/root.zig");
1631 const Context = @import("context/root.zig").Context;
1632 const Location = @import("location.zig").Location;
1633
1634 var ctx = try Context.init(allocator, Context.Limits.testing);
1635 defer ctx.deinit(allocator);
1636 try ctx.allowUnregistered();
1637 try test_dialect.registerTestDialect(&ctx);
1638
1639 const loc = Location.getUnknown();
1640 const module = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1641 const body = module.getBodyBlock();
1642 const target = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "target", &.{});
1643 try body.addOperation(target.op);
1644 const user = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "user", &.{});
1645 try body.addOperation(user.op);
1646
1647 const keep_attr = try ctx.getStringAttr("keep");
1648 const target_ref_attr = try ctx.getFlatSymbolRefAttr("target");
1649 const target_ref = target_ref_attr.cast(Attribute.SymbolRefAttr).?;
1650 const nested_array_attr = try ctx.getArrayAttr(&.{ target_ref_attr, keep_attr });
1651 const array_attr = try ctx.getArrayAttr(&.{ keep_attr, nested_array_attr });
1652 try user.op.setAttr("refs", array_attr);
1653
1654 var uses = try SymbolTable.collectSymbolUsesInSymbolTable(testing.allocator, module.op);
1655 defer uses.deinit(testing.allocator);
1656 try testing.expectEqual(@as(usize, 1), uses.items().len);
1657 try testing.expect(uses.items()[0].user == user.op);
1658 try testing.expectEqualStrings("refs", uses.items()[0].attr_name);
1659 try testing.expectEqualStrings("target", uses.items()[0].symbol_ref.getRootReference());
1660
1661 const replaced = try SymbolTable.replaceAllSymbolUsesInSymbolTable(testing.allocator, module.op, target_ref, "renamed");
1662
1663 try testing.expectEqual(@as(usize, 1), replaced);
1664 const updated_array = user.op.getAttrAs(Attribute.ArrayAttr, "refs") orelse return error.TestExpectedAttribute;
1665 try testing.expectEqual(@as(usize, 2), updated_array.values.len);
1666 try testing.expect(updated_array.values[0].eql(keep_attr));
1667
1668 const updated_nested_array = updated_array.values[1].cast(Attribute.ArrayAttr) orelse return error.TestExpectedAttribute;
1669 try testing.expectEqual(@as(usize, 2), updated_nested_array.values.len);
1670 const updated_ref = updated_nested_array.values[0].cast(Attribute.SymbolRefAttr) orelse return error.TestExpectedAttribute;
1671 try testing.expectEqualStrings("renamed", updated_ref.getRootReference());
1672 try testing.expect(updated_nested_array.values[1].eql(keep_attr));
1673 try testing.expect(try SymbolTable.symbolKnownUseEmptyInSymbolTable(testing.allocator, module.op, target_ref));
1674 }
1675
1676 test "SymbolTable renames symbols through array symbol reference attributes" {
1677 const testing = std.testing;
1678 var arena = alloc_arena.Arena.init(std.testing.allocator);
1679 defer arena.deinit();
1680 const allocator = arena.allocator();
1681
1682 const test_dialect = @import("../dialects/fixture/root.zig");
1683 const Context = @import("context/root.zig").Context;
1684 const Location = @import("location.zig").Location;
1685
1686 var ctx = try Context.init(allocator, Context.Limits.testing);
1687 defer ctx.deinit(allocator);
1688 try ctx.allowUnregistered();
1689 try test_dialect.registerTestDialect(&ctx);
1690
1691 const loc = Location.getUnknown();
1692 const module = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1693 const body = module.getBodyBlock();
1694 const target = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "target", &.{});
1695 try body.addOperation(target.op);
1696 const user = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "user", &.{});
1697 try body.addOperation(user.op);
1698
1699 const target_ref_attr = try ctx.getFlatSymbolRefAttr("target");
1700 const array_attr = try ctx.getArrayAttr(&.{target_ref_attr});
1701 try user.op.setAttr("refs", array_attr);
1702
1703 {
1704 var collection = SymbolTable.Collection.init(testing.allocator);
1705 defer collection.deinit();
1706 var users = try SymbolUserMap.init(testing.allocator, &collection, module.op);
1707 defer users.deinit();
1708 try testing.expectEqual(@as(usize, 1), users.getUsers(target.op).len);
1709 try testing.expect(users.getUsers(target.op)[0] == user.op);
1710 }
1711
1712 const renamed = try SymbolTable.renameSymbolInSymbolTable(testing.allocator, module.op, target.op, "renamed");
1713
1714 try testing.expectEqual(@as(usize, 1), renamed);
1715 try testing.expectEqualStrings("renamed", target.getName().?);
1716 const updated_array = user.op.getAttrAs(Attribute.ArrayAttr, "refs") orelse return error.TestExpectedAttribute;
1717 try testing.expectEqual(@as(usize, 1), updated_array.values.len);
1718 const updated_ref = updated_array.values[0].cast(Attribute.SymbolRefAttr) orelse return error.TestExpectedAttribute;
1719 try testing.expectEqualStrings("renamed", updated_ref.getRootReference());
1720 try testing.expect(SymbolTable.lookupSymbolIn(module.op, "target") == null);
1721 try testing.expect(SymbolTable.lookupSymbolIn(module.op, "renamed") == target.op);
1722 }
1723
1724 test "SymbolTable renames symbols and rewrites scoped uses" {
1725 const testing = std.testing;
1726 var arena = alloc_arena.Arena.init(std.testing.allocator);
1727 defer arena.deinit();
1728 const allocator = arena.allocator();
1729
1730 const dialects = @import("../dialects/root.zig");
1731 const core_dialects = @import("root.zig").dialects;
1732 const Context = @import("context/root.zig").Context;
1733 const Location = @import("location.zig").Location;
1734
1735 var ctx = try Context.init(allocator, Context.Limits.testing);
1736 defer ctx.deinit(allocator);
1737 try ctx.allowUnregistered();
1738 try core_dialects.loadDialectSpec(&ctx, dialects.BuiltinDialect.spec);
1739 try core_dialects.loadDialectSpec(&ctx, dialects.arith.spec);
1740 try core_dialects.loadDialectSpec(&ctx, dialects.FuncDialect.spec);
1741
1742 const loc = Location.getUnknown();
1743 const i32_type = try dialects.ArithDialect.getI32Type(&ctx);
1744
1745 const module = try dialects.BuiltinDialect.ModuleOp.create(&ctx, loc);
1746 const body = module.getBodyBlock();
1747 const target = try dialects.FuncDialect.FuncOp.createDeclaration(&ctx, loc, "target", &.{i32_type}, &.{i32_type});
1748 try body.addOperation(target.op);
1749
1750 var caller = try dialects.FuncDialect.FuncOp.create(&ctx, loc, "caller", &.{i32_type}, &.{i32_type});
1751 try body.addOperation(caller.op);
1752 const call = try dialects.FuncDialect.CallOp.create(&ctx, loc, "target", &.{caller.getArgument(0)}, &.{i32_type});
1753 try caller.getEntryBlock().addOperation(call.op);
1754
1755 const renamed = try SymbolTable.renameSymbolInSymbolTable(testing.allocator, module.op, target.op, "renamed");
1756
1757 try testing.expectEqual(@as(usize, 1), renamed);
1758 try testing.expectEqualStrings("renamed", target.getName().?);
1759 try testing.expectEqualStrings("renamed", SymbolTable.getSymbolName(target.op).?);
1760 try testing.expectEqualStrings("renamed", call.getCallee().?);
1761 try testing.expect(SymbolTable.lookupSymbolIn(module.op, "target") == null);
1762 try testing.expect(SymbolTable.lookupSymbolIn(module.op, "renamed") == target.op);
1763 }
1764
1765 test "SymbolTable rename rejects collisions before rewriting uses" {
1766 const testing = std.testing;
1767 var arena = alloc_arena.Arena.init(std.testing.allocator);
1768 defer arena.deinit();
1769 const allocator = arena.allocator();
1770
1771 const dialects = @import("../dialects/root.zig");
1772 const core_dialects = @import("root.zig").dialects;
1773 const Context = @import("context/root.zig").Context;
1774 const Location = @import("location.zig").Location;
1775
1776 var ctx = try Context.init(allocator, Context.Limits.testing);
1777 defer ctx.deinit(allocator);
1778 try ctx.allowUnregistered();
1779 try core_dialects.loadDialectSpec(&ctx, dialects.BuiltinDialect.spec);
1780 try core_dialects.loadDialectSpec(&ctx, dialects.arith.spec);
1781 try core_dialects.loadDialectSpec(&ctx, dialects.FuncDialect.spec);
1782
1783 const loc = Location.getUnknown();
1784 const i32_type = try dialects.ArithDialect.getI32Type(&ctx);
1785
1786 const module = try dialects.BuiltinDialect.ModuleOp.create(&ctx, loc);
1787 const body = module.getBodyBlock();
1788 const target = try dialects.FuncDialect.FuncOp.createDeclaration(&ctx, loc, "target", &.{i32_type}, &.{i32_type});
1789 const existing = try dialects.FuncDialect.FuncOp.createDeclaration(&ctx, loc, "existing", &.{i32_type}, &.{i32_type});
1790 try body.addOperation(target.op);
1791 try body.addOperation(existing.op);
1792
1793 var caller = try dialects.FuncDialect.FuncOp.create(&ctx, loc, "caller", &.{i32_type}, &.{i32_type});
1794 try body.addOperation(caller.op);
1795 const call = try dialects.FuncDialect.CallOp.create(&ctx, loc, "target", &.{caller.getArgument(0)}, &.{i32_type});
1796 try caller.getEntryBlock().addOperation(call.op);
1797
1798 try testing.expectError(
1799 error.DuplicateSymbol,
1800 SymbolTable.renameSymbolInSymbolTable(testing.allocator, module.op, target.op, "existing"),
1801 );
1802 try testing.expectEqualStrings("target", target.getName().?);
1803 try testing.expectEqualStrings("target", call.getCallee().?);
1804 }
1805
1806 test "SymbolTable visibility hides private nested symbol references" {
1807 const testing = std.testing;
1808 var arena = alloc_arena.Arena.init(std.testing.allocator);
1809 defer arena.deinit();
1810 const allocator = arena.allocator();
1811
1812 const test_dialect = @import("../dialects/fixture/root.zig");
1813 const Context = @import("context/root.zig").Context;
1814 const Location = @import("location.zig").Location;
1815
1816 var ctx = try Context.init(allocator, Context.Limits.testing);
1817 defer ctx.deinit(allocator);
1818 try ctx.allowUnregistered();
1819 try test_dialect.registerTestDialect(&ctx);
1820
1821 try ctx.registerOperationInterfaceExternal(
1822 "test.module",
1823 interfaces.SymbolOpInterface.entry(&TestModuleSymbol.vtable),
1824 );
1825
1826 const loc = Location.getUnknown();
1827 const outer = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1828 const inner = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1829 try inner.op.setAttr("sym_name", try test_dialect.TestDialect.getStringAttr(&ctx, "nested"));
1830 try outer.getBodyBlock().addOperation(inner.op);
1831
1832 const leaf = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "leaf", &.{});
1833 try inner.getBodyBlock().addOperation(leaf.op);
1834
1835 const nested_attr = try ctx.getSymbolRefAttr("nested", &.{"leaf"});
1836 const nested_ref = nested_attr.cast(Attribute.SymbolRefAttr).?;
1837 try testing.expect(SymbolTable.lookupSymbolRefIn(outer.op, nested_ref) == leaf.op);
1838 try testing.expectEqual(SymbolTable.Visibility.public, SymbolTable.getSymbolVisibility(leaf.op));
1839
1840 try SymbolTable.setSymbolVisibility(leaf.op, .private);
1841 try testing.expectEqual(SymbolTable.Visibility.private, SymbolTable.getSymbolVisibility(leaf.op));
1842 try testing.expect(SymbolTable.lookupSymbolRefIn(outer.op, nested_ref) == null);
1843 try testing.expect(leaf.op.getDiscardableAttr(SymbolTable.symbol_attr_names.sym_visibility) == null);
1844
1845 try SymbolTable.setSymbolVisibility(leaf.op, .nested);
1846 try testing.expectEqual(SymbolTable.Visibility.nested, SymbolTable.getSymbolVisibility(leaf.op));
1847 try testing.expect(SymbolTable.lookupSymbolRefIn(outer.op, nested_ref) == leaf.op);
1848
1849 try SymbolTable.setSymbolVisibility(leaf.op, .public);
1850 try testing.expectEqual(SymbolTable.Visibility.public, SymbolTable.getSymbolVisibility(leaf.op));
1851 try testing.expect(leaf.op.getAttr(SymbolTable.symbol_attr_names.sym_visibility) == null);
1852 }