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 {
3535 /// The type that this entity represents. This is just
3636 /// the instruction opcode.
3737 kind: Opcode,
38 /// Offset of first child result-id, stored in entity_children.
39 /// These are the shallow entities appearing directly in the
40 /// type's instruction.
41 first_child: u32,
42 /// Offset to the first word of extra-data: Data in the instruction
43 /// that must be considered for uniqueness, but doesn't include
44 /// any IDs.
45 first_extra_data: u32,
38 /// The offset of this entity's operands, in
39 /// `binary.instructions`.
40 first_operand: u32,
41 /// The number of operands in this entity
42 num_operands: u16,
43 /// The (first_operand-relative) offset of the result-id,
44 /// or the entity that is affected by this entity if this entity
45 /// is a decoration.
46 result_id_index: u16,
4647 };
4748
4849 /// Maps result-id to Entity's
4950 entities: std.AutoArrayHashMapUnmanaged(ResultId, Entity),
50 /// The list of children per instruction.
51 entity_children: []const ResultId,
52 /// The list of extra data per instruction.
53 /// TODO: This is a bit awkward, maybe we need to store it some
54 /// other way?
55 extra_data: []const u32,
51 /// A bit set that keeps track of which operands are result-ids.
52 /// Note: This also includes any result-id!
53 /// Because we need these values when recoding the module anyway,
54 /// it contains the status of ALL operands in the module.
55 operand_is_id: std.DynamicBitSetUnmanaged,
5656
5757 pub fn parse(
5858 arena: Allocator,
......@@ -60,19 +60,22 @@ const ModuleInfo = struct {
6060 binary: BinaryModule,
6161 ) !ModuleInfo {
6262 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);
6563 var id_offsets = std.ArrayList(u16).init(arena);
64 var operand_is_id = try std.DynamicBitSetUnmanaged.initEmpty(arena, binary.instructions.len);
6665
6766 var it = binary.iterateInstructions();
6867 while (it.next()) |inst| {
69 if (inst.opcode == .OpFunction) break; // No more declarations are possible
70 if (!canDeduplicate(inst.opcode)) continue;
71
7268 id_offsets.items.len = 0;
7369 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()) {
7679 .TypeDeclaration, .Annotation, .Debug => 0,
7780 .ConstantCreation => 1,
7881 else => unreachable,
......@@ -80,27 +83,6 @@ const ModuleInfo = struct {
8083
8184 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
10486 switch (inst.opcode.class()) {
10587 .Annotation, .Debug => {
10688 // TODO
......@@ -113,8 +95,9 @@ const ModuleInfo = struct {
11395 }
11496 entry.value_ptr.* = .{
11597 .kind = inst.opcode,
116 .first_child = first_child,
117 .first_extra_data = first_extra_data,
98 .first_operand = first_operand_offset,
99 .num_operands = @intCast(inst.operands.len),
100 .result_id_index = result_id_index,
118101 };
119102 },
120103 else => unreachable,
......@@ -123,34 +106,9 @@ const ModuleInfo = struct {
123106
124107 return ModuleInfo{
125108 .entities = entities.unmanaged,
126 .entity_children = entity_children.items,
127 .extra_data = extra_data.items,
109 .operand_is_id = operand_is_id,
128110 };
129111 }
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 }
154112};
155113
156114const EntityContext = struct {
......@@ -158,13 +116,7 @@ const EntityContext = struct {
158116 ptr_map_a: std.AutoArrayHashMapUnmanaged(ResultId, void) = .{},
159117 ptr_map_b: std.AutoArrayHashMapUnmanaged(ResultId, void) = .{},
160118 info: *const ModuleInfo,
161
162 fn init(a: Allocator, info: *const ModuleInfo) EntityContext {
163 return .{
164 .a = a,
165 .info = info,
166 };
167 }
119 binary: *const BinaryModule,
168120
169121 fn deinit(self: *EntityContext) void {
170122 self.ptr_map_a.deinit(self.a);
......@@ -203,14 +155,19 @@ const EntityContext = struct {
203155 }
204156 }
205157
206 // Hash extra data
207 for (self.info.extraDataByIndex(index)) |data| {
208 std.hash.autoHash(hasher, data);
209 }
210
211 // Hash children
212 for (self.info.childrenByIndex(index)) |child| {
213 try self.hashInner(hasher, child);
158 // Process operands
159 const operands = self.binary.instructions[entity.first_operand..][0..entity.num_operands];
160 for (operands, 0..) |operand, i| {
161 if (i == entity.result_id_index) {
162 // Not relevant, skip...
163 continue;
164 } else if (self.info.operand_is_id.isSet(entity.first_operand + i)) {
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 }
214171 }
215172 }
216173
......@@ -228,7 +185,11 @@ const EntityContext = struct {
228185 const entity_a = self.info.entities.values()[index_a];
229186 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
233194 if (entity_a.kind == .OpTypePointer) {
234195 // May be a forward reference, or should be saved as a potential
......@@ -246,18 +207,28 @@ const EntityContext = struct {
246207 }
247208 }
248209
249 // Check if extra data is the same.
250 if (!std.mem.eql(u32, self.info.extraDataByIndex(index_a), self.info.extraDataByIndex(index_b))) {
210 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];
212
213 // Note: returns false for operands that have explicit defaults in optional operands... oh well
214 if (operands_a.len != operands_b.len) {
251215 return false;
252216 }
253217
254 // Recursively check if children are the same
255 const children_a = self.info.childrenByIndex(index_a);
256 const children_b = self.info.childrenByIndex(index_b);
257 if (children_a.len != children_b.len) return false;
258
259 for (children_a, children_b) |child_a, child_b| {
260 if (!try self.eqlInner(child_a, child_b)) {
218 for (operands_a, operands_b, 0..) |operand_a, operand_b, i| {
219 const a_is_id = self.info.operand_is_id.isSet(entity_a.first_operand + i);
220 const b_is_id = self.info.operand_is_id.isSet(entity_b.first_operand + i);
221 if (a_is_id != b_is_id) {
222 return false;
223 } else if (i == entity_a.result_id_index) {
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) {
261232 return false;
262233 }
263234 }
......@@ -290,11 +261,13 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
290261
291262 const info = try ModuleInfo.parse(a, parser, binary.*);
292263 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
296265 // 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 };
298271 for (info.entities.keys()) |id| {
299272 _ = try ctx.hash(id);
300273 }
......@@ -318,7 +291,6 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
318291 // Now process the module, and replace instructions where needed.
319292 var section = Section{};
320293 var it = binary.iterateInstructions();
321 var id_offsets = std.ArrayList(u16).init(a);
322294 var new_functions_section: ?usize = null;
323295 var new_operands = std.ArrayList(u32).init(a);
324296 var emitted_ptrs = std.AutoHashMap(ResultId, void).init(a);
......@@ -347,38 +319,31 @@ pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
347319
348320 // 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
353322 new_operands.items.len = 0;
354323 try new_operands.appendSlice(inst.operands);
355 for (id_offsets.items) |offset| {
356 {
357 const id: ResultId = @enumFromInt(inst.operands[offset]);
358 if (replace.get(id)) |new_id| {
359 new_operands.items[offset] = @intFromEnum(new_id);
360 }
324
325 for (new_operands.items, 0..) |*operand, i| {
326 const is_id = info.operand_is_id.isSet(inst.offset + 1 + i);
327 if (!is_id) continue;
328
329 if (replace.get(@enumFromInt(operand.*))) |new_id| {
330 operand.* = @intFromEnum(new_id);
361331 }
362332
363 // TODO: Does this logic work? Maybe it will emit an OpTypeForwardPointer to
364 // something thats not a struct...
365 // It seems to work correctly on behavior.zig at least
366 const id: ResultId = @enumFromInt(new_operands.items[offset]);
333 const id: ResultId = @enumFromInt(operand.*);
334 // TODO: This test is a little janky. Check the offset instead?
367335 if (maybe_result_id == null or maybe_result_id.? != id) {
368336 const index = info.entities.getIndex(id) orelse continue;
369337 const entity = info.entities.values()[index];
370 if (entity.kind == .OpTypePointer) {
371 if (!emitted_ptrs.contains(id)) {
372 // The storage class is in the extra data
373 // TODO: This is kind of hacky...
374 const extra_data = info.extraDataByIndex(index);
375 const storage_class: spec.StorageClass = @enumFromInt(extra_data[0]);
376 try section.emit(a, .OpTypeForwardPointer, .{
377 .pointer_type = id,
378 .storage_class = storage_class,
379 });
380 try emitted_ptrs.put(id, {});
381 }
338 if (entity.kind == .OpTypePointer and !emitted_ptrs.contains(id)) {
339 // Grab the pointer's storage class from its operands in the original
340 // module.
341 const storage_class: spec.StorageClass = @enumFromInt(binary.instructions[entity.first_operand + 1]);
342 try section.emit(a, .OpTypeForwardPointer, .{
343 .pointer_type = id,
344 .storage_class = storage_class,
345 });
346 try emitted_ptrs.put(id, {});
382347 }
383348 }
384349 }