tiny.choir.ir.symbols
Defined in ir.
API (13)
Actions
Public operations.
SymbolUseRange.deinitSymbolUseRange.emptySymbolUseRange.itemsSymbolUserMap.deinitSymbolUserMap.getUsersSymbolUserMap.initSymbolUserMap.replaceAllUsesWithSymbolUserMap.useEmpty
Types and contracts
Public types and contracts.
Source
Source: lib/choir/src/core/root.zig:26
zig
pub const symbols = @import("symbols.zig");Source: lib/choir/src/core/symbols.zig
zig
const std = @import("std");const alloc_arena = @import("alloc_arena");const attribute = @import("attribute.zig");const Attribute = attribute.Attribute;const NamedAttribute = attribute.NamedAttribute;const Operation = @import("operation/root.zig").Operation;const Region = @import("region.zig").Region;const interfaces = @import("interfaces/root.zig");const AttributeReplacement = struct { attr: ?Attribute = null, replaced: usize = 0,};const SymbolTableSemanticError = error{ DuplicateSymbol, InvalidSymbol, InvalidSymbolTable, InvalidSymbolDeclaration,};pub const SymbolTableError = SymbolTableSemanticError || std.mem.Allocator.Error;pub const SymbolUse = struct { user: *Operation, attr_name: []const u8, symbol_ref: *const Attribute.SymbolRefAttr,};pub const SymbolUseRange = struct { uses: std.ArrayList(SymbolUse) = .empty, pub fn deinit(self: *SymbolUseRange, allocator: std.mem.Allocator) void { self.uses.deinit(allocator); self.* = .{}; } pub fn items(self: *const SymbolUseRange) []const SymbolUse { return self.uses.items; } pub fn empty(self: *const SymbolUseRange) bool { return self.uses.items.len == 0; } fn append(self: *SymbolUseRange, allocator: std.mem.Allocator, use: SymbolUse) !void { try self.uses.append(allocator, use); }};pub const SymbolTable = struct { allocator: std.mem.Allocator, symbols: std.StringHashMapUnmanaged(*Operation), pub const symbol_attr_names = struct { pub const sym_name = "sym_name"; pub const sym_visibility = "sym_visibility"; }; pub const Visibility = enum { public, private, nested, pub fn fromString(value: []const u8) ?Visibility { if (std.mem.eql(u8, value, "public")) return .public; if (std.mem.eql(u8, value, "private")) return .private; if (std.mem.eql(u8, value, "nested")) return .nested; return null; } pub fn toString(self: Visibility) []const u8 { return @tagName(self); } }; pub const Collection = struct { allocator: std.mem.Allocator, tables: std.AutoArrayHashMapUnmanaged(*Operation, SymbolTable) = .empty, pub fn init(allocator: std.mem.Allocator) Collection { return .{ .allocator = allocator }; } pub fn deinit(self: *Collection) void { for (self.tables.values()) |*table| { table.deinit(); } self.tables.deinit(self.allocator); self.tables = .empty; } pub fn getSymbolTable(self: *Collection, op: *Operation) SymbolTableError!*SymbolTable { if (!isSymbolTableOperation(op)) return error.InvalidSymbolTable; const entry = try self.tables.getOrPut(self.allocator, op); if (!entry.found_existing) { entry.value_ptr.* = SymbolTable.init(self.allocator); entry.value_ptr.buildFromOperation(op) catch |err| { entry.value_ptr.deinit(); _ = self.tables.swapRemove(op); return err; }; } return entry.value_ptr; } pub fn invalidateSymbolTable(self: *Collection, op: *Operation) void { if (self.tables.getPtr(op)) |table| { table.deinit(); _ = self.tables.swapRemove(op); } } pub fn lookupSymbolIn( self: *Collection, op: *Operation, name: []const u8, ) SymbolTableError!?*Operation { if (!isSymbolTableOperation(op)) return null; const table = try self.getSymbolTable(op); return table.lookup(name); } pub fn lookupSymbolRefIn( self: *Collection, op: *Operation, symbol_ref: *const Attribute.SymbolRefAttr, ) SymbolTableError!?*Operation { var resolved = try self.lookupSymbolIn(op, symbol_ref.getRootReference()) orelse return null; for (symbol_ref.getNestedReferences()) |nested_ref| { resolved = try self.lookupSymbolIn(resolved, nested_ref) orelse return null; if (getSymbolVisibility(resolved) == .private) return null; } return resolved; } pub fn lookupNearestSymbolFrom( self: *Collection, from: *Operation, name: []const u8, ) SymbolTableError!?*Operation { const table_op = getNearestSymbolTable(from) orelse return null; return self.lookupSymbolIn(table_op, name); } pub fn lookupNearestSymbolRefFrom( self: *Collection, from: *Operation, symbol_ref: *const Attribute.SymbolRefAttr, ) SymbolTableError!?*Operation { const table_op = getNearestSymbolTable(from) orelse return null; return self.lookupSymbolRefIn(table_op, symbol_ref); } }; pub fn init(allocator: std.mem.Allocator) SymbolTable { return .{ .allocator = allocator, .symbols = .{}, }; } pub fn deinit(self: *SymbolTable) void { self.symbols.deinit(self.allocator); } pub fn clearRetainingCapacity(self: *SymbolTable) void { self.symbols.clearRetainingCapacity(); } pub fn lookup(self: *const SymbolTable, name: []const u8) ?*Operation { return self.symbols.get(name); } pub fn iterator(self: *SymbolTable) std.StringHashMapUnmanaged(*Operation).Iterator { return self.symbols.iterator(); } pub fn buildFromOperation(self: *SymbolTable, op: *Operation) SymbolTableError!void { self.clearRetainingCapacity(); if (!isSymbolTableOperation(op)) return error.InvalidSymbolTable; if (op.regions.items.len != 1) return error.InvalidSymbolTable; const region = &op.regions.items[0]; if (region.blocks.size != 1) return error.InvalidSymbolTable; try self.appendFromRegion(region); } fn appendFromRegion(self: *SymbolTable, region: *Region) SymbolTableError!void { const block = region.blocks.front() orelse return; var ops = block.getOperations(); while (ops.next()) |op| { if (getSymbolName(op)) |name| { try self.addSymbol(name, op); } } } fn addSymbol(self: *SymbolTable, name: []const u8, op: *Operation) SymbolTableError!void { const entry = try self.symbols.getOrPut(self.allocator, name); if (entry.found_existing) return error.DuplicateSymbol; entry.value_ptr.* = op; } pub fn isSymbolTableOperation(op: *Operation) bool { return op.getTraits().is_symbol_table; } pub fn isSymbolOperation(op: *Operation) bool { return op.interface(interfaces.SymbolOpInterface) != null; } pub fn verifyOperation(op: *Operation) anyerror!void { var symbol_tables = Collection.init(op.allocator); defer symbol_tables.deinit(); _ = try symbol_tables.getSymbolTable(op); try verifySymbols(op); try verifySymbolUsesInSymbolTable(&symbol_tables, op); } fn verifySymbols(op: *Operation) SymbolTableError!void { if (!isSymbolTableOperation(op)) return error.InvalidSymbolTable; if (op.regions.items.len != 1) return error.InvalidSymbolTable; const region = &op.regions.items[0]; if (region.blocks.size != 1) return error.InvalidSymbolTable; const block = region.blocks.front() orelse return; var ops = block.getOperations(); while (ops.next()) |candidate| { if (!isSymbolOperation(candidate)) continue; if (isDeclaration(candidate) and getSymbolVisibility(candidate) == .public) { return error.InvalidSymbolDeclaration; } } } pub fn lookupSymbolIn(op: *Operation, name: []const u8) ?*Operation { if (!isSymbolTableOperation(op)) return null; if (op.regions.items.len == 0) return null; const region = &op.regions.items[0]; const block = region.blocks.front() orelse return null; var ops = block.getOperations(); while (ops.next()) |candidate| { if (getSymbolName(candidate)) |symbol_name| { if (std.mem.eql(u8, symbol_name, name)) return candidate; } } return null; } pub fn lookupSymbolRefIn(op: *Operation, symbol_ref: *const Attribute.SymbolRefAttr) ?*Operation { var resolved = lookupSymbolIn(op, symbol_ref.getRootReference()) orelse return null; for (symbol_ref.getNestedReferences()) |nested_ref| { resolved = lookupSymbolIn(resolved, nested_ref) orelse return null; if (getSymbolVisibility(resolved) == .private) return null; } return resolved; } pub fn getNearestSymbolTable(from: *Operation) ?*Operation { var current: ?*Operation = from; while (current) |op| { if (isSymbolTableOperation(op)) return op; current = op.getParentOp(); } return null; } pub fn lookupNearestSymbolFrom(from: *Operation, name: []const u8) ?*Operation { const table_op = getNearestSymbolTable(from) orelse return null; return lookupSymbolIn(table_op, name); } pub fn lookupNearestSymbolRefFrom(from: *Operation, symbol_ref: *const Attribute.SymbolRefAttr) ?*Operation { const table_op = getNearestSymbolTable(from) orelse return null; return lookupSymbolRefIn(table_op, symbol_ref); } pub fn collectSymbolUses( allocator: std.mem.Allocator, from: *Operation, ) anyerror!SymbolUseRange { var range = SymbolUseRange{}; errdefer range.deinit(allocator); try appendSymbolUsesFromOperation(allocator, &range, from, null); return range; } pub fn collectSymbolUsesInRegion( allocator: std.mem.Allocator, from: *Region, ) anyerror!SymbolUseRange { var range = SymbolUseRange{}; errdefer range.deinit(allocator); try appendSymbolUsesFromRegion(allocator, &range, from, null); return range; } pub fn collectSymbolUsesInSymbolTable( allocator: std.mem.Allocator, op: *Operation, ) anyerror!SymbolUseRange { if (!isSymbolTableOperation(op)) return error.InvalidSymbolTable; var range = SymbolUseRange{}; errdefer range.deinit(allocator); for (op.regions.items) |*region| { try appendSymbolUsesFromRegion(allocator, &range, region, null); } return range; } pub fn collectSymbolUsesOf( allocator: std.mem.Allocator, from: *Operation, symbol_ref: *const Attribute.SymbolRefAttr, ) anyerror!SymbolUseRange { var range = SymbolUseRange{}; errdefer range.deinit(allocator); try appendSymbolUsesFromOperation(allocator, &range, from, symbol_ref); return range; } pub fn collectSymbolUsesOfInRegion( allocator: std.mem.Allocator, from: *Region, symbol_ref: *const Attribute.SymbolRefAttr, ) anyerror!SymbolUseRange { var range = SymbolUseRange{}; errdefer range.deinit(allocator); try appendSymbolUsesFromRegion(allocator, &range, from, symbol_ref); return range; } pub fn collectSymbolUsesOfInSymbolTable( allocator: std.mem.Allocator, op: *Operation, symbol_ref: *const Attribute.SymbolRefAttr, ) anyerror!SymbolUseRange { if (!isSymbolTableOperation(op)) return error.InvalidSymbolTable; var range = SymbolUseRange{}; errdefer range.deinit(allocator); for (op.regions.items) |*region| { try appendSymbolUsesFromRegion(allocator, &range, region, symbol_ref); } return range; } pub fn symbolKnownUseEmpty( allocator: std.mem.Allocator, from: *Operation, symbol_ref: *const Attribute.SymbolRefAttr, ) anyerror!bool { var uses = try collectSymbolUsesOf(allocator, from, symbol_ref); defer uses.deinit(allocator); return uses.empty(); } pub fn symbolKnownUseEmptyInRegion( allocator: std.mem.Allocator, from: *Region, symbol_ref: *const Attribute.SymbolRefAttr, ) anyerror!bool { var uses = try collectSymbolUsesOfInRegion(allocator, from, symbol_ref); defer uses.deinit(allocator); return uses.empty(); } pub fn symbolKnownUseEmptyInSymbolTable( allocator: std.mem.Allocator, op: *Operation, symbol_ref: *const Attribute.SymbolRefAttr, ) anyerror!bool { var uses = try collectSymbolUsesOfInSymbolTable(allocator, op, symbol_ref); defer uses.deinit(allocator); return uses.empty(); } pub fn replaceAllSymbolUsesInRegion( allocator: std.mem.Allocator, from: *Region, old_ref: *const Attribute.SymbolRefAttr, new_leaf: []const u8, ) anyerror!usize { return replaceSymbolUsesInRegion(allocator, from, old_ref, new_leaf); } pub fn replaceAllSymbolUsesInSymbolTable( allocator: std.mem.Allocator, op: *Operation, old_ref: *const Attribute.SymbolRefAttr, new_leaf: []const u8, ) anyerror!usize { if (!isSymbolTableOperation(op)) return error.InvalidSymbolTable; var replaced: usize = 0; for (op.regions.items) |*region| { replaced += try replaceSymbolUsesInRegion(allocator, region, old_ref, new_leaf); } return replaced; } pub fn renameSymbolInSymbolTable( allocator: std.mem.Allocator, op: *Operation, symbol: *Operation, new_name: []const u8, ) anyerror!usize { if (!isSymbolTableOperation(op)) return error.InvalidSymbolTable; if (symbol.getParentOp() != op) return error.InvalidSymbolTable; const old_name = getSymbolName(symbol) orelse return error.InvalidSymbol; if (std.mem.eql(u8, old_name, new_name)) return 0; if (lookupSymbolIn(op, new_name)) |existing| { if (existing != symbol) return error.DuplicateSymbol; } var symbol_tables = Collection.init(allocator); defer symbol_tables.deinit(); var users = try SymbolUserMap.init(allocator, &symbol_tables, op); defer users.deinit(); const replaced = try users.replaceAllUsesWith(symbol, new_name); try setSymbolName(symbol, new_name); symbol_tables.invalidateSymbolTable(op); return replaced; } pub fn getSymbolName(op: *Operation) ?[]const u8 { const iface = op.interface(interfaces.SymbolOpInterface) orelse return null; return iface.call(.getSymbolName, .{}); } pub fn setSymbolName(op: *Operation, name: []const u8) anyerror!void { const iface = op.interface(interfaces.SymbolOpInterface) orelse return error.InvalidSymbol; try iface.call(.setSymbolName, .{name}); } pub fn isDeclaration(op: *Operation) bool { const iface = op.interface(interfaces.SymbolOpInterface) orelse return false; return iface.call(.isDeclaration, .{}); } pub fn getSymbolVisibility(op: *const Operation) Visibility { const string_attr = op.getAttrAs(Attribute.StringAttr, symbol_attr_names.sym_visibility) orelse return .public; return Visibility.fromString(string_attr.getValue()) orelse .public; } pub fn setSymbolVisibility(op: *Operation, visibility: Visibility) anyerror!void { if (!isSymbolOperation(op)) return error.InvalidSymbol; if (visibility == .public) { if (isDeclaration(op)) return error.InvalidSymbolDeclaration; _ = op.removeAttr(symbol_attr_names.sym_visibility); return; } try op.setAttr( symbol_attr_names.sym_visibility, try op.getContext().getStringAttr(visibility.toString()), ); } fn appendSymbolRefsFromOperation( allocator: std.mem.Allocator, range: *SymbolUseRange, op: *Operation, target_ref: ?*const Attribute.SymbolRefAttr, ) anyerror!void { var attrs = op.getAttrs(); while (attrs.next()) |attr| { try appendSymbolRefsFromAttribute(allocator, range, op, attr.name, attr.value, target_ref); } } fn appendSymbolRefsFromAttribute( allocator: std.mem.Allocator, range: *SymbolUseRange, op: *Operation, attr_name: []const u8, attr: Attribute, target_ref: ?*const Attribute.SymbolRefAttr, ) anyerror!void { if (getSymbolRefAttr(attr)) |symbol_ref| { if (target_ref) |target| { if (!isReferencePrefixOf(target, symbol_ref)) return; } try range.append(allocator, .{ .user = op, .attr_name = attr_name, .symbol_ref = symbol_ref, }); return; } const array_attr = getArrayAttr(attr) orelse return; for (array_attr.values) |child| { try appendSymbolRefsFromAttribute(allocator, range, op, attr_name, child, target_ref); } } fn appendSymbolUsesFromOperation( allocator: std.mem.Allocator, range: *SymbolUseRange, op: *Operation, target_ref: ?*const Attribute.SymbolRefAttr, ) anyerror!void { var state = SymbolUseWalkState{ .allocator = allocator, .range = range, .target_ref = target_ref, }; _ = try op.walk(.{ .order = .pre_order }, &state, SymbolUseWalkState.visit); } fn verifySymbolUsesInSymbolTable(symbol_tables: *Collection, op: *Operation) anyerror!void { if (!isSymbolTableOperation(op)) return error.InvalidSymbolTable; for (op.regions.items) |*region| { try verifySymbolUsesInRegion(symbol_tables, region); } } fn verifySymbolUsesFromOperation(symbol_tables: *Collection, op: *Operation) anyerror!void { var state = VerifySymbolUsesWalkState{ .symbol_tables = symbol_tables }; _ = try op.walk(.{ .order = .pre_order }, &state, VerifySymbolUsesWalkState.visit); } fn verifySymbolUsesOnOperation(symbol_tables: *Collection, op: *Operation) anyerror!void { if (op.interface(interfaces.SymbolUserOpInterface)) |iface| { try iface.call(.verifySymbolUses, .{symbol_tables}); } var attrs = op.getDiscardableAttrs(); while (attrs.next()) |attr| { if (attr.value.interface(interfaces.SymbolUserAttrInterface)) |iface| { try iface.call(.verifySymbolUses, .{ op, symbol_tables }); } } } fn verifySymbolUsesInRegion(symbol_tables: *Collection, region: *Region) anyerror!void { var state = VerifySymbolUsesWalkState{ .symbol_tables = symbol_tables }; _ = try region.walkOperations(.{ .order = .pre_order }, &state, VerifySymbolUsesWalkState.visit); } fn appendSymbolUsesFromRegion( allocator: std.mem.Allocator, range: *SymbolUseRange, region: *Region, target_ref: ?*const Attribute.SymbolRefAttr, ) anyerror!void { var state = SymbolUseWalkState{ .allocator = allocator, .range = range, .target_ref = target_ref, }; _ = try region.walkOperations(.{ .order = .pre_order }, &state, SymbolUseWalkState.visit); } fn replaceSymbolUsesOnOperation( allocator: std.mem.Allocator, op: *Operation, old_ref: *const Attribute.SymbolRefAttr, new_leaf: []const u8, ) anyerror!usize { var replaced: usize = 0; var inline_snapshot: [4]NamedAttribute = undefined; const attr_count = op.getNumAttrs(); const attr_snapshot = if (attr_count <= inline_snapshot.len) inline_snapshot[0..attr_count] else try allocator.alloc(NamedAttribute, attr_count); defer if (attr_count > inline_snapshot.len) allocator.free(attr_snapshot); var attrs = op.getAttrs(); var attr_index: usize = 0; while (attrs.next()) |attr| : (attr_index += 1) { if (attr_index >= attr_snapshot.len) return error.OperationAttributesChanged; attr_snapshot[attr_index] = attr; } if (attr_index != attr_snapshot.len) return error.OperationAttributesChanged; for (attr_snapshot) |attr| { const result = try replaceSymbolUsesInAttribute(allocator, op, old_ref, attr.value, new_leaf); const replacement = result.attr orelse continue; try op.setAttr(attr.name, replacement); replaced += result.replaced; } return replaced; } fn replaceSymbolUsesInAttribute( allocator: std.mem.Allocator, op: *Operation, old_ref: *const Attribute.SymbolRefAttr, attr: Attribute, new_leaf: []const u8, ) anyerror!AttributeReplacement { if (getSymbolRefAttr(attr)) |symbol_ref| { const replacement = try replacementSymbolRefAttr(allocator, op, old_ref, symbol_ref, new_leaf) orelse return .{}; return .{ .attr = replacement, .replaced = 1 }; } const array_attr = getArrayAttr(attr) orelse return .{}; var changed = false; var replaced: usize = 0; const values = try allocator.alloc(Attribute, array_attr.values.len); defer allocator.free(values); for (array_attr.values, 0..) |child, index| { const result = try replaceSymbolUsesInAttribute(allocator, op, old_ref, child, new_leaf); values[index] = result.attr orelse child; if (result.attr != null) changed = true; replaced += result.replaced; } if (!changed) return .{}; return .{ .attr = try op.getContext().getArrayAttr(values), .replaced = replaced, }; } fn replaceSymbolUsesInOperation( allocator: std.mem.Allocator, op: *Operation, old_ref: *const Attribute.SymbolRefAttr, new_leaf: []const u8, ) anyerror!usize { var state = ReplaceSymbolUsesWalkState{ .allocator = allocator, .old_ref = old_ref, .new_leaf = new_leaf, }; _ = try op.walk(.{ .order = .pre_order }, &state, ReplaceSymbolUsesWalkState.visit); return state.replaced; } fn replaceSymbolUsesInRegion( allocator: std.mem.Allocator, region: *Region, old_ref: *const Attribute.SymbolRefAttr, new_leaf: []const u8, ) anyerror!usize { var state = ReplaceSymbolUsesWalkState{ .allocator = allocator, .old_ref = old_ref, .new_leaf = new_leaf, }; _ = try region.walkOperations(.{ .order = .pre_order }, &state, ReplaceSymbolUsesWalkState.visit); return state.replaced; } fn replacementSymbolRefAttr( allocator: std.mem.Allocator, op: *Operation, old_ref: *const Attribute.SymbolRefAttr, current_ref: *const Attribute.SymbolRefAttr, new_leaf: []const u8, ) anyerror!?Attribute { if (!isReferencePrefixOf(old_ref, current_ref)) return null; if (current_ref.getNestedReferences().len == 0) { return try op.getContext().getFlatSymbolRefAttr(new_leaf); } if (old_ref.getNestedReferences().len == 0) { return try op.getContext().getSymbolRefAttr(new_leaf, current_ref.getNestedReferences()); } const nested = try allocator.dupe([]const u8, current_ref.getNestedReferences()); defer allocator.free(nested); nested[old_ref.getNestedReferences().len - 1] = new_leaf; return try op.getContext().getSymbolRefAttr(current_ref.getRootReference(), nested); } fn symbolUsePrefix(use: SymbolUse, nested_len: usize) anyerror!SymbolUse { const attr = if (nested_len == 0) try use.user.getContext().getFlatSymbolRefAttr(use.symbol_ref.getRootReference()) else try use.user.getContext().getSymbolRefAttr( use.symbol_ref.getRootReference(), use.symbol_ref.getNestedReferences()[0..nested_len], ); const symbol_ref = attr.cast(Attribute.SymbolRefAttr).?; return .{ .user = use.user, .attr_name = use.attr_name, .symbol_ref = symbol_ref, }; } fn sameUse(a: SymbolUse, b: SymbolUse) bool { return a.user == b.user and std.mem.eql(u8, a.attr_name, b.attr_name) and symbolRefsEqual(a.symbol_ref, b.symbol_ref); } fn symbolRefsEqual(a: *const Attribute.SymbolRefAttr, b: *const Attribute.SymbolRefAttr) bool { if (a == b) return true; if (!std.mem.eql(u8, a.getRootReference(), b.getRootReference())) return false; const a_nested = a.getNestedReferences(); const b_nested = b.getNestedReferences(); if (a_nested.len != b_nested.len) return false; for (a_nested, b_nested) |a_ref, b_ref| { if (!std.mem.eql(u8, a_ref, b_ref)) return false; } return true; } fn isReferencePrefixOf(prefix: *const Attribute.SymbolRefAttr, symbol_ref: *const Attribute.SymbolRefAttr) bool { if (!std.mem.eql(u8, prefix.getRootReference(), symbol_ref.getRootReference())) return false; const prefix_nested = prefix.getNestedReferences(); const ref_nested = symbol_ref.getNestedReferences(); if (prefix_nested.len > ref_nested.len) return false; for (prefix_nested, 0..) |nested, index| { if (!std.mem.eql(u8, nested, ref_nested[index])) return false; } return true; } fn getSymbolRefAttr(attr: Attribute) ?*const Attribute.SymbolRefAttr { if (!std.mem.eql(u8, attr.abstract.name, attribute.builtin_attr_names.symbol_ref)) return null; return attr.cast(Attribute.SymbolRefAttr); } fn getArrayAttr(attr: Attribute) ?*const Attribute.ArrayAttr { if (!std.mem.eql(u8, attr.abstract.name, attribute.builtin_attr_names.array)) return null; return attr.cast(Attribute.ArrayAttr); }};const SymbolUserSet = struct { users: std.ArrayListUnmanaged(*Operation) = .empty, uses: std.ArrayListUnmanaged(SymbolUse) = .empty, fn deinit(self: *SymbolUserSet, allocator: std.mem.Allocator) void { self.users.deinit(allocator); self.uses.deinit(allocator); self.* = .{}; } fn append(self: *SymbolUserSet, allocator: std.mem.Allocator, use: SymbolUse) !void { var found_user = false; for (self.users.items) |user| { if (user == use.user) { found_user = true; break; } } if (!found_user) try self.users.append(allocator, use.user); for (self.uses.items) |existing| { if (SymbolTable.sameUse(existing, use)) return; } try self.uses.append(allocator, use); } fn appendAll( self: *SymbolUserSet, allocator: std.mem.Allocator, other: *const SymbolUserSet, ) !void { for (other.uses.items) |use| { try self.append(allocator, use); } }};pub const SymbolUserMap = struct { allocator: std.mem.Allocator, symbol_tables: *SymbolTable.Collection, symbol_users: std.AutoArrayHashMapUnmanaged(*Operation, SymbolUserSet) = .empty, pub fn init( allocator: std.mem.Allocator, symbol_tables: *SymbolTable.Collection, symbol_table_op: *Operation, ) anyerror!SymbolUserMap { if (!SymbolTable.isSymbolTableOperation(symbol_table_op)) return error.InvalidSymbolTable; var self = SymbolUserMap{ .allocator = allocator, .symbol_tables = symbol_tables, }; errdefer self.deinit(); try self.appendFromSymbolTable(symbol_table_op); return self; } pub fn deinit(self: *SymbolUserMap) void { for (self.symbol_users.values()) |*users| { users.deinit(self.allocator); } self.symbol_users.deinit(self.allocator); self.symbol_users = .empty; } pub fn getUsers(self: *SymbolUserMap, symbol: *Operation) []const *Operation { if (self.symbol_users.getPtr(symbol)) |users| { return users.users.items; } return &.{}; } pub fn useEmpty(self: *SymbolUserMap, symbol: *Operation) bool { return self.getUsers(symbol).len == 0; } pub fn replaceAllUsesWith( self: *SymbolUserMap, symbol: *Operation, new_name: []const u8, ) anyerror!usize { const old_name = SymbolTable.getSymbolName(symbol) orelse return error.InvalidSymbol; if (std.mem.eql(u8, old_name, new_name)) return 0; const users = self.symbol_users.getPtr(symbol) orelse return 0; var replaced: usize = 0; for (users.uses.items) |*use| { const replacement = try SymbolTable.replacementSymbolRefAttr(self.allocator, use.user, use.symbol_ref, use.symbol_ref, new_name) orelse continue; const replacement_ref = replacement.cast(Attribute.SymbolRefAttr).?; replaced += try SymbolTable.replaceSymbolUsesOnOperation(self.allocator, use.user, use.symbol_ref, new_name); use.symbol_ref = replacement_ref; } const new_symbol = blk: { const parent = symbol.getParentOp() orelse break :blk null; break :blk try self.symbol_tables.lookupSymbolIn(parent, new_name); }; try self.moveUsers(symbol, new_symbol); return replaced; } fn appendFromSymbolTable(self: *SymbolUserMap, symbol_table_op: *Operation) anyerror!void { var uses = try SymbolTable.collectSymbolUsesInSymbolTable(self.allocator, symbol_table_op); defer uses.deinit(self.allocator); for (uses.items()) |use| { try self.appendResolvedUse(symbol_table_op, use); } try self.appendNestedSymbolTables(symbol_table_op); } fn appendNestedSymbolTables(self: *SymbolUserMap, op: *Operation) anyerror!void { var state = NestedSymbolTableWalkState{ .user_map = self }; for (op.regions.items) |*region| { _ = try region.walkOperations(.{ .order = .pre_order }, &state, NestedSymbolTableWalkState.visit); } } fn appendResolvedUse( self: *SymbolUserMap, symbol_table_op: *Operation, use: SymbolUse, ) anyerror!void { var resolved = try self.symbol_tables.lookupSymbolIn(symbol_table_op, use.symbol_ref.getRootReference()) orelse return; try self.appendSymbolUse(resolved, try SymbolTable.symbolUsePrefix(use, 0)); for (use.symbol_ref.getNestedReferences(), 0..) |nested_ref, index| { resolved = try self.symbol_tables.lookupSymbolIn(resolved, nested_ref) orelse return; if (SymbolTable.getSymbolVisibility(resolved) == .private) return; try self.appendSymbolUse(resolved, try SymbolTable.symbolUsePrefix(use, index + 1)); } } fn appendSymbolUse(self: *SymbolUserMap, symbol: *Operation, use: SymbolUse) !void { const entry = try self.symbol_users.getOrPut(self.allocator, symbol); if (!entry.found_existing) entry.value_ptr.* = .{}; try entry.value_ptr.append(self.allocator, use); } fn moveUsers(self: *SymbolUserMap, old_symbol: *Operation, new_symbol: ?*Operation) !void { var old_users = self.symbol_users.getPtr(old_symbol).?.*; _ = self.symbol_users.swapRemove(old_symbol); var transferred = false; errdefer if (!transferred) old_users.deinit(self.allocator); if (new_symbol) |target| { if (self.symbol_users.getPtr(target)) |target_users| { try target_users.appendAll(self.allocator, &old_users); old_users.deinit(self.allocator); transferred = true; return; } const entry = try self.symbol_users.getOrPut(self.allocator, target); entry.value_ptr.* = old_users; transferred = true; return; } old_users.deinit(self.allocator); transferred = true; }};const NestedSymbolTableWalkState = struct { user_map: *SymbolUserMap, fn visit(self: *@This(), op: *Operation) anyerror!Operation.WalkResult { if (!SymbolTable.isSymbolTableOperation(op)) return .advance; try self.user_map.appendFromSymbolTable(op); return .skip; }};const SymbolUseWalkState = struct { allocator: std.mem.Allocator, range: *SymbolUseRange, target_ref: ?*const Attribute.SymbolRefAttr, fn visit(self: *@This(), op: *Operation) anyerror!Operation.WalkResult { try SymbolTable.appendSymbolRefsFromOperation(self.allocator, self.range, op, self.target_ref); if (SymbolTable.isSymbolTableOperation(op)) return .skip; return .advance; }};const VerifySymbolUsesWalkState = struct { symbol_tables: *SymbolTable.Collection, fn visit(self: *@This(), op: *Operation) anyerror!Operation.WalkResult { try SymbolTable.verifySymbolUsesOnOperation(self.symbol_tables, op); if (SymbolTable.isSymbolTableOperation(op)) return .skip; return .advance; }};const ReplaceSymbolUsesWalkState = struct { allocator: std.mem.Allocator, old_ref: *const Attribute.SymbolRefAttr, new_leaf: []const u8, replaced: usize = 0, fn visit(self: *@This(), op: *Operation) anyerror!Operation.WalkResult { self.replaced += try SymbolTable.replaceSymbolUsesOnOperation(self.allocator, op, self.old_ref, self.new_leaf); if (SymbolTable.isSymbolTableOperation(op)) return .skip; return .advance; }};const TestModuleSymbol = struct { fn getSymbolName(op_ptr: *const anyopaque) ?[]const u8 { const op: *const Operation = @ptrCast(@alignCast(op_ptr)); const attr = op.getAttr("sym_name") orelse return null; if (!std.mem.eql(u8, attr.abstract.name, "test.string")) return null; const dialect_attr = attr.cast(Attribute.DialectAttr) orelse return null; return dialect_attr.payload; } fn setSymbolName(op_ptr: *const anyopaque, name: []const u8) anyerror!void { const op: *Operation = @ptrCast(@alignCast(@constCast(op_ptr))); try op.setAttr("sym_name", try op.getContext().getDialectAttr("test.string", name)); } fn isDeclaration(_: *const anyopaque) bool { return false; } const vtable = interfaces.SymbolOpInterface.VTable{ .getSymbolName = getSymbolName, .setSymbolName = setSymbolName, .isDeclaration = isDeclaration, };};const TestSymbolUserAttribute = struct { fn verifySymbolUses( attr_ptr: *const anyopaque, op: *Operation, symbol_tables: *SymbolTable.Collection, ) anyerror!void { const attr: *const Attribute.DialectAttr = @ptrCast(@alignCast(attr_ptr)); _ = try symbol_tables.lookupNearestSymbolFrom(op, attr.payload) orelse return error.UnresolvedAttrSymbol; }};const ReadOnlySymbolRefProperties = struct { value: ?Attribute = null, fn from(storage: *anyopaque) *@This() { return @ptrCast(@alignCast(storage)); } fn fromConst(storage: *const anyopaque) *const @This() { return @ptrCast(@alignCast(storage)); } fn init(storage: *anyopaque, _: std.mem.Allocator) anyerror!void { from(storage).* = .{}; } fn deinit(_: *anyopaque, _: std.mem.Allocator) void {} fn get(_: *const Operation, storage: *const anyopaque, name: []const u8) ?Attribute { if (!std.mem.eql(u8, name, "callee")) return null; return fromConst(storage).value; } fn getProperties(_: *const Operation, storage: *const anyopaque) ?Attribute { return fromConst(storage).value; } fn setProperties(_: *Operation, storage: *anyopaque, attr: Attribute) anyerror!void { from(storage).value = attr; } fn copyProperties(dest: *anyopaque, source: *const anyopaque) anyerror!void { from(dest).* = fromConst(source).*; } const model = interfaces.OperationPropertiesModel{ .name = "test.read_only_symbol_ref.properties", .size = @sizeOf(@This()), .alignment = std.mem.Alignment.fromByteUnits(@alignOf(@This())), .init = init, .deinit = deinit, .getInherentAttr = get, .getPropertiesAsAttr = getProperties, .setPropertiesFromAttr = setProperties, .copyProperties = copyProperties, };};test "SymbolTable collects symbol ops in a region" { const testing = std.testing; var arena = alloc_arena.Arena.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const test_dialect = @import("../dialects/fixture/root.zig"); const Location = @import("location.zig").Location; const Context = @import("context/root.zig").Context; var ctx = try Context.init(allocator, Context.Limits.testing); defer ctx.deinit(allocator); try ctx.allowUnregistered(); try test_dialect.registerTestDialect(&ctx); const loc = Location.getUnknown(); const module = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc); const block = module.getBodyBlock(); const f1 = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "foo", &.{}); const f2 = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "bar", &.{}); try block.addOperation(f1.op); try block.addOperation(f2.op); var table = SymbolTable.init(allocator); defer table.deinit(); try table.buildFromOperation(module.op); try testing.expect(table.lookup("foo") == f1.op); try testing.expect(table.lookup("bar") == f2.op);}test "SymbolTable ignores sym_name attributes on non-symbol operations" { const testing = std.testing; var arena = alloc_arena.Arena.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const test_dialect = @import("../dialects/fixture/root.zig"); const Location = @import("location.zig").Location; const Context = @import("context/root.zig").Context; var ctx = try Context.init(allocator, Context.Limits.testing); defer ctx.deinit(allocator); try ctx.allowUnregistered(); try test_dialect.registerTestDialect(&ctx); const loc = Location.getUnknown(); const module = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc); const block = module.getBodyBlock(); const symbol = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "target", &.{}); try block.addOperation(symbol.op); const plain = try ctx.createOperation(Operation.State.init("test.plain", loc)); try plain.setAttr(SymbolTable.symbol_attr_names.sym_name, try ctx.getStringAttr("target")); try block.addOperation(plain); var table = SymbolTable.init(allocator); defer table.deinit(); try table.buildFromOperation(module.op); try testing.expect(table.lookup("target") == symbol.op); try testing.expect(!SymbolTable.isSymbolOperation(plain)); try testing.expect(SymbolTable.getSymbolName(plain) == null); try testing.expectError(error.InvalidSymbol, SymbolTable.setSymbolName(plain, "renamed")); try testing.expectError(error.InvalidSymbol, SymbolTable.setSymbolVisibility(plain, .private));}test "SymbolTable rejects duplicate symbols" { const testing = std.testing; var arena = alloc_arena.Arena.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const test_dialect = @import("../dialects/fixture/root.zig"); const Location = @import("location.zig").Location; const Context = @import("context/root.zig").Context; var ctx = try Context.init(allocator, Context.Limits.testing); defer ctx.deinit(allocator); try ctx.allowUnregistered(); try test_dialect.registerTestDialect(&ctx); const loc = Location.getUnknown(); const module = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc); const block = module.getBodyBlock(); const f1 = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "dup", &.{}); const f2 = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "dup", &.{}); try block.addOperation(f1.op); try block.addOperation(f2.op); var table = SymbolTable.init(allocator); defer table.deinit(); try testing.expectError(error.DuplicateSymbol, table.buildFromOperation(module.op)); try testing.expectError(error.DuplicateSymbol, @import("verify.zig").verifyOperation(module.op, @import("verify.zig").default_options));}test "SymbolTable rejects public declarations" { const testing = std.testing; var arena = alloc_arena.Arena.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const dialects = @import("../dialects/root.zig"); const core_dialects = @import("root.zig").dialects; const Location = @import("location.zig").Location; const Context = @import("context/root.zig").Context; var ctx = try Context.init(allocator, Context.Limits.testing); defer ctx.deinit(allocator); try ctx.allowUnregistered(); try core_dialects.loadDialectSpec(&ctx, dialects.BuiltinDialect.spec); try core_dialects.loadDialectSpec(&ctx, dialects.FuncDialect.spec); const loc = Location.getUnknown(); const module = try dialects.BuiltinDialect.ModuleOp.create(&ctx, loc); const block = module.getBodyBlock(); const declaration = try dialects.FuncDialect.FuncOp.createDeclaration(&ctx, loc, "external", &.{}, &.{}); try testing.expect(SymbolTable.isDeclaration(declaration.op)); try testing.expectEqual(SymbolTable.Visibility.private, SymbolTable.getSymbolVisibility(declaration.op)); try testing.expectError(error.InvalidSymbolDeclaration, SymbolTable.setSymbolVisibility(declaration.op, .public)); _ = declaration.op.removeAttr(SymbolTable.symbol_attr_names.sym_visibility); try testing.expectEqual(SymbolTable.Visibility.public, SymbolTable.getSymbolVisibility(declaration.op)); try block.addOperation(declaration.op); try testing.expectError( error.InvalidSymbolDeclaration, @import("verify.zig").verifyOperation(module.op, @import("verify.zig").default_options), );}test "lookupNearestSymbolFrom stays within nearest symbol table" { const testing = std.testing; var arena = alloc_arena.Arena.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const test_dialect = @import("../dialects/fixture/root.zig"); const Location = @import("location.zig").Location; const Context = @import("context/root.zig").Context; var ctx = try Context.init(allocator, Context.Limits.testing); defer ctx.deinit(allocator); try ctx.allowUnregistered(); try test_dialect.registerTestDialect(&ctx); const loc = Location.getUnknown(); const outer = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc); const outer_block = outer.getBodyBlock(); const outer_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "outer", &.{}); try outer_block.addOperation(outer_func.op); const inner = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc); try outer_block.addOperation(inner.op); const inner_block = inner.getBodyBlock(); const inner_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "inner", &.{}); try inner_block.addOperation(inner_func.op); try testing.expect(SymbolTable.lookupNearestSymbolFrom(inner_func.op, "inner") == inner_func.op); try testing.expect(SymbolTable.lookupNearestSymbolFrom(inner_func.op, "outer") == null);}test "SymbolTable.Collection caches tables until invalidated" { const testing = std.testing; var arena = alloc_arena.Arena.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const dialects = @import("../dialects/root.zig"); const core_dialects = @import("root.zig").dialects; const Context = @import("context/root.zig").Context; const Location = @import("location.zig").Location; var ctx = try Context.init(allocator, Context.Limits.testing); defer ctx.deinit(allocator); try ctx.allowUnregistered(); try core_dialects.loadDialectSpec(&ctx, dialects.BuiltinDialect.spec); try core_dialects.loadDialectSpec(&ctx, dialects.FuncDialect.spec); const loc = Location.getUnknown(); const module = try dialects.BuiltinDialect.ModuleOp.create(&ctx, loc); const body = module.getBodyBlock(); const target = try dialects.FuncDialect.FuncOp.createDeclaration(&ctx, loc, "target", &.{}, &.{}); try body.addOperation(target.op); const caller = try dialects.FuncDialect.FuncOp.create(&ctx, loc, "caller", &.{}, &.{}); try body.addOperation(caller.op); var collection = SymbolTable.Collection.init(testing.allocator); defer collection.deinit(); try testing.expect(try collection.lookupSymbolIn(module.op, "target") == target.op); try testing.expect(try collection.lookupNearestSymbolFrom(caller.op, "target") == target.op); try testing.expect(try collection.lookupSymbolIn(module.op, "late") == null); const late = try dialects.FuncDialect.FuncOp.createDeclaration(&ctx, loc, "late", &.{}, &.{}); try body.addOperation(late.op); try testing.expect(try collection.lookupSymbolIn(module.op, "late") == null); collection.invalidateSymbolTable(module.op); try testing.expect(try collection.lookupSymbolIn(module.op, "late") == late.op); try testing.expect(try collection.lookupNearestSymbolFrom(caller.op, "late") == late.op);}test "SymbolTable.Collection resolves nested symbol references" { const testing = std.testing; var arena = alloc_arena.Arena.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const test_dialect = @import("../dialects/fixture/root.zig"); const Context = @import("context/root.zig").Context; const Location = @import("location.zig").Location; var ctx = try Context.init(allocator, Context.Limits.testing); defer ctx.deinit(allocator); try ctx.allowUnregistered(); try test_dialect.registerTestDialect(&ctx); try ctx.registerOperationInterfaceExternal( "test.module", interfaces.SymbolOpInterface.entry(&TestModuleSymbol.vtable), ); const loc = Location.getUnknown(); const outer = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc); const inner = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc); try inner.op.setAttr("sym_name", try test_dialect.TestDialect.getStringAttr(&ctx, "nested")); try outer.getBodyBlock().addOperation(inner.op); const leaf = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "leaf", &.{}); try inner.getBodyBlock().addOperation(leaf.op); const nested_attr = try ctx.getSymbolRefAttr("nested", &.{"leaf"}); const nested_ref = nested_attr.cast(Attribute.SymbolRefAttr).?; var collection = SymbolTable.Collection.init(testing.allocator); defer collection.deinit(); try testing.expect(try collection.lookupSymbolRefIn(outer.op, nested_ref) == leaf.op); try SymbolTable.setSymbolVisibility(leaf.op, .private); try testing.expect(try collection.lookupSymbolRefIn(outer.op, nested_ref) == null); try SymbolTable.setSymbolVisibility(leaf.op, .nested); try testing.expect(try collection.lookupSymbolRefIn(outer.op, nested_ref) == leaf.op);}test "SymbolTable.UserMap replaces known flat symbol users" { const testing = std.testing; var arena = alloc_arena.Arena.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const dialects = @import("../dialects/root.zig"); const core_dialects = @import("root.zig").dialects; const Context = @import("context/root.zig").Context; const Location = @import("location.zig").Location; var ctx = try Context.init(allocator, Context.Limits.testing); defer ctx.deinit(allocator); try ctx.allowUnregistered(); try core_dialects.loadDialectSpec(&ctx, dialects.BuiltinDialect.spec); try core_dialects.loadDialectSpec(&ctx, dialects.arith.spec); try core_dialects.loadDialectSpec(&ctx, dialects.FuncDialect.spec); const loc = Location.getUnknown(); const i32_type = try dialects.ArithDialect.getI32Type(&ctx); const module = try dialects.BuiltinDialect.ModuleOp.create(&ctx, loc); const body = module.getBodyBlock(); const target = try dialects.FuncDialect.FuncOp.createDeclaration(&ctx, loc, "target", &.{i32_type}, &.{i32_type}); const existing = try dialects.FuncDialect.FuncOp.createDeclaration(&ctx, loc, "existing", &.{i32_type}, &.{i32_type}); try body.addOperation(target.op); try body.addOperation(existing.op); var caller = try dialects.FuncDialect.FuncOp.create(&ctx, loc, "caller", &.{i32_type}, &.{i32_type}); try body.addOperation(caller.op); const call = try dialects.FuncDialect.CallOp.create(&ctx, loc, "target", &.{caller.getArgument(0)}, &.{i32_type}); try caller.getEntryBlock().addOperation(call.op); var collection = SymbolTable.Collection.init(testing.allocator); defer collection.deinit(); var users = try SymbolUserMap.init(testing.allocator, &collection, module.op); defer users.deinit(); try testing.expectEqual(@as(usize, 1), users.getUsers(target.op).len); try testing.expect(users.getUsers(target.op)[0] == call.op); try testing.expect(users.useEmpty(existing.op)); const replaced = try users.replaceAllUsesWith(target.op, "existing"); try testing.expectEqual(@as(usize, 1), replaced); try testing.expectEqualStrings("existing", call.getCallee().?); try testing.expect(users.useEmpty(target.op)); try testing.expectEqual(@as(usize, 1), users.getUsers(existing.op).len); try testing.expect(users.getUsers(existing.op)[0] == call.op);}test "SymbolTable verifies discardable symbol user attributes" { const testing = std.testing; var arena = alloc_arena.Arena.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const test_dialect = @import("../dialects/fixture/root.zig"); const Context = @import("context/root.zig").Context; const Location = @import("location.zig").Location; var ctx = try Context.init(allocator, Context.Limits.testing); defer ctx.deinit(allocator); try ctx.allowUnregistered(); try test_dialect.registerTestDialect(&ctx); _ = try ctx.registerAttributeType("test.symbol_user_attr", &.{ interfaces.SymbolUserAttrInterface.entryFor(TestSymbolUserAttribute.verifySymbolUses), }); const loc = Location.getUnknown(); const module = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc); const body = module.getBodyBlock(); const target = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "target", &.{}); try body.addOperation(target.op); const user = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "user", &.{}); try body.addOperation(user.op); try user.op.setDiscardableAttr("test.ref", try ctx.getDialectAttr("test.symbol_user_attr", "target")); try @import("verify.zig").verifyOperation(module.op, @import("verify.zig").default_options); try user.op.setDiscardableAttr("test.ref", try ctx.getDialectAttr("test.symbol_user_attr", "missing")); try testing.expectError( error.UnresolvedAttrSymbol, @import("verify.zig").verifyOperation(module.op, @import("verify.zig").default_options), );}test "SymbolTable.UserMap records and replaces nested symbol users" { const testing = std.testing; var arena = alloc_arena.Arena.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const test_dialect = @import("../dialects/fixture/root.zig"); const Context = @import("context/root.zig").Context; const Location = @import("location.zig").Location; var ctx = try Context.init(allocator, Context.Limits.testing); defer ctx.deinit(allocator); try ctx.allowUnregistered(); try test_dialect.registerTestDialect(&ctx); try ctx.registerOperationInterfaceExternal( "test.module", interfaces.SymbolOpInterface.entry(&TestModuleSymbol.vtable), ); const loc = Location.getUnknown(); const outer = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc); const inner = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc); try inner.op.setAttr("sym_name", try test_dialect.TestDialect.getStringAttr(&ctx, "nested")); try outer.getBodyBlock().addOperation(inner.op); const leaf = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "leaf", &.{}); const new_leaf = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "new_leaf", &.{}); try inner.getBodyBlock().addOperation(leaf.op); try inner.getBodyBlock().addOperation(new_leaf.op); const user = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "user", &.{}); try user.op.setAttr("nested_ref", try ctx.getSymbolRefAttr("nested", &.{"leaf"})); try outer.getBodyBlock().addOperation(user.op); var collection = SymbolTable.Collection.init(testing.allocator); defer collection.deinit(); var users = try SymbolUserMap.init(testing.allocator, &collection, outer.op); defer users.deinit(); try testing.expectEqual(@as(usize, 1), users.getUsers(inner.op).len); try testing.expect(users.getUsers(inner.op)[0] == user.op); try testing.expectEqual(@as(usize, 1), users.getUsers(leaf.op).len); try testing.expect(users.getUsers(leaf.op)[0] == user.op); const replaced = try users.replaceAllUsesWith(leaf.op, "new_leaf"); try testing.expectEqual(@as(usize, 1), replaced); const updated = user.op.getAttrAs(Attribute.SymbolRefAttr, "nested_ref") orelse return error.TestExpectedAttribute; try testing.expectEqualStrings("nested", updated.getRootReference()); try testing.expectEqual(@as(usize, 1), updated.getNestedReferences().len); try testing.expectEqualStrings("new_leaf", updated.getNestedReferences()[0]); try testing.expect(users.useEmpty(leaf.op)); try testing.expectEqual(@as(usize, 1), users.getUsers(new_leaf.op).len); try testing.expect(users.getUsers(new_leaf.op)[0] == user.op);}test "SymbolTable resolves nested symbol reference attributes" { const testing = std.testing; var arena = alloc_arena.Arena.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const test_dialect = @import("../dialects/fixture/root.zig"); const Location = @import("location.zig").Location; const Context = @import("context/root.zig").Context; var ctx = try Context.init(allocator, Context.Limits.testing); defer ctx.deinit(allocator); try ctx.allowUnregistered(); try test_dialect.registerTestDialect(&ctx); try ctx.registerOperationInterfaceExternal( "test.module", interfaces.SymbolOpInterface.entry(&TestModuleSymbol.vtable), ); const loc = Location.getUnknown(); const outer = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc); const outer_block = outer.getBodyBlock(); const inner = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc); try inner.op.setAttr("sym_name", try test_dialect.TestDialect.getStringAttr(&ctx, "nested")); try outer_block.addOperation(inner.op); const inner_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "leaf", &.{}); try inner.getBodyBlock().addOperation(inner_func.op); const nested_attr = try ctx.getSymbolRefAttr("nested", &.{"leaf"}); const nested_ref = nested_attr.cast(Attribute.SymbolRefAttr).?; try testing.expect(SymbolTable.lookupSymbolRefIn(outer.op, nested_ref) == inner_func.op); const flat_attr = try ctx.getFlatSymbolRefAttr("leaf"); const flat_ref = flat_attr.cast(Attribute.SymbolRefAttr).?; try testing.expect(SymbolTable.lookupNearestSymbolRefFrom(inner_func.op, flat_ref) == inner_func.op);}test "SymbolTable collects symbol uses without crossing nested symbol tables" { const testing = std.testing; var arena = alloc_arena.Arena.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const dialects = @import("../dialects/root.zig"); const core_dialects = @import("root.zig").dialects; const Context = @import("context/root.zig").Context; const Location = @import("location.zig").Location; var ctx = try Context.init(allocator, Context.Limits.testing); defer ctx.deinit(allocator); try ctx.allowUnregistered(); try core_dialects.loadDialectSpec(&ctx, dialects.BuiltinDialect.spec); try core_dialects.loadDialectSpec(&ctx, dialects.arith.spec); try core_dialects.loadDialectSpec(&ctx, dialects.FuncDialect.spec); const loc = Location.getUnknown(); const i32_type = try dialects.ArithDialect.getI32Type(&ctx); const outer = try dialects.BuiltinDialect.ModuleOp.create(&ctx, loc); const outer_block = outer.getBodyBlock(); const target = try dialects.FuncDialect.FuncOp.createDeclaration(&ctx, loc, "target", &.{i32_type}, &.{i32_type}); try outer_block.addOperation(target.op); var caller = try dialects.FuncDialect.FuncOp.create(&ctx, loc, "caller", &.{i32_type}, &.{i32_type}); try outer_block.addOperation(caller.op); const call = try dialects.FuncDialect.CallOp.create(&ctx, loc, "target", &.{caller.getArgument(0)}, &.{i32_type}); try caller.getEntryBlock().addOperation(call.op); const inner = try dialects.BuiltinDialect.ModuleOp.create(&ctx, loc); try outer_block.addOperation(inner.op); var inner_caller = try dialects.FuncDialect.FuncOp.create(&ctx, loc, "inner_caller", &.{i32_type}, &.{i32_type}); try inner.getBodyBlock().addOperation(inner_caller.op); const inner_call = try dialects.FuncDialect.CallOp.create(&ctx, loc, "target", &.{inner_caller.getArgument(0)}, &.{i32_type}); try inner_caller.getEntryBlock().addOperation(inner_call.op); var uses = try SymbolTable.collectSymbolUsesInSymbolTable(testing.allocator, outer.op); defer uses.deinit(testing.allocator); try testing.expectEqual(@as(usize, 1), uses.items().len); try testing.expect(uses.items()[0].user == call.op); try testing.expectEqualStrings("callee", uses.items()[0].attr_name); try testing.expectEqualStrings("target", uses.items()[0].symbol_ref.getLeafReference()); const target_ref_attr = try ctx.getFlatSymbolRefAttr("target"); const target_ref = target_ref_attr.cast(Attribute.SymbolRefAttr).?; try testing.expect(!try SymbolTable.symbolKnownUseEmptyInSymbolTable(testing.allocator, outer.op, target_ref));}test "SymbolTable replaces symbol uses without crossing nested symbol tables" { const testing = std.testing; var arena = alloc_arena.Arena.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const dialects = @import("../dialects/root.zig"); const core_dialects = @import("root.zig").dialects; const Context = @import("context/root.zig").Context; const Location = @import("location.zig").Location; var ctx = try Context.init(allocator, Context.Limits.testing); defer ctx.deinit(allocator); try ctx.allowUnregistered(); try core_dialects.loadDialectSpec(&ctx, dialects.BuiltinDialect.spec); try core_dialects.loadDialectSpec(&ctx, dialects.arith.spec); try core_dialects.loadDialectSpec(&ctx, dialects.FuncDialect.spec); const loc = Location.getUnknown(); const i32_type = try dialects.ArithDialect.getI32Type(&ctx); const outer = try dialects.BuiltinDialect.ModuleOp.create(&ctx, loc); const outer_block = outer.getBodyBlock(); var caller = try dialects.FuncDialect.FuncOp.create(&ctx, loc, "caller", &.{i32_type}, &.{i32_type}); try outer_block.addOperation(caller.op); var call = try dialects.FuncDialect.CallOp.create(&ctx, loc, "target", &.{caller.getArgument(0)}, &.{i32_type}); try caller.getEntryBlock().addOperation(call.op); const inner = try dialects.BuiltinDialect.ModuleOp.create(&ctx, loc); try outer_block.addOperation(inner.op); var inner_caller = try dialects.FuncDialect.FuncOp.create(&ctx, loc, "inner_caller", &.{i32_type}, &.{i32_type}); try inner.getBodyBlock().addOperation(inner_caller.op); var inner_call = try dialects.FuncDialect.CallOp.create(&ctx, loc, "target", &.{inner_caller.getArgument(0)}, &.{i32_type}); try inner_caller.getEntryBlock().addOperation(inner_call.op); const target_ref_attr = try ctx.getFlatSymbolRefAttr("target"); const target_ref = target_ref_attr.cast(Attribute.SymbolRefAttr).?; const replaced = try SymbolTable.replaceAllSymbolUsesInSymbolTable(testing.allocator, outer.op, target_ref, "renamed"); try testing.expectEqual(@as(usize, 1), replaced); try testing.expectEqualStrings("renamed", call.getCallee().?); try testing.expectEqualStrings("target", inner_call.getCallee().?); try testing.expect(try SymbolTable.symbolKnownUseEmptyInSymbolTable(testing.allocator, outer.op, target_ref));}test "SymbolTable replaces nested symbol reference prefixes" { const testing = std.testing; var arena = alloc_arena.Arena.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const test_dialect = @import("../dialects/fixture/root.zig"); const Context = @import("context/root.zig").Context; const Location = @import("location.zig").Location; var ctx = try Context.init(allocator, Context.Limits.testing); defer ctx.deinit(allocator); try ctx.allowUnregistered(); try test_dialect.registerTestDialect(&ctx); const loc = Location.getUnknown(); const module = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc); const body = module.getBodyBlock(); const user = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "user", &.{}); try user.op.setAttr("nested_ref", try ctx.getSymbolRefAttr("module", &.{ "old", "leaf" })); try body.addOperation(user.op); const old_attr = try ctx.getSymbolRefAttr("module", &.{"old"}); const old_ref = old_attr.cast(Attribute.SymbolRefAttr).?; const replaced = try SymbolTable.replaceAllSymbolUsesInSymbolTable(testing.allocator, module.op, old_ref, "new"); try testing.expectEqual(@as(usize, 1), replaced); const updated = user.op.getAttrAs(Attribute.SymbolRefAttr, "nested_ref") orelse return error.TestExpectedAttribute; try testing.expectEqualStrings("module", updated.getRootReference()); try testing.expectEqual(@as(usize, 2), updated.getNestedReferences().len); try testing.expectEqualStrings("new", updated.getNestedReferences()[0]); try testing.expectEqualStrings("leaf", updated.getNestedReferences()[1]);}test "SymbolTable rejects replacement of read-only inherent symbol references" { const testing = std.testing; var arena = alloc_arena.Arena.init(testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const dialects = @import("../dialects/root.zig"); const core = @import("root.zig"); const core_dialects = core.dialects; const Context = core.Context; const Location = core.Location; var ctx = try Context.init(allocator, Context.Limits.testing); defer ctx.deinit(allocator); try ctx.allowUnregistered(); try core_dialects.loadDialectSpec(&ctx, dialects.BuiltinDialect.spec); _ = try ctx.registerOperation("test.read_only_symbol_user", .{}); try ctx.registerOperationInherentAttributeName("test.read_only_symbol_user", "callee"); try ctx.registerOperationPropertiesModel( "test.read_only_symbol_user", ReadOnlySymbolRefProperties.model, ); const module = try dialects.BuiltinDialect.ModuleOp.create(&ctx, Location.getUnknown()); const target_attr = try ctx.getFlatSymbolRefAttr("target"); var state = Operation.State.init("test.read_only_symbol_user", Location.getUnknown()); try state.setPropertiesAttr(target_attr); const user = try ctx.createOperation(state); try module.getBodyBlock().addOperation(user); const target_ref = target_attr.cast(Attribute.SymbolRefAttr).?; try testing.expectError( error.ReadOnlyInherentAttribute, SymbolTable.replaceAllSymbolUsesInSymbolTable( testing.allocator, module.op, target_ref, "renamed", ), ); const preserved = user.getAttrAs(Attribute.SymbolRefAttr, "callee") orelse return error.TestExpectedAttribute; try testing.expectEqualStrings("target", preserved.getRootReference()); try testing.expect(user.raw_dictionary_attrs.get("callee") == null);}test "SymbolTable collects and replaces array symbol reference attributes" { const testing = std.testing; var arena = alloc_arena.Arena.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const test_dialect = @import("../dialects/fixture/root.zig"); const Context = @import("context/root.zig").Context; const Location = @import("location.zig").Location; var ctx = try Context.init(allocator, Context.Limits.testing); defer ctx.deinit(allocator); try ctx.allowUnregistered(); try test_dialect.registerTestDialect(&ctx); const loc = Location.getUnknown(); const module = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc); const body = module.getBodyBlock(); const target = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "target", &.{}); try body.addOperation(target.op); const user = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "user", &.{}); try body.addOperation(user.op); const keep_attr = try ctx.getStringAttr("keep"); const target_ref_attr = try ctx.getFlatSymbolRefAttr("target"); const target_ref = target_ref_attr.cast(Attribute.SymbolRefAttr).?; const nested_array_attr = try ctx.getArrayAttr(&.{ target_ref_attr, keep_attr }); const array_attr = try ctx.getArrayAttr(&.{ keep_attr, nested_array_attr }); try user.op.setAttr("refs", array_attr); var uses = try SymbolTable.collectSymbolUsesInSymbolTable(testing.allocator, module.op); defer uses.deinit(testing.allocator); try testing.expectEqual(@as(usize, 1), uses.items().len); try testing.expect(uses.items()[0].user == user.op); try testing.expectEqualStrings("refs", uses.items()[0].attr_name); try testing.expectEqualStrings("target", uses.items()[0].symbol_ref.getRootReference()); const replaced = try SymbolTable.replaceAllSymbolUsesInSymbolTable(testing.allocator, module.op, target_ref, "renamed"); try testing.expectEqual(@as(usize, 1), replaced); const updated_array = user.op.getAttrAs(Attribute.ArrayAttr, "refs") orelse return error.TestExpectedAttribute; try testing.expectEqual(@as(usize, 2), updated_array.values.len); try testing.expect(updated_array.values[0].eql(keep_attr)); const updated_nested_array = updated_array.values[1].cast(Attribute.ArrayAttr) orelse return error.TestExpectedAttribute; try testing.expectEqual(@as(usize, 2), updated_nested_array.values.len); const updated_ref = updated_nested_array.values[0].cast(Attribute.SymbolRefAttr) orelse return error.TestExpectedAttribute; try testing.expectEqualStrings("renamed", updated_ref.getRootReference()); try testing.expect(updated_nested_array.values[1].eql(keep_attr)); try testing.expect(try SymbolTable.symbolKnownUseEmptyInSymbolTable(testing.allocator, module.op, target_ref));}test "SymbolTable renames symbols through array symbol reference attributes" { const testing = std.testing; var arena = alloc_arena.Arena.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const test_dialect = @import("../dialects/fixture/root.zig"); const Context = @import("context/root.zig").Context; const Location = @import("location.zig").Location; var ctx = try Context.init(allocator, Context.Limits.testing); defer ctx.deinit(allocator); try ctx.allowUnregistered(); try test_dialect.registerTestDialect(&ctx); const loc = Location.getUnknown(); const module = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc); const body = module.getBodyBlock(); const target = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "target", &.{}); try body.addOperation(target.op); const user = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "user", &.{}); try body.addOperation(user.op); const target_ref_attr = try ctx.getFlatSymbolRefAttr("target"); const array_attr = try ctx.getArrayAttr(&.{target_ref_attr}); try user.op.setAttr("refs", array_attr); { var collection = SymbolTable.Collection.init(testing.allocator); defer collection.deinit(); var users = try SymbolUserMap.init(testing.allocator, &collection, module.op); defer users.deinit(); try testing.expectEqual(@as(usize, 1), users.getUsers(target.op).len); try testing.expect(users.getUsers(target.op)[0] == user.op); } const renamed = try SymbolTable.renameSymbolInSymbolTable(testing.allocator, module.op, target.op, "renamed"); try testing.expectEqual(@as(usize, 1), renamed); try testing.expectEqualStrings("renamed", target.getName().?); const updated_array = user.op.getAttrAs(Attribute.ArrayAttr, "refs") orelse return error.TestExpectedAttribute; try testing.expectEqual(@as(usize, 1), updated_array.values.len); const updated_ref = updated_array.values[0].cast(Attribute.SymbolRefAttr) orelse return error.TestExpectedAttribute; try testing.expectEqualStrings("renamed", updated_ref.getRootReference()); try testing.expect(SymbolTable.lookupSymbolIn(module.op, "target") == null); try testing.expect(SymbolTable.lookupSymbolIn(module.op, "renamed") == target.op);}test "SymbolTable renames symbols and rewrites scoped uses" { const testing = std.testing; var arena = alloc_arena.Arena.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const dialects = @import("../dialects/root.zig"); const core_dialects = @import("root.zig").dialects; const Context = @import("context/root.zig").Context; const Location = @import("location.zig").Location; var ctx = try Context.init(allocator, Context.Limits.testing); defer ctx.deinit(allocator); try ctx.allowUnregistered(); try core_dialects.loadDialectSpec(&ctx, dialects.BuiltinDialect.spec); try core_dialects.loadDialectSpec(&ctx, dialects.arith.spec); try core_dialects.loadDialectSpec(&ctx, dialects.FuncDialect.spec); const loc = Location.getUnknown(); const i32_type = try dialects.ArithDialect.getI32Type(&ctx); const module = try dialects.BuiltinDialect.ModuleOp.create(&ctx, loc); const body = module.getBodyBlock(); const target = try dialects.FuncDialect.FuncOp.createDeclaration(&ctx, loc, "target", &.{i32_type}, &.{i32_type}); try body.addOperation(target.op); var caller = try dialects.FuncDialect.FuncOp.create(&ctx, loc, "caller", &.{i32_type}, &.{i32_type}); try body.addOperation(caller.op); const call = try dialects.FuncDialect.CallOp.create(&ctx, loc, "target", &.{caller.getArgument(0)}, &.{i32_type}); try caller.getEntryBlock().addOperation(call.op); const renamed = try SymbolTable.renameSymbolInSymbolTable(testing.allocator, module.op, target.op, "renamed"); try testing.expectEqual(@as(usize, 1), renamed); try testing.expectEqualStrings("renamed", target.getName().?); try testing.expectEqualStrings("renamed", SymbolTable.getSymbolName(target.op).?); try testing.expectEqualStrings("renamed", call.getCallee().?); try testing.expect(SymbolTable.lookupSymbolIn(module.op, "target") == null); try testing.expect(SymbolTable.lookupSymbolIn(module.op, "renamed") == target.op);}test "SymbolTable rename rejects collisions before rewriting uses" { const testing = std.testing; var arena = alloc_arena.Arena.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const dialects = @import("../dialects/root.zig"); const core_dialects = @import("root.zig").dialects; const Context = @import("context/root.zig").Context; const Location = @import("location.zig").Location; var ctx = try Context.init(allocator, Context.Limits.testing); defer ctx.deinit(allocator); try ctx.allowUnregistered(); try core_dialects.loadDialectSpec(&ctx, dialects.BuiltinDialect.spec); try core_dialects.loadDialectSpec(&ctx, dialects.arith.spec); try core_dialects.loadDialectSpec(&ctx, dialects.FuncDialect.spec); const loc = Location.getUnknown(); const i32_type = try dialects.ArithDialect.getI32Type(&ctx); const module = try dialects.BuiltinDialect.ModuleOp.create(&ctx, loc); const body = module.getBodyBlock(); const target = try dialects.FuncDialect.FuncOp.createDeclaration(&ctx, loc, "target", &.{i32_type}, &.{i32_type}); const existing = try dialects.FuncDialect.FuncOp.createDeclaration(&ctx, loc, "existing", &.{i32_type}, &.{i32_type}); try body.addOperation(target.op); try body.addOperation(existing.op); var caller = try dialects.FuncDialect.FuncOp.create(&ctx, loc, "caller", &.{i32_type}, &.{i32_type}); try body.addOperation(caller.op); const call = try dialects.FuncDialect.CallOp.create(&ctx, loc, "target", &.{caller.getArgument(0)}, &.{i32_type}); try caller.getEntryBlock().addOperation(call.op); try testing.expectError( error.DuplicateSymbol, SymbolTable.renameSymbolInSymbolTable(testing.allocator, module.op, target.op, "existing"), ); try testing.expectEqualStrings("target", target.getName().?); try testing.expectEqualStrings("target", call.getCallee().?);}test "SymbolTable visibility hides private nested symbol references" { const testing = std.testing; var arena = alloc_arena.Arena.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const test_dialect = @import("../dialects/fixture/root.zig"); const Context = @import("context/root.zig").Context; const Location = @import("location.zig").Location; var ctx = try Context.init(allocator, Context.Limits.testing); defer ctx.deinit(allocator); try ctx.allowUnregistered(); try test_dialect.registerTestDialect(&ctx); try ctx.registerOperationInterfaceExternal( "test.module", interfaces.SymbolOpInterface.entry(&TestModuleSymbol.vtable), ); const loc = Location.getUnknown(); const outer = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc); const inner = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc); try inner.op.setAttr("sym_name", try test_dialect.TestDialect.getStringAttr(&ctx, "nested")); try outer.getBodyBlock().addOperation(inner.op); const leaf = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "leaf", &.{}); try inner.getBodyBlock().addOperation(leaf.op); const nested_attr = try ctx.getSymbolRefAttr("nested", &.{"leaf"}); const nested_ref = nested_attr.cast(Attribute.SymbolRefAttr).?; try testing.expect(SymbolTable.lookupSymbolRefIn(outer.op, nested_ref) == leaf.op); try testing.expectEqual(SymbolTable.Visibility.public, SymbolTable.getSymbolVisibility(leaf.op)); try SymbolTable.setSymbolVisibility(leaf.op, .private); try testing.expectEqual(SymbolTable.Visibility.private, SymbolTable.getSymbolVisibility(leaf.op)); try testing.expect(SymbolTable.lookupSymbolRefIn(outer.op, nested_ref) == null); try testing.expect(leaf.op.getDiscardableAttr(SymbolTable.symbol_attr_names.sym_visibility) == null); try SymbolTable.setSymbolVisibility(leaf.op, .nested); try testing.expectEqual(SymbolTable.Visibility.nested, SymbolTable.getSymbolVisibility(leaf.op)); try testing.expect(SymbolTable.lookupSymbolRefIn(outer.op, nested_ref) == leaf.op); try SymbolTable.setSymbolVisibility(leaf.op, .public); try testing.expectEqual(SymbolTable.Visibility.public, SymbolTable.getSymbolVisibility(leaf.op)); try testing.expect(leaf.op.getAttr(SymbolTable.symbol_attr_names.sym_visibility) == null);}Also reachable as
backends.wasm.emission.module_encoding.common.ir.symbols.
Audit
| Definitions | 13 |
|---|---|
| Public names | 50 |
| Members | 7 |
| Version | 26.7.0 |
| Revision | daab053ee433 |