authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-03-30 10:06:55+01:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-03-30 19:47:55+01:00
logf5ab3c93c9a083b730e91e362001da7b32668938
tree37ac30a892b2259c05bb809b62329ff9107ea4fe
parentb4960394efa71a8246b10b46165292f1797aaf87
signaturebadge-check Signed by SSH key SHA256:ZS52FNyUv2WUXvO4njmVaFVO46RHojFuOrxRc4LuKzg

spirv: handle annotations in deduplication pass


1 files changed, 150 insertions(+), 33 deletions(-)

src/link/SpirV/deduplicate.zig+150-33
...@@ -17,7 +17,8 @@ fn canDeduplicate(opcode: Opcode) bool {...@@ -17,7 +17,8 @@ fn canDeduplicate(opcode: Opcode) bool {
17 // These are deprecated, so don't bother supporting them for now.17 // These are deprecated, so don't bother supporting them for now.
18 return false;18 return false;
19 },19 },
20 .OpName, .OpMemberName => true, // Debug decoration-style instructions20 // Debug decoration-style instructions
21 .OpName, .OpMemberName => true,
21 else => switch (opcode.class()) {22 else => switch (opcode.class()) {
22 .TypeDeclaration,23 .TypeDeclaration,
23 .ConstantCreation,24 .ConstantCreation,
...@@ -44,6 +45,8 @@ const ModuleInfo = struct {...@@ -44,6 +45,8 @@ const ModuleInfo = struct {
44 /// or the entity that is affected by this entity if this entity45 /// or the entity that is affected by this entity if this entity
45 /// is a decoration.46 /// is a decoration.
46 result_id_index: u16,47 result_id_index: u16,
48 /// The first decoration in `self.decorations`.
49 first_decoration: u32,
47 };50 };
4851
49 /// Maps result-id to Entity's52 /// Maps result-id to Entity's
...@@ -53,6 +56,8 @@ const ModuleInfo = struct {...@@ -53,6 +56,8 @@ const ModuleInfo = struct {
53 /// Because we need these values when recoding the module anyway,56 /// Because we need these values when recoding the module anyway,
54 /// it contains the status of ALL operands in the module.57 /// it contains the status of ALL operands in the module.
55 operand_is_id: std.DynamicBitSetUnmanaged,58 operand_is_id: std.DynamicBitSetUnmanaged,
59 /// Store of decorations for each entity.
60 decorations: []const Entity,
5661
57 pub fn parse(62 pub fn parse(
58 arena: Allocator,63 arena: Allocator,
...@@ -62,6 +67,7 @@ const ModuleInfo = struct {...@@ -62,6 +67,7 @@ const ModuleInfo = struct {
62 var entities = std.AutoArrayHashMap(ResultId, Entity).init(arena);67 var entities = std.AutoArrayHashMap(ResultId, Entity).init(arena);
63 var id_offsets = std.ArrayList(u16).init(arena);68 var id_offsets = std.ArrayList(u16).init(arena);
64 var operand_is_id = try std.DynamicBitSetUnmanaged.initEmpty(arena, binary.instructions.len);69 var operand_is_id = try std.DynamicBitSetUnmanaged.initEmpty(arena, binary.instructions.len);
70 var decorations = std.MultiArrayList(struct { target_id: ResultId, entity: Entity }){};
6571
66 var it = binary.iterateInstructions();72 var it = binary.iterateInstructions();
67 while (it.next()) |inst| {73 while (it.next()) |inst| {
...@@ -82,10 +88,20 @@ const ModuleInfo = struct {...@@ -82,10 +88,20 @@ const ModuleInfo = struct {
82 };88 };
8389
84 const result_id: ResultId = @enumFromInt(inst.operands[id_offsets.items[result_id_index]]);90 const result_id: ResultId = @enumFromInt(inst.operands[id_offsets.items[result_id_index]]);
91 const entity = Entity{
92 .kind = inst.opcode,
93 .first_operand = first_operand_offset,
94 .num_operands = @intCast(inst.operands.len),
95 .result_id_index = result_id_index,
96 .first_decoration = undefined, // Filled in later
97 };
8598
86 switch (inst.opcode.class()) {99 switch (inst.opcode.class()) {
87 .Annotation, .Debug => {100 .Annotation, .Debug => {
88 // TODO101 try decorations.append(arena, .{
102 .target_id = result_id,
103 .entity = entity,
104 });
89 },105 },
90 .TypeDeclaration, .ConstantCreation => {106 .TypeDeclaration, .ConstantCreation => {
91 const entry = try entities.getOrPut(result_id);107 const entry = try entities.getOrPut(result_id);
...@@ -93,22 +109,67 @@ const ModuleInfo = struct {...@@ -93,22 +109,67 @@ const ModuleInfo = struct {
93 log.err("type or constant {} has duplicate definition", .{result_id});109 log.err("type or constant {} has duplicate definition", .{result_id});
94 return error.DuplicateId;110 return error.DuplicateId;
95 }111 }
96 entry.value_ptr.* = .{112 entry.value_ptr.* = entity;
97 .kind = inst.opcode,
98 .first_operand = first_operand_offset,
99 .num_operands = @intCast(inst.operands.len),
100 .result_id_index = result_id_index,
101 };
102 },113 },
103 else => unreachable,114 else => unreachable,
104 }115 }
105 }116 }
106117
118 // Sort decorations by the index of the result-id in `entities.
119 // This ensures not only that the decorations of a particular reuslt-id
120 // are continuous, but the subsequences also appear in the same order as in `entities`.
121
122 const SortContext = struct {
123 entities: std.AutoArrayHashMapUnmanaged(ResultId, Entity),
124 ids: []const ResultId,
125
126 pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
127 // If any index is not in the entities set, its because its not a
128 // deduplicatable result-id. Those should be considered largest and
129 // float to the end.
130 const entity_index_a = ctx.entities.getIndex(ctx.ids[a_index]) orelse return false;
131 const entity_index_b = ctx.entities.getIndex(ctx.ids[b_index]) orelse return true;
132
133 return entity_index_a < entity_index_b;
134 }
135 };
136
137 decorations.sort(SortContext{
138 .entities = entities.unmanaged,
139 .ids = decorations.items(.target_id),
140 });
141
142 // Now go through the decorations and add the offsets to the entities list.
143 var decoration_i: u32 = 0;
144 const target_ids = decorations.items(.target_id);
145 for (entities.keys(), entities.values()) |id, *entity| {
146 entity.first_decoration = decoration_i;
147
148 // Scan ahead to the next decoration
149 while (decoration_i < target_ids.len and target_ids[decoration_i] == id) {
150 decoration_i += 1;
151 }
152 }
153
107 return ModuleInfo{154 return ModuleInfo{
108 .entities = entities.unmanaged,155 .entities = entities.unmanaged,
109 .operand_is_id = operand_is_id,156 .operand_is_id = operand_is_id,
157 // There may be unrelated decorations at the end, so make sure to
158 // slice those off.
159 .decorations = decorations.items(.entity)[0..decoration_i],
110 };160 };
111 }161 }
162
163 fn entityDecorationsByIndex(self: ModuleInfo, index: usize) []const Entity {
164 const values = self.entities.values();
165 const first_decoration = values[index].first_decoration;
166 if (index == values.len - 1) {
167 return self.decorations[first_decoration..];
168 } else {
169 const next_first_decoration = values[index + 1].first_decoration;
170 return self.decorations[first_decoration..next_first_decoration];
171 }
172 }
112};173};
113174
114const EntityContext = struct {175const EntityContext = struct {
...@@ -138,23 +199,39 @@ const EntityContext = struct {...@@ -138,23 +199,39 @@ const EntityContext = struct {
138 return hasher.final();199 return hasher.final();
139 }200 }
140201
141 fn hashInner(self: *EntityContext, hasher: *std.hash.Wyhash, id: ResultId) !void {202 fn hashInner(self: *EntityContext, hasher: *std.hash.Wyhash, id: ResultId) error{OutOfMemory}!void {
142 const index = self.info.entities.getIndex(id).?;203 const index = self.info.entities.getIndex(id) orelse {
204 // Index unknown, the type or constant may depend on another result-id
205 // that couldn't be deduplicated and so it wasn't added to info.entities.
206 // In this case, just has the ID itself.
207 std.hash.autoHash(hasher, id);
208 return;
209 };
210
143 const entity = self.info.entities.values()[index];211 const entity = self.info.entities.values()[index];
144212
145 std.hash.autoHash(hasher, entity.kind);
146 if (entity.kind == .OpTypePointer) {213 if (entity.kind == .OpTypePointer) {
147 // This may be either a pointer that is forward-referenced in the future,214 // This may be either a pointer that is forward-referenced in the future,
148 // or a forward reference to a pointer.215 // or a forward reference to a pointer.
149 const entry = try self.ptr_map_a.getOrPut(self.a, id);216 const entry = try self.ptr_map_a.getOrPut(self.a, id);
150 if (entry.found_existing) {217 if (entry.found_existing) {
151 // Pointer already seen. Hash the index instead of recursing into its children.218 // Pointer already seen. Hash the index instead of recursing into its children.
152 // TODO: Discriminate this path somehow?
153 std.hash.autoHash(hasher, entry.index);219 std.hash.autoHash(hasher, entry.index);
154 return;220 return;
155 }221 }
156 }222 }
157223
224 try self.hashEntity(hasher, entity);
225
226 // Process decorations.
227 const decorations = self.info.entityDecorationsByIndex(index);
228 for (decorations) |decoration| {
229 try self.hashEntity(hasher, decoration);
230 }
231 }
232
233 fn hashEntity(self: *EntityContext, hasher: *std.hash.Wyhash, entity: ModuleInfo.Entity) !void {
234 std.hash.autoHash(hasher, entity.kind);
158 // Process operands235 // Process operands
159 const operands = self.binary.instructions[entity.first_operand..][0..entity.num_operands];236 const operands = self.binary.instructions[entity.first_operand..][0..entity.num_operands];
160 for (operands, 0..) |operand, i| {237 for (operands, 0..) |operand, i| {
...@@ -178,19 +255,24 @@ const EntityContext = struct {...@@ -178,19 +255,24 @@ const EntityContext = struct {
178 return try self.eqlInner(a, b);255 return try self.eqlInner(a, b);
179 }256 }
180257
181 fn eqlInner(self: *EntityContext, id_a: ResultId, id_b: ResultId) !bool {258 fn eqlInner(self: *EntityContext, id_a: ResultId, id_b: ResultId) error{OutOfMemory}!bool {
182 const index_a = self.info.entities.getIndex(id_a).?;259 const maybe_index_a = self.info.entities.getIndex(id_a);
183 const index_b = self.info.entities.getIndex(id_b).?;260 const maybe_index_b = self.info.entities.getIndex(id_b);
261
262 if (maybe_index_a == null and maybe_index_b == null) {
263 // Both indices unknown. In this case the type or constant
264 // may depend on another result-id that couldn't be deduplicated
265 // (so it wasn't added to info.entities). In this case, that particular
266 // result-id should be the same one.
267 return id_a == id_b;
268 }
269
270 const index_a = maybe_index_a orelse return false;
271 const index_b = maybe_index_b orelse return false;
184272
185 const entity_a = self.info.entities.values()[index_a];273 const entity_a = self.info.entities.values()[index_a];
186 const entity_b = self.info.entities.values()[index_b];274 const entity_b = self.info.entities.values()[index_b];
187275
188 if (entity_a.kind != entity_b.kind) {
189 return false;
190 } else if (entity_a.result_id_index != entity_a.result_id_index) {
191 return false;
192 }
193
194 if (entity_a.kind == .OpTypePointer) {276 if (entity_a.kind == .OpTypePointer) {
195 // May be a forward reference, or should be saved as a potential277 // May be a forward reference, or should be saved as a potential
196 // forward reference in the future. Whatever the case, it should278 // forward reference in the future. Whatever the case, it should
...@@ -207,6 +289,33 @@ const EntityContext = struct {...@@ -207,6 +289,33 @@ const EntityContext = struct {
207 }289 }
208 }290 }
209291
292 if (!try self.eqlEntities(entity_a, entity_b)) {
293 return false;
294 }
295
296 // Compare decorations.
297 const decorations_a = self.info.entityDecorationsByIndex(index_a);
298 const decorations_b = self.info.entityDecorationsByIndex(index_b);
299 if (decorations_a.len != decorations_b.len) {
300 return false;
301 }
302
303 for (decorations_a, decorations_b) |decoration_a, decoration_b| {
304 if (!try self.eqlEntities(decoration_a, decoration_b)) {
305 return false;
306 }
307 }
308
309 return true;
310 }
311
312 fn eqlEntities(self: *EntityContext, entity_a: ModuleInfo.Entity, entity_b: ModuleInfo.Entity) !bool {
313 if (entity_a.kind != entity_b.kind) {
314 return false;
315 } else if (entity_a.result_id_index != entity_a.result_id_index) {
316 return false;
317 }
318
210 const operands_a = self.binary.instructions[entity_a.first_operand..][0..entity_a.num_operands];319 const operands_a = self.binary.instructions[entity_a.first_operand..][0..entity_a.num_operands];
211 const operands_b = self.binary.instructions[entity_b.first_operand..][0..entity_b.num_operands];320 const operands_b = self.binary.instructions[entity_b.first_operand..][0..entity_b.num_operands];
212321
...@@ -260,7 +369,6 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {...@@ -260,7 +369,6 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
260 const a = arena.allocator();369 const a = arena.allocator();
261370
262 const info = try ModuleInfo.parse(a, parser, binary.*);371 const info = try ModuleInfo.parse(a, parser, binary.*);
263 log.info("added {} entities", .{info.entities.count()});
264372
265 // Hash all keys once so that the maps can be allocated the right size.373 // Hash all keys once so that the maps can be allocated the right size.
266 var ctx = EntityContext{374 var ctx = EntityContext{
...@@ -280,10 +388,9 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {...@@ -280,10 +388,9 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
280 .entity_context = &ctx,388 .entity_context = &ctx,
281 });389 });
282 var replace = std.AutoArrayHashMap(ResultId, ResultId).init(a);390 var replace = std.AutoArrayHashMap(ResultId, ResultId).init(a);
283 for (info.entities.keys(), info.entities.values()) |id, entity| {391 for (info.entities.keys()) |id| {
284 const entry = try map.getOrPut(id);392 const entry = try map.getOrPut(id);
285 if (entry.found_existing) {393 if (entry.found_existing) {
286 log.info("deduplicating {} - {s} (prior definition: {})", .{ id, @tagName(entity.kind), entry.key_ptr.* });
287 try replace.putNoClobber(id, entry.key_ptr.*);394 try replace.putNoClobber(id, entry.key_ptr.*);
288 }395 }
289 }396 }
...@@ -297,13 +404,15 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {...@@ -297,13 +404,15 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
297 while (it.next()) |inst| {404 while (it.next()) |inst| {
298 // Result-id can only be the first or second operand405 // Result-id can only be the first or second operand
299 const inst_spec = parser.getInstSpec(inst.opcode).?;406 const inst_spec = parser.getInstSpec(inst.opcode).?;
300 const maybe_result_id: ?ResultId = for (0..2) |i| {407
408 const maybe_result_id_offset: ?u16 = for (0..2) |i| {
301 if (inst_spec.operands.len > i and inst_spec.operands[i].kind == .IdResult) {409 if (inst_spec.operands.len > i and inst_spec.operands[i].kind == .IdResult) {
302 break @enumFromInt(inst.operands[i]);410 break @intCast(i);
303 }411 }
304 } else null;412 } else null;
305413
306 if (maybe_result_id) |result_id| {414 if (maybe_result_id_offset) |offset| {
415 const result_id: ResultId = @enumFromInt(inst.operands[offset]);
307 if (replace.contains(result_id)) continue;416 if (replace.contains(result_id)) continue;
308 }417 }
309418
...@@ -312,8 +421,16 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {...@@ -312,8 +421,16 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
312 new_functions_section = section.instructions.items.len;421 new_functions_section = section.instructions.items.len;
313 },422 },
314 .OpTypeForwardPointer => continue, // We re-emit these where needed423 .OpTypeForwardPointer => continue, // We re-emit these where needed
315 // TODO: These aren't supported yet, strip them out for testing purposes.424 else => {},
316 .OpName, .OpMemberName => continue,425 }
426
427 switch (inst.opcode.class()) {
428 .Annotation, .Debug => {
429 // For decoration-style instructions, only emit them
430 // if the target is not removed.
431 const target: ResultId = @enumFromInt(inst.operands[0]);
432 if (replace.contains(target)) continue;
433 },
317 else => {},434 else => {},
318 }435 }
319436
...@@ -330,9 +447,8 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {...@@ -330,9 +447,8 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
330 operand.* = @intFromEnum(new_id);447 operand.* = @intFromEnum(new_id);
331 }448 }
332449
333 const id: ResultId = @enumFromInt(operand.*);450 if (maybe_result_id_offset == null or maybe_result_id_offset.? != i) {
334 // TODO: This test is a little janky. Check the offset instead?451 const id: ResultId = @enumFromInt(operand.*);
335 if (maybe_result_id == null or maybe_result_id.? != id) {
336 const index = info.entities.getIndex(id) orelse continue;452 const index = info.entities.getIndex(id) orelse continue;
337 const entity = info.entities.values()[index];453 const entity = info.entities.values()[index];
338 if (entity.kind == .OpTypePointer and !emitted_ptrs.contains(id)) {454 if (entity.kind == .OpTypePointer and !emitted_ptrs.contains(id)) {
...@@ -349,7 +465,8 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {...@@ -349,7 +465,8 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
349 }465 }
350466
351 if (inst.opcode == .OpTypePointer) {467 if (inst.opcode == .OpTypePointer) {
352 try emitted_ptrs.put(maybe_result_id.?, {});468 const result_id: ResultId = @enumFromInt(new_operands.items[maybe_result_id_offset.?]);
469 try emitted_ptrs.put(result_id, {});
353 }470 }
354471
355 try section.emitRawInstruction(a, inst.opcode, new_operands.items);472 try section.emitRawInstruction(a, inst.opcode, new_operands.items);