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 {
1717 // These are deprecated, so don't bother supporting them for now.
1818 return false;
1919 },
20 .OpName, .OpMemberName => true, // Debug decoration-style instructions
20 // Debug decoration-style instructions
21 .OpName, .OpMemberName => true,
2122 else => switch (opcode.class()) {
2223 .TypeDeclaration,
2324 .ConstantCreation,
......@@ -44,6 +45,8 @@ const ModuleInfo = struct {
4445 /// or the entity that is affected by this entity if this entity
4546 /// is a decoration.
4647 result_id_index: u16,
48 /// The first decoration in `self.decorations`.
49 first_decoration: u32,
4750 };
4851
4952 /// Maps result-id to Entity's
......@@ -53,6 +56,8 @@ const ModuleInfo = struct {
5356 /// Because we need these values when recoding the module anyway,
5457 /// it contains the status of ALL operands in the module.
5558 operand_is_id: std.DynamicBitSetUnmanaged,
59 /// Store of decorations for each entity.
60 decorations: []const Entity,
5661
5762 pub fn parse(
5863 arena: Allocator,
......@@ -62,6 +67,7 @@ const ModuleInfo = struct {
6267 var entities = std.AutoArrayHashMap(ResultId, Entity).init(arena);
6368 var id_offsets = std.ArrayList(u16).init(arena);
6469 var operand_is_id = try std.DynamicBitSetUnmanaged.initEmpty(arena, binary.instructions.len);
70 var decorations = std.MultiArrayList(struct { target_id: ResultId, entity: Entity }){};
6571
6672 var it = binary.iterateInstructions();
6773 while (it.next()) |inst| {
......@@ -82,10 +88,20 @@ const ModuleInfo = struct {
8288 };
8389
8490 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
8699 switch (inst.opcode.class()) {
87100 .Annotation, .Debug => {
88 // TODO
101 try decorations.append(arena, .{
102 .target_id = result_id,
103 .entity = entity,
104 });
89105 },
90106 .TypeDeclaration, .ConstantCreation => {
91107 const entry = try entities.getOrPut(result_id);
......@@ -93,22 +109,67 @@ const ModuleInfo = struct {
93109 log.err("type or constant {} has duplicate definition", .{result_id});
94110 return error.DuplicateId;
95111 }
96 entry.value_ptr.* = .{
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 };
112 entry.value_ptr.* = entity;
102113 },
103114 else => unreachable,
104115 }
105116 }
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
107154 return ModuleInfo{
108155 .entities = entities.unmanaged,
109156 .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],
110160 };
111161 }
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 }
112173};
113174
114175const EntityContext = struct {
......@@ -138,23 +199,39 @@ const EntityContext = struct {
138199 return hasher.final();
139200 }
140201
141 fn hashInner(self: *EntityContext, hasher: *std.hash.Wyhash, id: ResultId) !void {
142 const index = self.info.entities.getIndex(id).?;
202 fn hashInner(self: *EntityContext, hasher: *std.hash.Wyhash, id: ResultId) error{OutOfMemory}!void {
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
143211 const entity = self.info.entities.values()[index];
144212
145 std.hash.autoHash(hasher, entity.kind);
146213 if (entity.kind == .OpTypePointer) {
147214 // This may be either a pointer that is forward-referenced in the future,
148215 // or a forward reference to a pointer.
149216 const entry = try self.ptr_map_a.getOrPut(self.a, id);
150217 if (entry.found_existing) {
151218 // Pointer already seen. Hash the index instead of recursing into its children.
152 // TODO: Discriminate this path somehow?
153219 std.hash.autoHash(hasher, entry.index);
154220 return;
155221 }
156222 }
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);
158235 // Process operands
159236 const operands = self.binary.instructions[entity.first_operand..][0..entity.num_operands];
160237 for (operands, 0..) |operand, i| {
......@@ -178,19 +255,24 @@ const EntityContext = struct {
178255 return try self.eqlInner(a, b);
179256 }
180257
181 fn eqlInner(self: *EntityContext, id_a: ResultId, id_b: ResultId) !bool {
182 const index_a = self.info.entities.getIndex(id_a).?;
183 const index_b = self.info.entities.getIndex(id_b).?;
258 fn eqlInner(self: *EntityContext, id_a: ResultId, id_b: ResultId) error{OutOfMemory}!bool {
259 const maybe_index_a = self.info.entities.getIndex(id_a);
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
185273 const entity_a = self.info.entities.values()[index_a];
186274 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
194276 if (entity_a.kind == .OpTypePointer) {
195277 // May be a forward reference, or should be saved as a potential
196278 // forward reference in the future. Whatever the case, it should
......@@ -207,6 +289,33 @@ const EntityContext = struct {
207289 }
208290 }
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
210319 const operands_a = self.binary.instructions[entity_a.first_operand..][0..entity_a.num_operands];
211320 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 {
260369 const a = arena.allocator();
261370
262371 const info = try ModuleInfo.parse(a, parser, binary.*);
263 log.info("added {} entities", .{info.entities.count()});
264372
265373 // Hash all keys once so that the maps can be allocated the right size.
266374 var ctx = EntityContext{
......@@ -280,10 +388,9 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
280388 .entity_context = &ctx,
281389 });
282390 var replace = std.AutoArrayHashMap(ResultId, ResultId).init(a);
283 for (info.entities.keys(), info.entities.values()) |id, entity| {
391 for (info.entities.keys()) |id| {
284392 const entry = try map.getOrPut(id);
285393 if (entry.found_existing) {
286 log.info("deduplicating {} - {s} (prior definition: {})", .{ id, @tagName(entity.kind), entry.key_ptr.* });
287394 try replace.putNoClobber(id, entry.key_ptr.*);
288395 }
289396 }
......@@ -297,13 +404,15 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
297404 while (it.next()) |inst| {
298405 // Result-id can only be the first or second operand
299406 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| {
301409 if (inst_spec.operands.len > i and inst_spec.operands[i].kind == .IdResult) {
302 break @enumFromInt(inst.operands[i]);
410 break @intCast(i);
303411 }
304412 } 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]);
307416 if (replace.contains(result_id)) continue;
308417 }
309418
......@@ -312,8 +421,16 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
312421 new_functions_section = section.instructions.items.len;
313422 },
314423 .OpTypeForwardPointer => continue, // We re-emit these where needed
315 // TODO: These aren't supported yet, strip them out for testing purposes.
316 .OpName, .OpMemberName => continue,
424 else => {},
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 },
317434 else => {},
318435 }
319436
......@@ -330,9 +447,8 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
330447 operand.* = @intFromEnum(new_id);
331448 }
332449
333 const id: ResultId = @enumFromInt(operand.*);
334 // TODO: This test is a little janky. Check the offset instead?
335 if (maybe_result_id == null or maybe_result_id.? != id) {
450 if (maybe_result_id_offset == null or maybe_result_id_offset.? != i) {
451 const id: ResultId = @enumFromInt(operand.*);
336452 const index = info.entities.getIndex(id) orelse continue;
337453 const entity = info.entities.values()[index];
338454 if (entity.kind == .OpTypePointer and !emitted_ptrs.contains(id)) {
......@@ -349,7 +465,8 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
349465 }
350466
351467 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, {});
353470 }
354471
355472 try section.emitRawInstruction(a, inst.opcode, new_operands.items);