| author | |
| committer | |
| log | 8ed134243ac9b3d1286153f95495176875472669 |
| tree | 4a698f99f03aea95c42335ad44fa5687265a57d5 |
| parent | 9b18125562b2402cae8450253decd906f09e4dc6 |
| signature |
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 | 245 | const module = try spv.finalize(arena, target); |
| 246 | 246 | errdefer arena.free(module); |
| 247 | 247 | |
| 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 | 249 | error.OutOfMemory => return error.OutOfMemory, |
| 250 | 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 | 252 | return error.FlushFailure; |
| 253 | 253 | }, |
| 254 | 254 | }; |
| 255 | defer arena.free(new_module); | |
| 256 | 255 | |
| 257 | try self.base.file.?.writeAll(std.mem.sliceAsBytes(new_module)); | |
| 256 | try self.base.file.?.writeAll(std.mem.sliceAsBytes(linked_module)); | |
| 258 | 257 | } |
| 259 | 258 | |
| 260 | fn lowerInstanceGlobals(self: *SpirV, a: Allocator, module: []Word) ![]Word { | |
| 259 | fn linkModule(self: *SpirV, a: Allocator, module: []Word) ![]Word { | |
| 261 | 260 | _ = self; |
| 262 | 261 | |
| 262 | const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig"); | |
| 263 | const prune_unused = @import("SpirV/prune_unused.zig"); | |
| 264 | ||
| 263 | 265 | var parser = try BinaryModule.Parser.init(a); |
| 264 | 266 | defer parser.deinit(); |
| 265 | const binary = try parser.parse(module); | |
| 266 | const lower_invocation_globals = @import("SpirV/lower_invocation_globals.zig"); | |
| 267 | return try lower_invocation_globals.run(&parser, binary); | |
| 267 | var binary = try parser.parse(module); | |
| 268 | ||
| 269 | try lower_invocation_globals.run(&parser, &binary); | |
| 270 | try prune_unused.run(&parser, &binary); | |
| 271 | ||
| 272 | return binary.finalize(a); | |
| 268 | 273 | } |
| 269 | 274 | |
| 270 | 275 | fn 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 | 45 | } |
| 46 | 46 | |
| 47 | 47 | pub fn iterateInstructions(self: BinaryModule) Instruction.Iterator { |
| 48 | return Instruction.Iterator.init(self.instructions); | |
| 48 | return Instruction.Iterator.init(self.instructions, 0); | |
| 49 | 49 | } |
| 50 | 50 | |
| 51 | 51 | pub 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 | ||
| 55 | pub fn instructionAt(self: BinaryModule, offset: usize) Instruction { | |
| 56 | var it = self.iterateInstructionsFrom(offset); | |
| 57 | return it.next().?; | |
| 58 | } | |
| 59 | ||
| 60 | pub 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 | } |
| 54 | 73 | |
| 55 | 74 | /// Errors that can be raised when the module is not correct. |
| ... | ... | @@ -85,8 +104,8 @@ pub const Instruction = struct { |
| 85 | 104 | index: usize = 0, |
| 86 | 105 | offset: usize = 0, |
| 87 | 106 | |
| 88 | pub fn init(words: []const Word) Iterator { | |
| 89 | return .{ .words = words }; | |
| 107 | pub fn init(words: []const Word, start_offset: usize) Iterator { | |
| 108 | return .{ .words = words, .offset = start_offset }; | |
| 90 | 109 | } |
| 91 | 110 | |
| 92 | 111 | pub fn next(self: *Iterator) ?Instruction { |
| ... | ... | @@ -159,6 +178,11 @@ pub const Parser = struct { |
| 159 | 178 | return (@as(u32, @intFromEnum(set)) << 16) | opcode; |
| 160 | 179 | } |
| 161 | 180 | |
| 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 | 186 | pub fn parse(self: *Parser, module: []const u32) ParseError!BinaryModule { |
| 163 | 187 | if (module[0] != spec.magic_number) { |
| 164 | 188 | return error.InvalidMagic; |
| ... | ... | @@ -195,13 +219,12 @@ pub const Parser = struct { |
| 195 | 219 | // We can't really efficiently use non-exhaustive enums here, because we would |
| 196 | 220 | // need to manually write out all valid cases. Since we have this map anyway, just |
| 197 | 221 | // use that. |
| 198 | const opcode_num: u16 = @truncate(binary.instructions[offset]); | |
| 199 | const index = self.opcode_table.get(mapSetAndOpcode(.core, opcode_num)) orelse { | |
| 200 | log.err("invalid opcode for core set: {}", .{opcode_num}); | |
| 222 | const opcode: Opcode = @enumFromInt(@as(u16, @truncate(binary.instructions[offset]))); | |
| 223 | const inst_spec = self.getInstSpec(opcode) orelse { | |
| 224 | log.err("invalid opcode for core set: {}", .{@intFromEnum(opcode)}); | |
| 201 | 225 | return error.InvalidOpcode; |
| 202 | 226 | }; |
| 203 | 227 | |
| 204 | const opcode: Opcode = @enumFromInt(opcode_num); | |
| 205 | 228 | const operands = binary.instructions[offset..][1..len]; |
| 206 | 229 | switch (opcode) { |
| 207 | 230 | .OpExtInstImport => { |
| ... | ... | @@ -226,11 +249,10 @@ pub const Parser = struct { |
| 226 | 249 | |
| 227 | 250 | // OpSwitch takes a value as argument, not an OpType... hence we need to populate arith_type_width |
| 228 | 251 | // with ALL operations that return an int or float. |
| 229 | const proper_operands = InstructionSet.core.instructions()[index].operands; | |
| 230 | ||
| 231 | if (proper_operands.len >= 2 and | |
| 232 | proper_operands[0].kind == .IdResultType and | |
| 233 | proper_operands[1].kind == .IdResult) | |
| 252 | const spec_operands = inst_spec.operands; | |
| 253 | if (spec_operands.len >= 2 and | |
| 254 | spec_operands[0].kind == .IdResultType and | |
| 255 | spec_operands[1].kind == .IdResult) | |
| 234 | 256 | { |
| 235 | 257 | if (operands.len < 2) return error.InvalidOperands; |
| 236 | 258 | if (binary.arith_type_width.get(@enumFromInt(operands[0]))) |width| { |
| ... | ... | @@ -283,6 +305,7 @@ pub const Parser = struct { |
| 283 | 305 | |
| 284 | 306 | if (offset + 1 >= inst.operands.len) return error.InvalidPhysicalFormat; |
| 285 | 307 | const set_id: ResultId = @enumFromInt(inst.operands[offset]); |
| 308 | try offsets.append(@intCast(offset)); | |
| 286 | 309 | const set = binary.ext_inst_map.get(set_id) orelse { |
| 287 | 310 | log.err("invalid instruction set {}", .{@intFromEnum(set_id)}); |
| 288 | 311 | return error.InvalidId; |
| ... | ... | @@ -375,8 +398,7 @@ pub const Parser = struct { |
| 375 | 398 | } |
| 376 | 399 | }, |
| 377 | 400 | .id => { |
| 378 | const this_offset = std.math.cast(u16, offset) orelse return error.InvalidPhysicalFormat; | |
| 379 | try offsets.append(this_offset); | |
| 401 | try offsets.append(@intCast(offset)); | |
| 380 | 402 | offset += 1; |
| 381 | 403 | }, |
| 382 | 404 | else => switch (kind) { |
| ... | ... | @@ -419,20 +441,16 @@ pub const Parser = struct { |
| 419 | 441 | 33...64 => 2, |
| 420 | 442 | else => unreachable, |
| 421 | 443 | }; |
| 422 | const this_offset = std.math.cast(u16, offset) orelse return error.InvalidPhysicalFormat; | |
| 423 | try offsets.append(this_offset); | |
| 444 | try offsets.append(@intCast(offset)); | |
| 424 | 445 | offset += 1; |
| 425 | 446 | }, |
| 426 | 447 | .PairIdRefLiteralInteger => { |
| 427 | const this_offset = std.math.cast(u16, offset) orelse return error.InvalidPhysicalFormat; | |
| 428 | try offsets.append(this_offset); | |
| 448 | try offsets.append(@intCast(offset)); | |
| 429 | 449 | offset += 2; |
| 430 | 450 | }, |
| 431 | 451 | .PairIdRefIdRef => { |
| 432 | const a = std.math.cast(u16, offset) orelse return error.InvalidPhysicalFormat; | |
| 433 | const b = std.math.cast(u16, offset + 1) orelse return error.InvalidPhysicalFormat; | |
| 434 | try offsets.append(a); | |
| 435 | try offsets.append(b); | |
| 452 | try offsets.append(@intCast(offset)); | |
| 453 | try offsets.append(@intCast(offset + 1)); | |
| 436 | 454 | offset += 2; |
| 437 | 455 | }, |
| 438 | 456 | else => unreachable, |
src/link/SpirV/lower_invocation_globals.zig+17-31| ... | ... | @@ -345,23 +345,16 @@ const ModuleBuilder = struct { |
| 345 | 345 | function_types: std.ArrayHashMapUnmanaged(FunctionType, ResultId, FunctionType.Context, true) = .{}, |
| 346 | 346 | /// Maps functions to new information required for creating the module |
| 347 | 347 | function_new_info: std.AutoArrayHashMapUnmanaged(ResultId, FunctionNewInfo) = .{}, |
| 348 | /// Offset of the functions section in the new binary. | |
| 349 | new_functions_section: ?usize, | |
| 348 | 350 | |
| 349 | 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 | 352 | var self = ModuleBuilder{ |
| 361 | 353 | .arena = arena, |
| 362 | .section = section, | |
| 354 | .section = .{}, | |
| 363 | 355 | .id_bound = binary.id_bound, |
| 364 | 356 | .entry_point_new_id_base = undefined, |
| 357 | .new_functions_section = null, | |
| 365 | 358 | }; |
| 366 | 359 | self.entry_point_new_id_base = @intFromEnum(self.allocIds(@intCast(info.entry_points.count()))); |
| 367 | 360 | return self; |
| ... | ... | @@ -376,9 +369,12 @@ const ModuleBuilder = struct { |
| 376 | 369 | return @enumFromInt(self.id_bound); |
| 377 | 370 | } |
| 378 | 371 | |
| 379 | fn finalize(self: *ModuleBuilder, a: Allocator) ![]Word { | |
| 380 | self.section.instructions.items[3] = self.id_bound; | |
| 381 | return try a.dupe(Word, self.section.instructions.items); | |
| 372 | fn finalize(self: *ModuleBuilder, a: Allocator, binary: *BinaryModule) !void { | |
| 373 | binary.id_bound = self.id_bound; | |
| 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 | } |
| 383 | 379 | |
| 384 | 380 | /// Process everything from `binary` up to the first function and emit it into the builder. |
| ... | ... | @@ -386,16 +382,6 @@ const ModuleBuilder = struct { |
| 386 | 382 | var it = binary.iterateInstructions(); |
| 387 | 383 | while (it.next()) |inst| { |
| 388 | 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 | 385 | .OpExtInst => { |
| 400 | 386 | const set_id: ResultId = @enumFromInt(inst.operands[2]); |
| 401 | 387 | const set_inst = inst.operands[3]; |
| ... | ... | @@ -499,6 +485,7 @@ const ModuleBuilder = struct { |
| 499 | 485 | |
| 500 | 486 | var maybe_current_function: ?ResultId = null; |
| 501 | 487 | var it = binary.iterateInstructionsFrom(binary.sections.functions); |
| 488 | self.new_functions_section = self.section.instructions.items.len; | |
| 502 | 489 | while (it.next()) |inst| { |
| 503 | 490 | result_id_offsets.items.len = 0; |
| 504 | 491 | try parser.parseInstructionResultIds(binary, inst, &result_id_offsets); |
| ... | ... | @@ -695,20 +682,19 @@ const ModuleBuilder = struct { |
| 695 | 682 | } |
| 696 | 683 | }; |
| 697 | 684 | |
| 698 | pub fn run(parser: *BinaryModule.Parser, binary: BinaryModule) ![]Word { | |
| 685 | pub fn run(parser: *BinaryModule.Parser, binary: *BinaryModule) !void { | |
| 699 | 686 | var arena = std.heap.ArenaAllocator.init(parser.a); |
| 700 | 687 | defer arena.deinit(); |
| 701 | 688 | const a = arena.allocator(); |
| 702 | 689 | |
| 703 | var info = try ModuleInfo.parse(a, parser, binary); | |
| 690 | var info = try ModuleInfo.parse(a, parser, binary.*); | |
| 704 | 691 | try info.resolve(a); |
| 705 | 692 | |
| 706 | var builder = try ModuleBuilder.init(a, binary, info); | |
| 693 | var builder = try ModuleBuilder.init(a, binary.*, info); | |
| 707 | 694 | try builder.deriveNewFnInfo(info); |
| 708 | try builder.processPreamble(binary, info); | |
| 695 | try builder.processPreamble(binary.*, info); | |
| 709 | 696 | try builder.emitFunctionTypes(info); |
| 710 | try builder.rewriteFunctions(parser, binary, info); | |
| 697 | try builder.rewriteFunctions(parser, binary.*, info); | |
| 711 | 698 | try builder.emitNewEntryPoints(info); |
| 712 | ||
| 713 | return builder.finalize(parser.a); | |
| 699 | try builder.finalize(parser.a, binary); | |
| 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 | ||
| 9 | const std = @import("std"); | |
| 10 | const Allocator = std.mem.Allocator; | |
| 11 | const assert = std.debug.assert; | |
| 12 | const log = std.log.scoped(.spirv_link); | |
| 13 | ||
| 14 | const BinaryModule = @import("BinaryModule.zig"); | |
| 15 | const Section = @import("../../codegen/spirv/Section.zig"); | |
| 16 | const spec = @import("../../codegen/spirv/spec.zig"); | |
| 17 | const Opcode = spec.Opcode; | |
| 18 | const ResultId = spec.IdResult; | |
| 19 | const 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. | |
| 28 | fn 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 | ||
| 68 | const 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 | ||
| 177 | const 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 | ||
| 242 | fn 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 | ||
| 258 | pub 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 | 18 | test "large alignment of local constant" { |
| 19 | 19 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; |
| 20 | 20 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; |
| 21 | if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // flaky | |
| 21 | 22 | |
| 22 | 23 | const x: f32 align(128) = 12.34; |
| 23 | 24 | try std.testing.expect(@intFromPtr(&x) % 128 == 0); |