authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-03-17 14:16:04+01:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2024-03-18 19:13:50+01:00
log8ed134243ac9b3d1286153f95495176875472669
tree4a698f99f03aea95c42335ad44fa5687265a57d5
parent9b18125562b2402cae8450253decd906f09e4dc6
signaturebadge-check Signed by SSH key SHA256:ZS52FNyUv2WUXvO4njmVaFVO46RHojFuOrxRc4LuKzg

spirv: unused instruction pruning linker pass


5 files changed, 426 insertions(+), 62 deletions(-)

src/link/SpirV.zig+13-8
...@@ -245,26 +245,31 @@ pub fn flushModule(self: *SpirV, arena: Allocator, prog_node: *std.Progress.Node...@@ -245,26 +245,31 @@ pub fn flushModule(self: *SpirV, arena: Allocator, prog_node: *std.Progress.Node
245 const module = try spv.finalize(arena, target);245 const module = try spv.finalize(arena, target);
246 errdefer arena.free(module);246 errdefer arena.free(module);
247247
248 const new_module = self.lowerInstanceGlobals(arena, module) catch |err| switch (err) {248 const linked_module = self.linkModule(arena, module) catch |err| switch (err) {
249 error.OutOfMemory => return error.OutOfMemory,249 error.OutOfMemory => return error.OutOfMemory,
250 else => |other| {250 else => |other| {
251 std.debug.print("error while lowering instance globals: {s}\n", .{@errorName(other)});251 log.err("error while linking: {s}\n", .{@errorName(other)});
252 return error.FlushFailure;252 return error.FlushFailure;
253 },253 },
254 };254 };
255 defer arena.free(new_module);
256255
257 try self.base.file.?.writeAll(std.mem.sliceAsBytes(new_module));256 try self.base.file.?.writeAll(std.mem.sliceAsBytes(linked_module));
258}257}
259258
260fn lowerInstanceGlobals(self: *SpirV, a: Allocator, module: []Word) ![]Word {259fn linkModule(self: *SpirV, a: Allocator, module: []Word) ![]Word {
261 _ = self;260 _ = self;
262261
262 const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig");
263 const prune_unused = @import("SpirV/prune_unused.zig");
264
263 var parser = try BinaryModule.Parser.init(a);265 var parser = try BinaryModule.Parser.init(a);
264 defer parser.deinit();266 defer parser.deinit();
265 const binary = try parser.parse(module);267 var binary = try parser.parse(module);
266 const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig");268
267 return try lower_invocation_globals.run(&parser, binary);269 try lower_invocation_globals.run(&parser, &binary);
270 try prune_unused.run(&parser, &binary);
271
272 return binary.finalize(a);
268}273}
269274
270fn writeCapabilities(spv: *SpvModule, target: std.Target) !void {275fn writeCapabilities(spv: *SpvModule, target: std.Target) !void {
src/link/SpirV/BinaryModule.zig+41-23
...@@ -45,11 +45,30 @@ pub fn deinit(self: *BinaryModule, a: Allocator) void {...@@ -45,11 +45,30 @@ pub fn deinit(self: *BinaryModule, a: Allocator) void {
45}45}
4646
47pub fn iterateInstructions(self: BinaryModule) Instruction.Iterator {47pub fn iterateInstructions(self: BinaryModule) Instruction.Iterator {
48 return Instruction.Iterator.init(self.instructions);48 return Instruction.Iterator.init(self.instructions, 0);
49}49}
5050
51pub fn iterateInstructionsFrom(self: BinaryModule, offset: usize) Instruction.Iterator {51pub fn iterateInstructionsFrom(self: BinaryModule, offset: usize) Instruction.Iterator {
52 return Instruction.Iterator.init(self.instructions[offset..]);52 return Instruction.Iterator.init(self.instructions, offset);
53}
54
55pub fn instructionAt(self: BinaryModule, offset: usize) Instruction {
56 var it = self.iterateInstructionsFrom(offset);
57 return it.next().?;
58}
59
60pub fn finalize(self: BinaryModule, a: Allocator) ![]Word {
61 const result = try a.alloc(Word, 5 + self.instructions.len);
62 errdefer a.free(result);
63
64 result[0] = spec.magic_number;
65 result[1] = @bitCast(self.version);
66 result[2] = spec.zig_generator_id;
67 result[3] = self.id_bound;
68 result[4] = 0; // Schema
69
70 @memcpy(result[5..], self.instructions);
71 return result;
53}72}
5473
55/// Errors that can be raised when the module is not correct.74/// Errors that can be raised when the module is not correct.
...@@ -85,8 +104,8 @@ pub const Instruction = struct {...@@ -85,8 +104,8 @@ pub const Instruction = struct {
85 index: usize = 0,104 index: usize = 0,
86 offset: usize = 0,105 offset: usize = 0,
87106
88 pub fn init(words: []const Word) Iterator {107 pub fn init(words: []const Word, start_offset: usize) Iterator {
89 return .{ .words = words };108 return .{ .words = words, .offset = start_offset };
90 }109 }
91110
92 pub fn next(self: *Iterator) ?Instruction {111 pub fn next(self: *Iterator) ?Instruction {
...@@ -159,6 +178,11 @@ pub const Parser = struct {...@@ -159,6 +178,11 @@ pub const Parser = struct {
159 return (@as(u32, @intFromEnum(set)) << 16) | opcode;178 return (@as(u32, @intFromEnum(set)) << 16) | opcode;
160 }179 }
161180
181 pub fn getInstSpec(self: Parser, opcode: Opcode) ?spec.Instruction {
182 const index = self.opcode_table.get(mapSetAndOpcode(.core, @intFromEnum(opcode))) orelse return null;
183 return InstructionSet.core.instructions()[index];
184 }
185
162 pub fn parse(self: *Parser, module: []const u32) ParseError!BinaryModule {186 pub fn parse(self: *Parser, module: []const u32) ParseError!BinaryModule {
163 if (module[0] != spec.magic_number) {187 if (module[0] != spec.magic_number) {
164 return error.InvalidMagic;188 return error.InvalidMagic;
...@@ -195,13 +219,12 @@ pub const Parser = struct {...@@ -195,13 +219,12 @@ pub const Parser = struct {
195 // We can't really efficiently use non-exhaustive enums here, because we would219 // We can't really efficiently use non-exhaustive enums here, because we would
196 // need to manually write out all valid cases. Since we have this map anyway, just220 // need to manually write out all valid cases. Since we have this map anyway, just
197 // use that.221 // use that.
198 const opcode_num: u16 = @truncate(binary.instructions[offset]);222 const opcode: Opcode = @enumFromInt(@as(u16, @truncate(binary.instructions[offset])));
199 const index = self.opcode_table.get(mapSetAndOpcode(.core, opcode_num)) orelse {223 const inst_spec = self.getInstSpec(opcode) orelse {
200 log.err("invalid opcode for core set: {}", .{opcode_num});224 log.err("invalid opcode for core set: {}", .{@intFromEnum(opcode)});
201 return error.InvalidOpcode;225 return error.InvalidOpcode;
202 };226 };
203227
204 const opcode: Opcode = @enumFromInt(opcode_num);
205 const operands = binary.instructions[offset..][1..len];228 const operands = binary.instructions[offset..][1..len];
206 switch (opcode) {229 switch (opcode) {
207 .OpExtInstImport => {230 .OpExtInstImport => {
...@@ -226,11 +249,10 @@ pub const Parser = struct {...@@ -226,11 +249,10 @@ pub const Parser = struct {
226249
227 // OpSwitch takes a value as argument, not an OpType... hence we need to populate arith_type_width250 // OpSwitch takes a value as argument, not an OpType... hence we need to populate arith_type_width
228 // with ALL operations that return an int or float.251 // with ALL operations that return an int or float.
229 const proper_operands = InstructionSet.core.instructions()[index].operands;252 const spec_operands = inst_spec.operands;
230253 if (spec_operands.len >= 2 and
231 if (proper_operands.len >= 2 and254 spec_operands[0].kind == .IdResultType and
232 proper_operands[0].kind == .IdResultType and255 spec_operands[1].kind == .IdResult)
233 proper_operands[1].kind == .IdResult)
234 {256 {
235 if (operands.len < 2) return error.InvalidOperands;257 if (operands.len < 2) return error.InvalidOperands;
236 if (binary.arith_type_width.get(@enumFromInt(operands[0]))) |width| {258 if (binary.arith_type_width.get(@enumFromInt(operands[0]))) |width| {
...@@ -283,6 +305,7 @@ pub const Parser = struct {...@@ -283,6 +305,7 @@ pub const Parser = struct {
283305
284 if (offset + 1 >= inst.operands.len) return error.InvalidPhysicalFormat;306 if (offset + 1 >= inst.operands.len) return error.InvalidPhysicalFormat;
285 const set_id: ResultId = @enumFromInt(inst.operands[offset]);307 const set_id: ResultId = @enumFromInt(inst.operands[offset]);
308 try offsets.append(@intCast(offset));
286 const set = binary.ext_inst_map.get(set_id) orelse {309 const set = binary.ext_inst_map.get(set_id) orelse {
287 log.err("invalid instruction set {}", .{@intFromEnum(set_id)});310 log.err("invalid instruction set {}", .{@intFromEnum(set_id)});
288 return error.InvalidId;311 return error.InvalidId;
...@@ -375,8 +398,7 @@ pub const Parser = struct {...@@ -375,8 +398,7 @@ pub const Parser = struct {
375 }398 }
376 },399 },
377 .id => {400 .id => {
378 const this_offset = std.math.cast(u16, offset) orelse return error.InvalidPhysicalFormat;401 try offsets.append(@intCast(offset));
379 try offsets.append(this_offset);
380 offset += 1;402 offset += 1;
381 },403 },
382 else => switch (kind) {404 else => switch (kind) {
...@@ -419,20 +441,16 @@ pub const Parser = struct {...@@ -419,20 +441,16 @@ pub const Parser = struct {
419 33...64 => 2,441 33...64 => 2,
420 else => unreachable,442 else => unreachable,
421 };443 };
422 const this_offset = std.math.cast(u16, offset) orelse return error.InvalidPhysicalFormat;444 try offsets.append(@intCast(offset));
423 try offsets.append(this_offset);
424 offset += 1;445 offset += 1;
425 },446 },
426 .PairIdRefLiteralInteger => {447 .PairIdRefLiteralInteger => {
427 const this_offset = std.math.cast(u16, offset) orelse return error.InvalidPhysicalFormat;448 try offsets.append(@intCast(offset));
428 try offsets.append(this_offset);
429 offset += 2;449 offset += 2;
430 },450 },
431 .PairIdRefIdRef => {451 .PairIdRefIdRef => {
432 const a = std.math.cast(u16, offset) orelse return error.InvalidPhysicalFormat;452 try offsets.append(@intCast(offset));
433 const b = std.math.cast(u16, offset + 1) orelse return error.InvalidPhysicalFormat;453 try offsets.append(@intCast(offset + 1));
434 try offsets.append(a);
435 try offsets.append(b);
436 offset += 2;454 offset += 2;
437 },455 },
438 else => unreachable,456 else => unreachable,
src/link/SpirV/lower_invocation_globals.zig+17-31
...@@ -345,23 +345,16 @@ const ModuleBuilder = struct {...@@ -345,23 +345,16 @@ const ModuleBuilder = struct {
345 function_types: std.ArrayHashMapUnmanaged(FunctionType, ResultId, FunctionType.Context, true) = .{},345 function_types: std.ArrayHashMapUnmanaged(FunctionType, ResultId, FunctionType.Context, true) = .{},
346 /// Maps functions to new information required for creating the module346 /// Maps functions to new information required for creating the module
347 function_new_info: std.AutoArrayHashMapUnmanaged(ResultId, FunctionNewInfo) = .{},347 function_new_info: std.AutoArrayHashMapUnmanaged(ResultId, FunctionNewInfo) = .{},
348 /// Offset of the functions section in the new binary.
349 new_functions_section: ?usize,
348350
349 fn init(arena: Allocator, binary: BinaryModule, info: ModuleInfo) !ModuleBuilder {351 fn init(arena: Allocator, binary: BinaryModule, info: ModuleInfo) !ModuleBuilder {
350 var section = Section{};
351
352 try section.instructions.appendSlice(arena, &.{
353 spec.magic_number,
354 @bitCast(binary.version),
355 spec.zig_generator_id,
356 0, // Filled in in finalize()
357 0, // Schema (reserved)
358 });
359
360 var self = ModuleBuilder{352 var self = ModuleBuilder{
361 .arena = arena,353 .arena = arena,
362 .section = section,354 .section = .{},
363 .id_bound = binary.id_bound,355 .id_bound = binary.id_bound,
364 .entry_point_new_id_base = undefined,356 .entry_point_new_id_base = undefined,
357 .new_functions_section = null,
365 };358 };
366 self.entry_point_new_id_base = @intFromEnum(self.allocIds(@intCast(info.entry_points.count())));359 self.entry_point_new_id_base = @intFromEnum(self.allocIds(@intCast(info.entry_points.count())));
367 return self;360 return self;
...@@ -376,9 +369,12 @@ const ModuleBuilder = struct {...@@ -376,9 +369,12 @@ const ModuleBuilder = struct {
376 return @enumFromInt(self.id_bound);369 return @enumFromInt(self.id_bound);
377 }370 }
378371
379 fn finalize(self: *ModuleBuilder, a: Allocator) ![]Word {372 fn finalize(self: *ModuleBuilder, a: Allocator, binary: *BinaryModule) !void {
380 self.section.instructions.items[3] = self.id_bound;373 binary.id_bound = self.id_bound;
381 return try a.dupe(Word, self.section.instructions.items);374 binary.instructions = try a.dupe(Word, self.section.instructions.items);
375 // Nothing is removed in this pass so we don't need to change any of the maps,
376 // just make sure the section is updated.
377 binary.sections.functions = self.new_functions_section orelse binary.instructions.len;
382 }378 }
383379
384 /// Process everything from `binary` up to the first function and emit it into the builder.380 /// Process everything from `binary` up to the first function and emit it into the builder.
...@@ -386,16 +382,6 @@ const ModuleBuilder = struct {...@@ -386,16 +382,6 @@ const ModuleBuilder = struct {
386 var it = binary.iterateInstructions();382 var it = binary.iterateInstructions();
387 while (it.next()) |inst| {383 while (it.next()) |inst| {
388 switch (inst.opcode) {384 switch (inst.opcode) {
389 // TODO: We should remove this instruction using something that eliminates unreferenced instructions.
390 // For now, this is the only place where the .zig instruction set is being referenced, so its safe
391 // to remove it here.
392 .OpExtInstImport => {
393 const set_id: ResultId = @enumFromInt(inst.operands[0]);
394 const set = binary.ext_inst_map.get(set_id).?;
395 if (set == .zig) {
396 continue;
397 }
398 },
399 .OpExtInst => {385 .OpExtInst => {
400 const set_id: ResultId = @enumFromInt(inst.operands[2]);386 const set_id: ResultId = @enumFromInt(inst.operands[2]);
401 const set_inst = inst.operands[3];387 const set_inst = inst.operands[3];
...@@ -499,6 +485,7 @@ const ModuleBuilder = struct {...@@ -499,6 +485,7 @@ const ModuleBuilder = struct {
499485
500 var maybe_current_function: ?ResultId = null;486 var maybe_current_function: ?ResultId = null;
501 var it = binary.iterateInstructionsFrom(binary.sections.functions);487 var it = binary.iterateInstructionsFrom(binary.sections.functions);
488 self.new_functions_section = self.section.instructions.items.len;
502 while (it.next()) |inst| {489 while (it.next()) |inst| {
503 result_id_offsets.items.len = 0;490 result_id_offsets.items.len = 0;
504 try parser.parseInstructionResultIds(binary, inst, &result_id_offsets);491 try parser.parseInstructionResultIds(binary, inst, &result_id_offsets);
...@@ -695,20 +682,19 @@ const ModuleBuilder = struct {...@@ -695,20 +682,19 @@ const ModuleBuilder = struct {
695 }682 }
696};683};
697684
698pub fn run(parser: *BinaryModule.Parser, binary: BinaryModule) ![]Word {685pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
699 var arena = std.heap.ArenaAllocator.init(parser.a);686 var arena = std.heap.ArenaAllocator.init(parser.a);
700 defer arena.deinit();687 defer arena.deinit();
701 const a = arena.allocator();688 const a = arena.allocator();
702689
703 var info = try ModuleInfo.parse(a, parser, binary);690 var info = try ModuleInfo.parse(a, parser, binary.*);
704 try info.resolve(a);691 try info.resolve(a);
705692
706 var builder = try ModuleBuilder.init(a, binary, info);693 var builder = try ModuleBuilder.init(a, binary.*, info);
707 try builder.deriveNewFnInfo(info);694 try builder.deriveNewFnInfo(info);
708 try builder.processPreamble(binary, info);695 try builder.processPreamble(binary.*, info);
709 try builder.emitFunctionTypes(info);696 try builder.emitFunctionTypes(info);
710 try builder.rewriteFunctions(parser, binary, info);697 try builder.rewriteFunctions(parser, binary.*, info);
711 try builder.emitNewEntryPoints(info);698 try builder.emitNewEntryPoints(info);
712699 try builder.finalize(parser.a, binary);
713 return builder.finalize(parser.a);
714}700}
src/link/SpirV/prune_unused.zig created+354
...@@ -0,0 +1,354 @@
1//! This pass is used to simple pruning of unused things:
2//! - Instructions at global scope
3//! - Functions
4//! Debug info and nonsemantic instructions are not handled;
5//! this pass is mainly intended for cleaning up left over
6//! stuff from codegen and other passes that is generated
7//! but not actually used.
8
9const std = @import("std");
10const Allocator = std.mem.Allocator;
11const assert = std.debug.assert;
12const log = std.log.scoped(.spirv_link);
13
14const BinaryModule = @import("BinaryModule.zig");
15const Section = @import("../../codegen/spirv/Section.zig");
16const spec = @import("../../codegen/spirv/spec.zig");
17const Opcode = spec.Opcode;
18const ResultId = spec.IdResult;
19const Word = spec.Word;
20
21/// Return whether a particular opcode's instruction can be pruned.
22/// These are idempotent instructions at globals scope and instructions
23/// within functions that do not have any side effects.
24/// The opcodes that return true here do not necessarily need to
25/// have an .IdResult. If they don't, then they are regarded
26/// as 'decoration'-style instructions that don't keep their
27/// operands alive, but will be emitted if they are.
28fn canPrune(op: Opcode) bool {
29 // This list should be as worked out as possible, but just
30 // getting common instructions is a good effort/effect ratio.
31 // When adding items to this list, also check whether the
32 // instruction requires any special control flow rules (like
33 // with labels and control flow and stuff) and whether the
34 // instruction has any non-trivial side effects (like OpLoad
35 // with the Volatile memory semantics).
36 return switch (op.class()) {
37 .TypeDeclaration,
38 .Conversion,
39 .Arithmetic,
40 .RelationalAndLogical,
41 .Bit,
42 => true,
43 else => switch (op) {
44 .OpFunction,
45 .OpUndef,
46 .OpString,
47 .OpName,
48 .OpMemberName,
49 // Prune OpConstant* instructions but
50 // retain OpSpecConstant declaration instructions
51 .OpConstantTrue,
52 .OpConstantFalse,
53 .OpConstant,
54 .OpConstantComposite,
55 .OpConstantSampler,
56 .OpConstantNull,
57 .OpSpecConstantOp,
58 // Prune ext inst import instructions, but not
59 // ext inst instructions themselves, because
60 // we don't know if they might have side effects.
61 .OpExtInstImport,
62 => true,
63 else => false,
64 },
65 };
66}
67
68const ModuleInfo = struct {
69 const Fn = struct {
70 /// The index of the first callee in `callee_store`.
71 first_callee: usize,
72 };
73
74 /// Maps function result-id -> Fn information structure.
75 functions: std.AutoArrayHashMapUnmanaged(ResultId, Fn),
76 /// For each function, a list of function result-ids that it calls.
77 callee_store: []const ResultId,
78 /// For each instruction, the offset at which it appears in the source module.
79 result_id_to_code_offset: std.AutoArrayHashMapUnmanaged(ResultId, usize),
80
81 /// Fetch the list of callees per function. Guaranteed to contain only unique IDs.
82 fn callees(self: ModuleInfo, fn_id: ResultId) []const ResultId {
83 const fn_index = self.functions.getIndex(fn_id).?;
84 const values = self.functions.values();
85 const first_callee = values[fn_index].first_callee;
86 if (fn_index == values.len - 1) {
87 return self.callee_store[first_callee..];
88 } else {
89 const next_first_callee = values[fn_index + 1].first_callee;
90 return self.callee_store[first_callee..next_first_callee];
91 }
92 }
93
94 /// Extract the information required to run this pass from the binary.
95 // TODO: Should the contents of this function be merged with that of lower_invocation_globals.zig?
96 // Many of the contents are the same...
97 fn parse(
98 arena: Allocator,
99 parser: *BinaryModule.Parser,
100 binary: BinaryModule,
101 ) !ModuleInfo {
102 var functions = std.AutoArrayHashMap(ResultId, Fn).init(arena);
103 var calls = std.AutoArrayHashMap(ResultId, void).init(arena);
104 var callee_store = std.ArrayList(ResultId).init(arena);
105 var result_id_to_code_offset = std.AutoArrayHashMap(ResultId, usize).init(arena);
106 var maybe_current_function: ?ResultId = null;
107 var it = binary.iterateInstructions();
108 while (it.next()) |inst| {
109 const inst_spec = parser.getInstSpec(inst.opcode).?;
110
111 // Result-id can only be the first or second operand
112 const maybe_result_id: ?ResultId = for (0..2) |i| {
113 if (inst_spec.operands.len > i and inst_spec.operands[i].kind == .IdResult) {
114 break @enumFromInt(inst.operands[i]);
115 }
116 } else null;
117
118 // Only add result-ids of functions and anything outside a function.
119 // Result-ids declared inside functions cannot be reached outside anyway,
120 // and we don't care about the internals of functions anyway.
121 // Note that in the case of OpFunction, `maybe_current_function` is
122 // also `null`, because it is set below.
123 if (maybe_result_id) |result_id| {
124 try result_id_to_code_offset.put(result_id, inst.offset);
125 }
126
127 switch (inst.opcode) {
128 .OpFunction => {
129 if (maybe_current_function) |current_function| {
130 log.err("OpFunction {} does not have an OpFunctionEnd", .{current_function});
131 return error.InvalidPhysicalFormat;
132 }
133
134 maybe_current_function = @enumFromInt(inst.operands[1]);
135 },
136 .OpFunctionCall => {
137 const callee: ResultId = @enumFromInt(inst.operands[2]);
138 try calls.put(callee, {});
139 },
140 .OpFunctionEnd => {
141 const current_function = maybe_current_function orelse {
142 log.err("encountered OpFunctionEnd without corresponding OpFunction", .{});
143 return error.InvalidPhysicalFormat;
144 };
145 const entry = try functions.getOrPut(current_function);
146 if (entry.found_existing) {
147 log.err("Function {} has duplicate definition", .{current_function});
148 return error.DuplicateId;
149 }
150
151 const first_callee = callee_store.items.len;
152 try callee_store.appendSlice(calls.keys());
153
154 entry.value_ptr.* = .{
155 .first_callee = first_callee,
156 };
157 maybe_current_function = null;
158 calls.clearRetainingCapacity();
159 },
160 else => {},
161 }
162 }
163
164 if (maybe_current_function) |current_function| {
165 log.err("OpFunction {} does not have an OpFunctionEnd", .{current_function});
166 return error.InvalidPhysicalFormat;
167 }
168
169 return ModuleInfo{
170 .functions = functions.unmanaged,
171 .callee_store = callee_store.items,
172 .result_id_to_code_offset = result_id_to_code_offset.unmanaged,
173 };
174 }
175};
176
177const AliveMarker = struct {
178 parser: *BinaryModule.Parser,
179 binary: BinaryModule,
180 info: ModuleInfo,
181 result_id_offsets: std.ArrayList(u16),
182 alive: std.DynamicBitSetUnmanaged,
183
184 fn markAlive(self: *AliveMarker, result_id: ResultId) BinaryModule.ParseError!void {
185 const index = self.info.result_id_to_code_offset.getIndex(result_id) orelse {
186 log.err("undefined result-id {}", .{result_id});
187 return error.InvalidId;
188 };
189
190 if (self.alive.isSet(index)) {
191 return;
192 }
193 self.alive.set(index);
194
195 const offset = self.info.result_id_to_code_offset.values()[index];
196 const inst = self.binary.instructionAt(offset);
197
198 if (inst.opcode == .OpFunction) {
199 try self.markFunctionAlive(inst);
200 } else {
201 try self.markInstructionAlive(inst);
202 }
203 }
204
205 fn markFunctionAlive(
206 self: *AliveMarker,
207 func_inst: BinaryModule.Instruction,
208 ) !void {
209 // Go through the instruction and mark the
210 // operands of each instruction alive.
211 var it = self.binary.iterateInstructionsFrom(func_inst.offset);
212 try self.markInstructionAlive(it.next().?);
213 while (it.next()) |inst| {
214 if (inst.opcode == .OpFunctionEnd) {
215 break;
216 }
217
218 if (!canPrune(inst.opcode)) {
219 try self.markInstructionAlive(inst);
220 }
221 }
222 }
223
224 fn markInstructionAlive(
225 self: *AliveMarker,
226 inst: BinaryModule.Instruction,
227 ) !void {
228 const start_offset = self.result_id_offsets.items.len;
229 try self.parser.parseInstructionResultIds(self.binary, inst, &self.result_id_offsets);
230 const end_offset = self.result_id_offsets.items.len;
231
232 // Recursive calls to markInstructionAlive() might change the pointer in self.result_id_offsets,
233 // so we need to iterate it manually.
234 var i = start_offset;
235 while (i < end_offset) : (i += 1) {
236 const offset = self.result_id_offsets.items[i];
237 try self.markAlive(@enumFromInt(inst.operands[offset]));
238 }
239 }
240};
241
242fn removeIdsFromMap(a: Allocator, map: anytype, info: ModuleInfo, alive_marker: AliveMarker) !void {
243 var to_remove = std.ArrayList(ResultId).init(a);
244 var it = map.iterator();
245 while (it.next()) |entry| {
246 const id = entry.key_ptr.*;
247 const index = info.result_id_to_code_offset.getIndex(id).?;
248 if (!alive_marker.alive.isSet(index)) {
249 try to_remove.append(id);
250 }
251 }
252
253 for (to_remove.items) |id| {
254 assert(map.remove(id));
255 }
256}
257
258pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void {
259 var arena = std.heap.ArenaAllocator.init(parser.a);
260 defer arena.deinit();
261 const a = arena.allocator();
262
263 const info = try ModuleInfo.parse(a, parser, binary.*);
264
265 var alive_marker = AliveMarker{
266 .parser = parser,
267 .binary = binary.*,
268 .info = info,
269 .result_id_offsets = std.ArrayList(u16).init(a),
270 .alive = try std.DynamicBitSetUnmanaged.initEmpty(a, info.result_id_to_code_offset.count()),
271 };
272
273 // Mark initial stuff as slive
274 {
275 var it = binary.iterateInstructions();
276 while (it.next()) |inst| {
277 if (inst.opcode == .OpFunction) {
278 // No need to process further.
279 break;
280 } else if (!canPrune(inst.opcode)) {
281 try alive_marker.markInstructionAlive(inst);
282 }
283 }
284 }
285
286 var section = Section{};
287
288 var new_functions_section: ?usize = null;
289 var it = binary.iterateInstructions();
290 skip: while (it.next()) |inst| {
291 const inst_spec = parser.getInstSpec(inst.opcode).?;
292
293 reemit: {
294 if (!canPrune(inst.opcode)) {
295 break :reemit;
296 }
297
298 // Result-id can only be the first or second operand
299 const result_id: ResultId = for (0..2) |i| {
300 if (inst_spec.operands.len > i and inst_spec.operands[i].kind == .IdResult) {
301 break @enumFromInt(inst.operands[i]);
302 }
303 } else {
304 // Instruction can be pruned but doesn't have a result id.
305 // Check all operands to see if they are alive, and emit it only if so.
306 alive_marker.result_id_offsets.items.len = 0;
307 try parser.parseInstructionResultIds(binary.*, inst, &alive_marker.result_id_offsets);
308 for (alive_marker.result_id_offsets.items) |offset| {
309 const id: ResultId = @enumFromInt(inst.operands[offset]);
310 const index = info.result_id_to_code_offset.getIndex(id).?;
311
312 if (!alive_marker.alive.isSet(index)) {
313 continue :skip;
314 }
315 }
316
317 break :reemit;
318 };
319
320 const index = info.result_id_to_code_offset.getIndex(result_id).?;
321 if (alive_marker.alive.isSet(index)) {
322 break :reemit;
323 }
324
325 if (inst.opcode != .OpFunction) {
326 // Instruction can be pruned and its not alive, so skip it.
327 continue :skip;
328 }
329
330 // We're at the start of a function that can be pruned, so skip everything until
331 // we encounter an OpFunctionEnd.
332 while (it.next()) |body_inst| {
333 if (body_inst.opcode == .OpFunctionEnd)
334 break;
335 }
336
337 continue :skip;
338 }
339
340 if (inst.opcode == .OpFunction and new_functions_section == null) {
341 new_functions_section = section.instructions.items.len;
342 }
343
344 try section.emitRawInstruction(a, inst.opcode, inst.operands);
345 }
346
347 // This pass might have pruned ext inst imports or arith types, update
348 // those maps to main consistency.
349 try removeIdsFromMap(a, &binary.ext_inst_map, info, alive_marker);
350 try removeIdsFromMap(a, &binary.arith_type_width, info, alive_marker);
351
352 binary.instructions = try parser.a.dupe(Word, section.toWords());
353 binary.sections.functions = new_functions_section orelse binary.instructions.len;
354}
test/behavior/align.zig+1
...@@ -18,6 +18,7 @@ test "global variable alignment" {...@@ -18,6 +18,7 @@ test "global variable alignment" {
18test "large alignment of local constant" {18test "large alignment of local constant" {
19 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;19 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
20 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;20 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
21 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // flaky
2122
22 const x: f32 align(128) = 12.34;23 const x: f32 align(128) = 12.34;
23 try std.testing.expect(@intFromPtr(&x) % 128 == 0);24 try std.testing.expect(@intFromPtr(&x) % 128 == 0);