authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-03-29 23:39:30+01:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-03-30 19:47:52+01:00
logb4960394efa71a8246b10b46165292f1797aaf87
treed2c9be2d2ae03fac1061593e34fe98aedfde9212
parent393a805741c2528a9eb46c457d2e8f939a7fb2b3
signaturebadge-check Signed by SSH key SHA256:ZS52FNyUv2WUXvO4njmVaFVO46RHojFuOrxRc4LuKzg

spirv: avoid copying operands in dedup pass


1 files changed, 88 insertions(+), 123 deletions(-)

src/link/SpirV/deduplicate.zig+88-123
...@@ -35,24 +35,24 @@ const ModuleInfo = struct {...@@ -35,24 +35,24 @@ const ModuleInfo = struct {
35 /// The type that this entity represents. This is just35 /// The type that this entity represents. This is just
36 /// the instruction opcode.36 /// the instruction opcode.
37 kind: Opcode,37 kind: Opcode,
38 /// Offset of first child result-id, stored in entity_children.38 /// The offset of this entity's operands, in
39 /// These are the shallow entities appearing directly in the39 /// `binary.instructions`.
40 /// type's instruction.40 first_operand: u32,
41 first_child: u32,41 /// The number of operands in this entity
42 /// Offset to the first word of extra-data: Data in the instruction42 num_operands: u16,
43 /// that must be considered for uniqueness, but doesn't include43 /// The (first_operand-relative) offset of the result-id,
44 /// any IDs.44 /// or the entity that is affected by this entity if this entity
45 first_extra_data: u32,45 /// is a decoration.
46 result_id_index: u16,
46 };47 };
4748
48 /// Maps result-id to Entity's49 /// Maps result-id to Entity's
49 entities: std.AutoArrayHashMapUnmanaged(ResultId, Entity),50 entities: std.AutoArrayHashMapUnmanaged(ResultId, Entity),
50 /// The list of children per instruction.51 /// A bit set that keeps track of which operands are result-ids.
51 entity_children: []const ResultId,52 /// Note: This also includes any result-id!
52 /// The list of extra data per instruction.53 /// Because we need these values when recoding the module anyway,
53 /// TODO: This is a bit awkward, maybe we need to store it some54 /// it contains the status of ALL operands in the module.
54 /// other way?55 operand_is_id: std.DynamicBitSetUnmanaged,
55 extra_data: []const u32,
5656
57 pub fn parse(57 pub fn parse(
58 arena: Allocator,58 arena: Allocator,
...@@ -60,19 +60,22 @@ const ModuleInfo = struct {...@@ -60,19 +60,22 @@ const ModuleInfo = struct {
60 binary: BinaryModule,60 binary: BinaryModule,
61 ) !ModuleInfo {61 ) !ModuleInfo {
62 var entities = std.AutoArrayHashMap(ResultId, Entity).init(arena);62 var entities = std.AutoArrayHashMap(ResultId, Entity).init(arena);
63 var entity_children = std.ArrayList(ResultId).init(arena);
64 var extra_data = std.ArrayList(u32).init(arena);
65 var id_offsets = std.ArrayList(u16).init(arena);63 var id_offsets = std.ArrayList(u16).init(arena);
64 var operand_is_id = try std.DynamicBitSetUnmanaged.initEmpty(arena, binary.instructions.len);
6665
67 var it = binary.iterateInstructions();66 var it = binary.iterateInstructions();
68 while (it.next()) |inst| {67 while (it.next()) |inst| {
69 if (inst.opcode == .OpFunction) break; // No more declarations are possible
70 if (!canDeduplicate(inst.opcode)) continue;
71
72 id_offsets.items.len = 0;68 id_offsets.items.len = 0;
73 try parser.parseInstructionResultIds(binary, inst, &id_offsets);69 try parser.parseInstructionResultIds(binary, inst, &id_offsets);
7470
75 const result_id_index: u32 = switch (inst.opcode.class()) {71 const first_operand_offset: u32 = @intCast(inst.offset + 1);
72 for (id_offsets.items) |offset| {
73 operand_is_id.set(first_operand_offset + offset);
74 }
75
76 if (!canDeduplicate(inst.opcode)) continue;
77
78 const result_id_index: u16 = switch (inst.opcode.class()) {
76 .TypeDeclaration, .Annotation, .Debug => 0,79 .TypeDeclaration, .Annotation, .Debug => 0,
77 .ConstantCreation => 1,80 .ConstantCreation => 1,
78 else => unreachable,81 else => unreachable,
...@@ -80,27 +83,6 @@ const ModuleInfo = struct {...@@ -80,27 +83,6 @@ const ModuleInfo = struct {
8083
81 const result_id: ResultId = @enumFromInt(inst.operands[id_offsets.items[result_id_index]]);84 const result_id: ResultId = @enumFromInt(inst.operands[id_offsets.items[result_id_index]]);
8285
83 const first_child: u32 = @intCast(entity_children.items.len);
84 const first_extra_data: u32 = @intCast(extra_data.items.len);
85
86 try entity_children.ensureUnusedCapacity(id_offsets.items.len - 1);
87 try extra_data.ensureUnusedCapacity(inst.operands.len - id_offsets.items.len);
88
89 var id_i: usize = 0;
90 for (inst.operands, 0..) |operand, i| {
91 assert(id_i == id_offsets.items.len or id_offsets.items[id_i] >= i);
92 if (id_i != id_offsets.items.len and id_offsets.items[id_i] == i) {
93 // Skip .IdResult / .IdResultType.
94 if (id_i != result_id_index) {
95 entity_children.appendAssumeCapacity(@enumFromInt(operand));
96 }
97 id_i += 1;
98 } else {
99 // Non-id operand, add it to extra data.
100 extra_data.appendAssumeCapacity(operand);
101 }
102 }
103
104 switch (inst.opcode.class()) {86 switch (inst.opcode.class()) {
105 .Annotation, .Debug => {87 .Annotation, .Debug => {
106 // TODO88 // TODO
...@@ -113,8 +95,9 @@ const ModuleInfo = struct {...@@ -113,8 +95,9 @@ const ModuleInfo = struct {
113 }95 }
114 entry.value_ptr.* = .{96 entry.value_ptr.* = .{
115 .kind = inst.opcode,97 .kind = inst.opcode,
116 .first_child = first_child,98 .first_operand = first_operand_offset,
117 .first_extra_data = first_extra_data,99 .num_operands = @intCast(inst.operands.len),
100 .result_id_index = result_id_index,
118 };101 };
119 },102 },
120 else => unreachable,103 else => unreachable,
...@@ -123,34 +106,9 @@ const ModuleInfo = struct {...@@ -123,34 +106,9 @@ const ModuleInfo = struct {
123106
124 return ModuleInfo{107 return ModuleInfo{
125 .entities = entities.unmanaged,108 .entities = entities.unmanaged,
126 .entity_children = entity_children.items,109 .operand_is_id = operand_is_id,
127 .extra_data = extra_data.items,
128 };110 };
129 }111 }
130
131 /// Fetch a slice of children for the index corresponding to an entity.
132 fn childrenByIndex(self: ModuleInfo, index: usize) []const ResultId {
133 const values = self.entities.values();
134 const first_child = values[index].first_child;
135 if (index == values.len - 1) {
136 return self.entity_children[first_child..];
137 } else {
138 const next_first_child = values[index + 1].first_child;
139 return self.entity_children[first_child..next_first_child];
140 }
141 }
142
143 /// Fetch the slice of extra-data for the index corresponding to an entity.
144 fn extraDataByIndex(self: ModuleInfo, index: usize) []const u32 {
145 const values = self.entities.values();
146 const first_extra_data = values[index].first_extra_data;
147 if (index == values.len - 1) {
148 return self.extra_data[first_extra_data..];
149 } else {
150 const next_extra_data = values[index + 1].first_extra_data;
151 return self.extra_data[first_extra_data..next_extra_data];
152 }
153 }
154};112};
155113
156const EntityContext = struct {114const EntityContext = struct {
...@@ -158,13 +116,7 @@ const EntityContext = struct {...@@ -158,13 +116,7 @@ const EntityContext = struct {
158 ptr_map_a: std.AutoArrayHashMapUnmanaged(ResultId, void) = .{},116 ptr_map_a: std.AutoArrayHashMapUnmanaged(ResultId, void) = .{},
159 ptr_map_b: std.AutoArrayHashMapUnmanaged(ResultId, void) = .{},117 ptr_map_b: std.AutoArrayHashMapUnmanaged(ResultId, void) = .{},
160 info: *const ModuleInfo,118 info: *const ModuleInfo,
161119 binary: *const BinaryModule,
162 fn init(a: Allocator, info: *const ModuleInfo) EntityContext {
163 return .{
164 .a = a,
165 .info = info,
166 };
167 }
168120
169 fn deinit(self: *EntityContext) void {121 fn deinit(self: *EntityContext) void {
170 self.ptr_map_a.deinit(self.a);122 self.ptr_map_a.deinit(self.a);
...@@ -203,14 +155,19 @@ const EntityContext = struct {...@@ -203,14 +155,19 @@ const EntityContext = struct {
203 }155 }
204 }156 }
205157
206 // Hash extra data158 // Process operands
207 for (self.info.extraDataByIndex(index)) |data| {159 const operands = self.binary.instructions[entity.first_operand..][0..entity.num_operands];
208 std.hash.autoHash(hasher, data);160 for (operands, 0..) |operand, i| {
209 }161 if (i == entity.result_id_index) {
210162 // Not relevant, skip...
211 // Hash children163 continue;
212 for (self.info.childrenByIndex(index)) |child| {164 } else if (self.info.operand_is_id.isSet(entity.first_operand + i)) {
213 try self.hashInner(hasher, child);165 // Operand is ID
166 try self.hashInner(hasher, @enumFromInt(operand));
167 } else {
168 // Operand is merely data
169 std.hash.autoHash(hasher, operand);
170 }
214 }171 }
215 }172 }
216173
...@@ -228,7 +185,11 @@ const EntityContext = struct {...@@ -228,7 +185,11 @@ const EntityContext = struct {
228 const entity_a = self.info.entities.values()[index_a];185 const entity_a = self.info.entities.values()[index_a];
229 const entity_b = self.info.entities.values()[index_b];186 const entity_b = self.info.entities.values()[index_b];
230187
231 if (entity_a.kind != entity_b.kind) return false;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 }
232193
233 if (entity_a.kind == .OpTypePointer) {194 if (entity_a.kind == .OpTypePointer) {
234 // May be a forward reference, or should be saved as a potential195 // May be a forward reference, or should be saved as a potential
...@@ -246,18 +207,28 @@ const EntityContext = struct {...@@ -246,18 +207,28 @@ const EntityContext = struct {
246 }207 }
247 }208 }
248209
249 // Check if extra data is the same.210 const operands_a = self.binary.instructions[entity_a.first_operand..][0..entity_a.num_operands];
250 if (!std.mem.eql(u32, self.info.extraDataByIndex(index_a), self.info.extraDataByIndex(index_b))) {211 const operands_b = self.binary.instructions[entity_b.first_operand..][0..entity_b.num_operands];
212
213 // Note: returns false for operands that have explicit defaults in optional operands... oh well
214 if (operands_a.len != operands_b.len) {
251 return false;215 return false;
252 }216 }
253217
254 // Recursively check if children are the same218 for (operands_a, operands_b, 0..) |operand_a, operand_b, i| {
255 const children_a = self.info.childrenByIndex(index_a);219 const a_is_id = self.info.operand_is_id.isSet(entity_a.first_operand + i);
256 const children_b = self.info.childrenByIndex(index_b);220 const b_is_id = self.info.operand_is_id.isSet(entity_b.first_operand + i);
257 if (children_a.len != children_b.len) return false;221 if (a_is_id != b_is_id) {
258222 return false;
259 for (children_a, children_b) |child_a, child_b| {223 } else if (i == entity_a.result_id_index) {
260 if (!try self.eqlInner(child_a, child_b)) {224 // result-id for both...
225 continue;
226 } else if (a_is_id) {
227 // Both are IDs, so recurse.
228 if (!try self.eqlInner(@enumFromInt(operand_a), @enumFromInt(operand_b))) {
229 return false;
230 }
231 } else if (operand_a != operand_b) {
261 return false;232 return false;
262 }233 }
263 }234 }
...@@ -290,11 +261,13 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {...@@ -290,11 +261,13 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
290261
291 const info = try ModuleInfo.parse(a, parser, binary.*);262 const info = try ModuleInfo.parse(a, parser, binary.*);
292 log.info("added {} entities", .{info.entities.count()});263 log.info("added {} entities", .{info.entities.count()});
293 log.info("children size: {}", .{info.entity_children.len});
294 log.info("extra data size: {}", .{info.extra_data.len});
295264
296 // Hash all keys once so that the maps can be allocated the right size.265 // Hash all keys once so that the maps can be allocated the right size.
297 var ctx = EntityContext.init(a, &info);266 var ctx = EntityContext{
267 .a = a,
268 .info = &info,
269 .binary = binary,
270 };
298 for (info.entities.keys()) |id| {271 for (info.entities.keys()) |id| {
299 _ = try ctx.hash(id);272 _ = try ctx.hash(id);
300 }273 }
...@@ -318,7 +291,6 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {...@@ -318,7 +291,6 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
318 // Now process the module, and replace instructions where needed.291 // Now process the module, and replace instructions where needed.
319 var section = Section{};292 var section = Section{};
320 var it = binary.iterateInstructions();293 var it = binary.iterateInstructions();
321 var id_offsets = std.ArrayList(u16).init(a);
322 var new_functions_section: ?usize = null;294 var new_functions_section: ?usize = null;
323 var new_operands = std.ArrayList(u32).init(a);295 var new_operands = std.ArrayList(u32).init(a);
324 var emitted_ptrs = std.AutoHashMap(ResultId, void).init(a);296 var emitted_ptrs = std.AutoHashMap(ResultId, void).init(a);
...@@ -347,38 +319,31 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {...@@ -347,38 +319,31 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
347319
348 // Re-emit the instruction, but replace all the IDs.320 // Re-emit the instruction, but replace all the IDs.
349321
350 id_offsets.items.len = 0;
351 try parser.parseInstructionResultIds(binary.*, inst, &id_offsets);
352
353 new_operands.items.len = 0;322 new_operands.items.len = 0;
354 try new_operands.appendSlice(inst.operands);323 try new_operands.appendSlice(inst.operands);
355 for (id_offsets.items) |offset| {324
356 {325 for (new_operands.items, 0..) |*operand, i| {
357 const id: ResultId = @enumFromInt(inst.operands[offset]);326 const is_id = info.operand_is_id.isSet(inst.offset + 1 + i);
358 if (replace.get(id)) |new_id| {327 if (!is_id) continue;
359 new_operands.items[offset] = @intFromEnum(new_id);328
360 }329 if (replace.get(@enumFromInt(operand.*))) |new_id| {
330 operand.* = @intFromEnum(new_id);
361 }331 }
362332
363 // TODO: Does this logic work? Maybe it will emit an OpTypeForwardPointer to333 const id: ResultId = @enumFromInt(operand.*);
364 // something thats not a struct...334 // TODO: This test is a little janky. Check the offset instead?
365 // It seems to work correctly on behavior.zig at least
366 const id: ResultId = @enumFromInt(new_operands.items[offset]);
367 if (maybe_result_id == null or maybe_result_id.? != id) {335 if (maybe_result_id == null or maybe_result_id.? != id) {
368 const index = info.entities.getIndex(id) orelse continue;336 const index = info.entities.getIndex(id) orelse continue;
369 const entity = info.entities.values()[index];337 const entity = info.entities.values()[index];
370 if (entity.kind == .OpTypePointer) {338 if (entity.kind == .OpTypePointer and !emitted_ptrs.contains(id)) {
371 if (!emitted_ptrs.contains(id)) {339 // Grab the pointer's storage class from its operands in the original
372 // The storage class is in the extra data340 // module.
373 // TODO: This is kind of hacky...341 const storage_class: spec.StorageClass = @enumFromInt(binary.instructions[entity.first_operand + 1]);
374 const extra_data = info.extraDataByIndex(index);342 try section.emit(a, .OpTypeForwardPointer, .{
375 const storage_class: spec.StorageClass = @enumFromInt(extra_data[0]);343 .pointer_type = id,
376 try section.emit(a, .OpTypeForwardPointer, .{344 .storage_class = storage_class,
377 .pointer_type = id,345 });
378 .storage_class = storage_class,346 try emitted_ptrs.put(id, {});
379 });
380 try emitted_ptrs.put(id, {});
381 }
382 }347 }
383 }348 }
384 }349 }