authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-04-01 09:51:04+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-04-01 09:51:04+02:00
logd2be725e4b14c33dbd39054e33d926913eee3cd4
treed9bf0cde23ca8553192f0cc8bc77762ce0eaf1b4
parent3cb987f5a575bc5871459805a86640ba1a2d7cae
parent27b91288dc3c0442b645e06cae75c076600bdfd3
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #19490 from Snektron/spirv-dedup

spirv: deduplication pass

6 files changed, 568 insertions(+), 3 deletions(-)

src/codegen/spirv.zig+80
...@@ -2332,6 +2332,9 @@ const DeclGen = struct {...@@ -2332,6 +2332,9 @@ const DeclGen = struct {
23322332
2333 .mul_add => try self.airMulAdd(inst),2333 .mul_add => try self.airMulAdd(inst),
23342334
2335 .ctz => try self.airClzCtz(inst, .ctz),
2336 .clz => try self.airClzCtz(inst, .clz),
2337
2335 .splat => try self.airSplat(inst),2338 .splat => try self.airSplat(inst),
2336 .reduce, .reduce_optimized => try self.airReduce(inst),2339 .reduce, .reduce_optimized => try self.airReduce(inst),
2337 .shuffle => try self.airShuffle(inst),2340 .shuffle => try self.airShuffle(inst),
...@@ -3029,6 +3032,83 @@ const DeclGen = struct {...@@ -3029,6 +3032,83 @@ const DeclGen = struct {
3029 return try wip.finalize();3032 return try wip.finalize();
3030 }3033 }
30313034
3035 fn airClzCtz(self: *DeclGen, inst: Air.Inst.Index, op: enum { clz, ctz }) !?IdRef {
3036 if (self.liveness.isUnused(inst)) return null;
3037
3038 const mod = self.module;
3039 const target = self.getTarget();
3040 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
3041 const result_ty = self.typeOfIndex(inst);
3042 const operand_ty = self.typeOf(ty_op.operand);
3043 const operand = try self.resolve(ty_op.operand);
3044
3045 const info = self.arithmeticTypeInfo(operand_ty);
3046 switch (info.class) {
3047 .composite_integer => unreachable, // TODO
3048 .integer, .strange_integer => {},
3049 .float, .bool => unreachable,
3050 }
3051
3052 var wip = try self.elementWise(result_ty, false);
3053 defer wip.deinit();
3054
3055 const elem_ty = if (wip.is_array) operand_ty.scalarType(mod) else operand_ty;
3056 const elem_ty_ref = try self.resolveType(elem_ty, .direct);
3057 const elem_ty_id = self.typeId(elem_ty_ref);
3058
3059 for (wip.results, 0..) |*result_id, i| {
3060 const elem = try wip.elementAt(operand_ty, operand, i);
3061
3062 switch (target.os.tag) {
3063 .opencl => {
3064 const set = try self.spv.importInstructionSet(.@"OpenCL.std");
3065 const ext_inst: u32 = switch (op) {
3066 .clz => 151, // clz
3067 .ctz => 152, // ctz
3068 };
3069
3070 // Note: result of OpenCL ctz/clz returns operand_ty, and we want result_ty.
3071 // result_ty is always large enough to hold the result, so we might have to down
3072 // cast it.
3073 const tmp = self.spv.allocId();
3074 try self.func.body.emit(self.spv.gpa, .OpExtInst, .{
3075 .id_result_type = elem_ty_id,
3076 .id_result = tmp,
3077 .set = set,
3078 .instruction = .{ .inst = ext_inst },
3079 .id_ref_4 = &.{elem},
3080 });
3081
3082 if (wip.ty_id == elem_ty_id) {
3083 result_id.* = tmp;
3084 continue;
3085 }
3086
3087 result_id.* = self.spv.allocId();
3088 if (result_ty.scalarType(mod).isSignedInt(mod)) {
3089 assert(elem_ty.scalarType(mod).isSignedInt(mod));
3090 try self.func.body.emit(self.spv.gpa, .OpSConvert, .{
3091 .id_result_type = wip.ty_id,
3092 .id_result = result_id.*,
3093 .signed_value = tmp,
3094 });
3095 } else {
3096 assert(elem_ty.scalarType(mod).isUnsignedInt(mod));
3097 try self.func.body.emit(self.spv.gpa, .OpUConvert, .{
3098 .id_result_type = wip.ty_id,
3099 .id_result = result_id.*,
3100 .unsigned_value = tmp,
3101 });
3102 }
3103 },
3104 .vulkan => unreachable, // TODO
3105 else => unreachable,
3106 }
3107 }
3108
3109 return try wip.finalize();
3110 }
3111
3032 fn airSplat(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {3112 fn airSplat(self: *DeclGen, inst: Air.Inst.Index) !?IdRef {
3033 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;3113 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
3034 const operand_id = try self.resolve(ty_op.operand);3114 const operand_id = try self.resolve(ty_op.operand);
src/link/SpirV.zig+2
...@@ -261,6 +261,7 @@ fn linkModule(self: *SpirV, a: Allocator, module: []Word) ![]Word {...@@ -261,6 +261,7 @@ fn linkModule(self: *SpirV, a: Allocator, module: []Word) ![]Word {
261261
262 const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig");262 const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig");
263 const prune_unused = @import("SpirV/prune_unused.zig");263 const prune_unused = @import("SpirV/prune_unused.zig");
264 const dedup = @import("SpirV/deduplicate.zig");
264265
265 var parser = try BinaryModule.Parser.init(a);266 var parser = try BinaryModule.Parser.init(a);
266 defer parser.deinit();267 defer parser.deinit();
...@@ -268,6 +269,7 @@ fn linkModule(self: *SpirV, a: Allocator, module: []Word) ![]Word {...@@ -268,6 +269,7 @@ fn linkModule(self: *SpirV, a: Allocator, module: []Word) ![]Word {
268269
269 try lower_invocation_globals.run(&parser, &binary);270 try lower_invocation_globals.run(&parser, &binary);
270 try prune_unused.run(&parser, &binary);271 try prune_unused.run(&parser, &binary);
272 try dedup.run(&parser, &binary);
271273
272 return binary.finalize(a);274 return binary.finalize(a);
273}275}
src/link/SpirV/BinaryModule.zig+2
...@@ -94,6 +94,8 @@ pub const ParseError = error{...@@ -94,6 +94,8 @@ pub const ParseError = error{
94 DuplicateId,94 DuplicateId,
95 /// Some ID did not resolve.95 /// Some ID did not resolve.
96 InvalidId,96 InvalidId,
97 /// This opcode or instruction is not supported yet.
98 UnsupportedOperation,
97 /// Parser ran out of memory.99 /// Parser ran out of memory.
98 OutOfMemory,100 OutOfMemory,
99};101};
src/link/SpirV/deduplicate.zig created+482
...@@ -0,0 +1,482 @@
1const std = @import("std");
2const Allocator = std.mem.Allocator;
3const log = std.log.scoped(.spirv_link);
4const assert = std.debug.assert;
5
6const BinaryModule = @import("BinaryModule.zig");
7const Section = @import("../../codegen/spirv/Section.zig");
8const spec = @import("../../codegen/spirv/spec.zig");
9const Opcode = spec.Opcode;
10const ResultId = spec.IdResult;
11const Word = spec.Word;
12
13fn canDeduplicate(opcode: Opcode) bool {
14 return switch (opcode) {
15 .OpTypeForwardPointer => false, // Don't need to handle these
16 .OpGroupDecorate, .OpGroupMemberDecorate => {
17 // These are deprecated, so don't bother supporting them for now.
18 return false;
19 },
20 // Debug decoration-style instructions
21 .OpName, .OpMemberName => true,
22 else => switch (opcode.class()) {
23 .TypeDeclaration,
24 .ConstantCreation,
25 .Annotation,
26 => true,
27 else => false,
28 },
29 };
30}
31
32const ModuleInfo = struct {
33 /// This models a type, decoration or constant instruction
34 /// and its dependencies.
35 const Entity = struct {
36 /// The type that this entity represents. This is just
37 /// the instruction opcode.
38 kind: Opcode,
39 /// The offset of this entity's operands, in
40 /// `binary.instructions`.
41 first_operand: u32,
42 /// The number of operands in this entity
43 num_operands: u16,
44 /// The (first_operand-relative) offset of the result-id,
45 /// or the entity that is affected by this entity if this entity
46 /// is a decoration.
47 result_id_index: u16,
48 /// The first decoration in `self.decorations`.
49 first_decoration: u32,
50 };
51
52 /// Maps result-id to Entity's
53 entities: std.AutoArrayHashMapUnmanaged(ResultId, Entity),
54 /// A bit set that keeps track of which operands are result-ids.
55 /// Note: This also includes any result-id!
56 /// Because we need these values when recoding the module anyway,
57 /// it contains the status of ALL operands in the module.
58 operand_is_id: std.DynamicBitSetUnmanaged,
59 /// Store of decorations for each entity.
60 decorations: []const Entity,
61
62 pub fn parse(
63 arena: Allocator,
64 parser: *BinaryModule.Parser,
65 binary: BinaryModule,
66 ) !ModuleInfo {
67 var entities = std.AutoArrayHashMap(ResultId, Entity).init(arena);
68 var id_offsets = std.ArrayList(u16).init(arena);
69 var operand_is_id = try std.DynamicBitSetUnmanaged.initEmpty(arena, binary.instructions.len);
70 var decorations = std.MultiArrayList(struct { target_id: ResultId, entity: Entity }){};
71
72 var it = binary.iterateInstructions();
73 while (it.next()) |inst| {
74 id_offsets.items.len = 0;
75 try parser.parseInstructionResultIds(binary, inst, &id_offsets);
76
77 const first_operand_offset: u32 = @intCast(inst.offset + 1);
78 for (id_offsets.items) |offset| {
79 operand_is_id.set(first_operand_offset + offset);
80 }
81
82 if (!canDeduplicate(inst.opcode)) continue;
83
84 const result_id_index: u16 = switch (inst.opcode.class()) {
85 .TypeDeclaration, .Annotation, .Debug => 0,
86 .ConstantCreation => 1,
87 else => unreachable,
88 };
89
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 };
98
99 switch (inst.opcode.class()) {
100 .Annotation, .Debug => {
101 try decorations.append(arena, .{
102 .target_id = result_id,
103 .entity = entity,
104 });
105 },
106 .TypeDeclaration, .ConstantCreation => {
107 const entry = try entities.getOrPut(result_id);
108 if (entry.found_existing) {
109 log.err("type or constant {} has duplicate definition", .{result_id});
110 return error.DuplicateId;
111 }
112 entry.value_ptr.* = entity;
113 },
114 else => unreachable,
115 }
116 }
117
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
154 return ModuleInfo{
155 .entities = entities.unmanaged,
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],
160 };
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 }
173};
174
175const EntityContext = struct {
176 a: Allocator,
177 ptr_map_a: std.AutoArrayHashMapUnmanaged(ResultId, void) = .{},
178 ptr_map_b: std.AutoArrayHashMapUnmanaged(ResultId, void) = .{},
179 info: *const ModuleInfo,
180 binary: *const BinaryModule,
181
182 fn deinit(self: *EntityContext) void {
183 self.ptr_map_a.deinit(self.a);
184 self.ptr_map_b.deinit(self.a);
185
186 self.* = undefined;
187 }
188
189 fn equalizeMapCapacity(self: *EntityContext) !void {
190 const cap = @max(self.ptr_map_a.capacity(), self.ptr_map_b.capacity());
191 try self.ptr_map_a.ensureTotalCapacity(self.a, cap);
192 try self.ptr_map_b.ensureTotalCapacity(self.a, cap);
193 }
194
195 fn hash(self: *EntityContext, id: ResultId) !u64 {
196 var hasher = std.hash.Wyhash.init(0);
197 self.ptr_map_a.clearRetainingCapacity();
198 try self.hashInner(&hasher, id);
199 return hasher.final();
200 }
201
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
211 const entity = self.info.entities.values()[index];
212
213 if (entity.kind == .OpTypePointer) {
214 // This may be either a pointer that is forward-referenced in the future,
215 // or a forward reference to a pointer.
216 const entry = try self.ptr_map_a.getOrPut(self.a, id);
217 if (entry.found_existing) {
218 // Pointer already seen. Hash the index instead of recursing into its children.
219 std.hash.autoHash(hasher, entry.index);
220 return;
221 }
222 }
223
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);
235 // Process operands
236 const operands = self.binary.instructions[entity.first_operand..][0..entity.num_operands];
237 for (operands, 0..) |operand, i| {
238 if (i == entity.result_id_index) {
239 // Not relevant, skip...
240 continue;
241 } else if (self.info.operand_is_id.isSet(entity.first_operand + i)) {
242 // Operand is ID
243 try self.hashInner(hasher, @enumFromInt(operand));
244 } else {
245 // Operand is merely data
246 std.hash.autoHash(hasher, operand);
247 }
248 }
249 }
250
251 fn eql(self: *EntityContext, a: ResultId, b: ResultId) !bool {
252 self.ptr_map_a.clearRetainingCapacity();
253 self.ptr_map_b.clearRetainingCapacity();
254
255 return try self.eqlInner(a, b);
256 }
257
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;
272
273 const entity_a = self.info.entities.values()[index_a];
274 const entity_b = self.info.entities.values()[index_b];
275
276 if (entity_a.kind == .OpTypePointer) {
277 // May be a forward reference, or should be saved as a potential
278 // forward reference in the future. Whatever the case, it should
279 // be the same for both a and b.
280 const entry_a = try self.ptr_map_a.getOrPut(self.a, id_a);
281 const entry_b = try self.ptr_map_b.getOrPut(self.a, id_b);
282
283 if (entry_a.found_existing != entry_b.found_existing) return false;
284 if (entry_a.index != entry_b.index) return false;
285
286 if (entry_a.found_existing) {
287 // No need to recurse.
288 return true;
289 }
290 }
291
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
319 const operands_a = self.binary.instructions[entity_a.first_operand..][0..entity_a.num_operands];
320 const operands_b = self.binary.instructions[entity_b.first_operand..][0..entity_b.num_operands];
321
322 // Note: returns false for operands that have explicit defaults in optional operands... oh well
323 if (operands_a.len != operands_b.len) {
324 return false;
325 }
326
327 for (operands_a, operands_b, 0..) |operand_a, operand_b, i| {
328 const a_is_id = self.info.operand_is_id.isSet(entity_a.first_operand + i);
329 const b_is_id = self.info.operand_is_id.isSet(entity_b.first_operand + i);
330 if (a_is_id != b_is_id) {
331 return false;
332 } else if (i == entity_a.result_id_index) {
333 // result-id for both...
334 continue;
335 } else if (a_is_id) {
336 // Both are IDs, so recurse.
337 if (!try self.eqlInner(@enumFromInt(operand_a), @enumFromInt(operand_b))) {
338 return false;
339 }
340 } else if (operand_a != operand_b) {
341 return false;
342 }
343 }
344
345 return true;
346 }
347};
348
349/// This struct is a wrapper around EntityContext that adapts it for
350/// use in a hash map. Because EntityContext allocates, it cannot be
351/// used. This wrapper simply assumes that the maps have been allocated
352/// the max amount of memory they are going to use.
353/// This is done by pre-hashing all keys.
354const EntityHashContext = struct {
355 entity_context: *EntityContext,
356
357 pub fn hash(self: EntityHashContext, key: ResultId) u64 {
358 return self.entity_context.hash(key) catch unreachable;
359 }
360
361 pub fn eql(self: EntityHashContext, a: ResultId, b: ResultId) bool {
362 return self.entity_context.eql(a, b) catch unreachable;
363 }
364};
365
366pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
367 var arena = std.heap.ArenaAllocator.init(parser.a);
368 defer arena.deinit();
369 const a = arena.allocator();
370
371 const info = try ModuleInfo.parse(a, parser, binary.*);
372
373 // Hash all keys once so that the maps can be allocated the right size.
374 var ctx = EntityContext{
375 .a = a,
376 .info = &info,
377 .binary = binary,
378 };
379 for (info.entities.keys()) |id| {
380 _ = try ctx.hash(id);
381 }
382
383 // hash only uses ptr_map_a, so allocate ptr_map_b too
384 try ctx.equalizeMapCapacity();
385
386 // Figure out which entities can be deduplicated.
387 var map = std.HashMap(ResultId, void, EntityHashContext, 80).initContext(a, .{
388 .entity_context = &ctx,
389 });
390 var replace = std.AutoArrayHashMap(ResultId, ResultId).init(a);
391 for (info.entities.keys()) |id| {
392 const entry = try map.getOrPut(id);
393 if (entry.found_existing) {
394 try replace.putNoClobber(id, entry.key_ptr.*);
395 }
396 }
397
398 // Now process the module, and replace instructions where needed.
399 var section = Section{};
400 var it = binary.iterateInstructions();
401 var new_functions_section: ?usize = null;
402 var new_operands = std.ArrayList(u32).init(a);
403 var emitted_ptrs = std.AutoHashMap(ResultId, void).init(a);
404 while (it.next()) |inst| {
405 // Result-id can only be the first or second operand
406 const inst_spec = parser.getInstSpec(inst.opcode).?;
407
408 const maybe_result_id_offset: ?u16 = for (0..2) |i| {
409 if (inst_spec.operands.len > i and inst_spec.operands[i].kind == .IdResult) {
410 break @intCast(i);
411 }
412 } else null;
413
414 if (maybe_result_id_offset) |offset| {
415 const result_id: ResultId = @enumFromInt(inst.operands[offset]);
416 if (replace.contains(result_id)) continue;
417 }
418
419 switch (inst.opcode) {
420 .OpFunction => if (new_functions_section == null) {
421 new_functions_section = section.instructions.items.len;
422 },
423 .OpTypeForwardPointer => continue, // We re-emit these where needed
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 },
434 else => {},
435 }
436
437 // Re-emit the instruction, but replace all the IDs.
438
439 new_operands.items.len = 0;
440 try new_operands.appendSlice(inst.operands);
441
442 for (new_operands.items, 0..) |*operand, i| {
443 const is_id = info.operand_is_id.isSet(inst.offset + 1 + i);
444 if (!is_id) continue;
445
446 if (replace.get(@enumFromInt(operand.*))) |new_id| {
447 operand.* = @intFromEnum(new_id);
448 }
449
450 if (maybe_result_id_offset == null or maybe_result_id_offset.? != i) {
451 const id: ResultId = @enumFromInt(operand.*);
452 const index = info.entities.getIndex(id) orelse continue;
453 const entity = info.entities.values()[index];
454 if (entity.kind == .OpTypePointer and !emitted_ptrs.contains(id)) {
455 // Grab the pointer's storage class from its operands in the original
456 // module.
457 const storage_class: spec.StorageClass = @enumFromInt(binary.instructions[entity.first_operand + 1]);
458 try section.emit(a, .OpTypeForwardPointer, .{
459 .pointer_type = id,
460 .storage_class = storage_class,
461 });
462 try emitted_ptrs.put(id, {});
463 }
464 }
465 }
466
467 if (inst.opcode == .OpTypePointer) {
468 const result_id: ResultId = @enumFromInt(new_operands.items[maybe_result_id_offset.?]);
469 try emitted_ptrs.put(result_id, {});
470 }
471
472 try section.emitRawInstruction(a, inst.opcode, new_operands.items);
473 }
474
475 for (replace.keys()) |key| {
476 _ = binary.ext_inst_map.remove(key);
477 _ = binary.arith_type_width.remove(key);
478 }
479
480 binary.instructions = try parser.a.dupe(Word, section.toWords());
481 binary.sections.functions = new_functions_section orelse binary.instructions.len;
482}
test/behavior/destructure.zig+2
...@@ -23,6 +23,8 @@ test "simple destructure" {...@@ -23,6 +23,8 @@ test "simple destructure" {
23}23}
2424
25test "destructure with comptime syntax" {25test "destructure with comptime syntax" {
26 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
27
26 const S = struct {28 const S = struct {
27 fn doTheTest() !void {29 fn doTheTest() !void {
28 {30 {
test/behavior/math.zig-3
...@@ -65,7 +65,6 @@ test "@clz" {...@@ -65,7 +65,6 @@ test "@clz" {
65 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO65 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
66 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO66 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
67 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO67 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
68 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
6968
70 try testClz();69 try testClz();
71 try comptime testClz();70 try comptime testClz();
...@@ -148,7 +147,6 @@ test "@ctz" {...@@ -148,7 +147,6 @@ test "@ctz" {
148 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO147 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
149 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO148 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
150 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO149 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
151 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
152150
153 try testCtz();151 try testCtz();
154 try comptime testCtz();152 try comptime testCtz();
...@@ -1752,7 +1750,6 @@ test "@clz works on both vector and scalar inputs" {...@@ -1752,7 +1750,6 @@ test "@clz works on both vector and scalar inputs" {
1752 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO1750 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1753 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO1751 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1754 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO1752 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
1755 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
17561753
1757 var x: u32 = 0x1;1754 var x: u32 = 0x1;
1758 _ = &x;1755 _ = &x;