diff --git a/CMakeLists.txt b/CMakeLists.txt index 835f3c4d62ca1f3ab30d138e306a17f6a495bb04..372338bb7eb04bd1154f0c44c9f4bc3edbc897b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -753,6 +753,8 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/link.zig" "${CMAKE_SOURCE_DIR}/src/link/C.zig" "${CMAKE_SOURCE_DIR}/src/link/Coff.zig" + "${CMAKE_SOURCE_DIR}/src/link/Coff/Atom.zig" + "${CMAKE_SOURCE_DIR}/src/link/Coff/lld.zig" "${CMAKE_SOURCE_DIR}/src/link/Elf.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/Archive.zig" diff --git a/lib/std/coff.zig b/lib/std/coff.zig index f9de318e7a6932b005de7f78da244aac437d0ddf..1640d44fb5708967807fc1952ed45dbfcafad91c 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -303,6 +303,15 @@ pub const SectionHeader = extern struct { return std.math.powi(u16, 2, self.flags.ALIGN - 1) catch unreachable; } + pub fn setAlignment(self: *SectionHeader, new_alignment: u16) void { + assert(new_alignment > 0 and new_alignment <= 8192); + self.flags.ALIGN = std.math.log2(new_alignment); + } + + pub fn isCode(self: SectionHeader) bool { + return self.flags.CNT_CODE == 0b1; + } + pub fn isComdat(self: SectionHeader) bool { return self.flags.LNK_COMDAT == 0b1; } diff --git a/src/Module.zig b/src/Module.zig index a92849e127e4c6df05285b1ae890b90698e0b14d..706ab5ff17f979d4f52088c156fabaca9e826e99 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -5259,9 +5259,9 @@ pub fn clearDecl( // TODO instead of a union, put this memory trailing Decl objects, // and allow it to be variably sized. decl.link = switch (mod.comp.bin_file.tag) { - .coff => .{ .coff = link.File.Coff.TextBlock.empty }, + .coff => .{ .coff = link.File.Coff.Atom.empty }, .elf => .{ .elf = link.File.Elf.TextBlock.empty }, - .macho => .{ .macho = link.File.MachO.TextBlock.empty }, + .macho => .{ .macho = link.File.MachO.Atom.empty }, .plan9 => .{ .plan9 = link.File.Plan9.DeclBlock.empty }, .c => .{ .c = {} }, .wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty }, @@ -5680,9 +5680,9 @@ pub fn allocateNewDecl( .zir_decl_index = 0, .src_scope = src_scope, .link = switch (mod.comp.bin_file.tag) { - .coff => .{ .coff = link.File.Coff.TextBlock.empty }, + .coff => .{ .coff = link.File.Coff.Atom.empty }, .elf => .{ .elf = link.File.Elf.TextBlock.empty }, - .macho => .{ .macho = link.File.MachO.TextBlock.empty }, + .macho => .{ .macho = link.File.MachO.Atom.empty }, .plan9 => .{ .plan9 = link.File.Plan9.DeclBlock.empty }, .c => .{ .c = {} }, .wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty }, diff --git a/src/Sema.zig b/src/Sema.zig index f884684d73794b87424ff3158cf9f34dc07f52c5..a4e2106afb18415f01ef9de99dcdb4496f11f396 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -5076,7 +5076,7 @@ pub fn analyzeExport( }, .src = src, .link = switch (mod.comp.bin_file.tag) { - .coff => .{ .coff = {} }, + .coff => .{ .coff = .{} }, .elf => .{ .elf = .{} }, .macho => .{ .macho = .{} }, .plan9 => .{ .plan9 = null }, diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index d256f9a55856b24f42d3426121446ffa86f9d26d..98f778ef3505dcb548bb5dc695554663814f0005 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -3475,10 +3475,11 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| - coff_file.offset_table_virtual_address + fn_owner_decl.link.coff.offset_table_index * ptr_bytes - else - unreachable; + } else if (self.bin_file.cast(link.File.Coff)) |coff_file| blk: { + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = fn_owner_decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + break :blk got_sym.value; + } else unreachable; try self.genSetReg(Type.initTag(.usize), .x30, .{ .memory = got_addr }); @@ -5110,8 +5111,9 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne assert(decl.link.macho.sym_index != 0); return MCValue{ .got_load = decl.link.macho.sym_index }; } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes; - return MCValue{ .memory = got_addr }; + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + return MCValue{ .memory = got_sym.value }; } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index e8f0507614bbd2805aeb6539ac2c07960ed840d4..0f796c530dd545c3f8e9d1d7b15760dfc7e6694e 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -3709,10 +3709,11 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| - coff_file.offset_table_virtual_address + fn_owner_decl.link.coff.offset_table_index * ptr_bytes - else - unreachable; + } else if (self.bin_file.cast(link.File.Coff)) |coff_file| blk: { + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = fn_owner_decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + break :blk @intCast(u32, got_sym.value); + } else unreachable; try self.genSetReg(Type.initTag(.usize), .lr, .{ .memory = got_addr }); } else if (func_value.castTag(.extern_fn)) |_| { @@ -5549,8 +5550,9 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne } else if (self.bin_file.cast(link.File.MachO)) |_| { unreachable; // unsupported architecture for MachO } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes; - return MCValue{ .memory = got_addr }; + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + return MCValue{ .memory = got_sym.value }; } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 06adcff6d45f1bed059a932a5066bcbe3baf62aa..2bb68086d872e86000234b70571127305b56d06f 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -1755,10 +1755,11 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| - coff_file.offset_table_virtual_address + fn_owner_decl.link.coff.offset_table_index * ptr_bytes - else - unreachable; + } else if (self.bin_file.cast(link.File.Coff)) |coff_file| blk: { + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = fn_owner_decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + break :blk got_sym.value; + } else unreachable; try self.genSetReg(Type.initTag(.usize), .ra, .{ .memory = got_addr }); _ = try self.addInst(.{ @@ -2592,8 +2593,9 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne // index to the GOT target symbol index. return MCValue{ .memory = decl.link.macho.sym_index }; } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes; - return MCValue{ .memory = got_addr }; + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + return MCValue{ .memory = got_sym.value }; } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 106d2feec02bf213ed1ef3ea3528f097c54baf92..d7263523c27f5eeb5eac3608f6b411d803c78767 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -3971,10 +3971,11 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes); - } else if (self.bin_file.cast(link.File.Coff)) |coff_file| - @intCast(u32, coff_file.offset_table_virtual_address + fn_owner_decl.link.coff.offset_table_index * ptr_bytes) - else - unreachable; + } else if (self.bin_file.cast(link.File.Coff)) |coff_file| blk: { + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = fn_owner_decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + break :blk got_sym.value; + } else unreachable; _ = try self.addInst(.{ .tag = .call, .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }), @@ -6847,8 +6848,9 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne assert(decl.link.macho.sym_index != 0); return MCValue{ .got_load = decl.link.macho.sym_index }; } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes; - return MCValue{ .memory = got_addr }; + const got_atom = coff_file.getGotAtomForSymbol(.{ .sym_index = decl.link.coff.sym_index, .file = null }).?; + const got_sym = coff_file.getSymbol(got_atom.getSymbolWithLoc()); + return MCValue{ .memory = got_sym.value }; } else if (self.bin_file.cast(link.File.Plan9)) |p9| { try p9.seeDecl(decl_index); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; diff --git a/src/link.zig b/src/link.zig index 60c6b24640e0f592cc441a7870f263535d56c338..421188bd4756de5d8f5e312103074268ca3bebbb 100644 --- a/src/link.zig +++ b/src/link.zig @@ -245,8 +245,8 @@ pub const File = struct { pub const LinkBlock = union { elf: Elf.TextBlock, - coff: Coff.TextBlock, - macho: MachO.TextBlock, + coff: Coff.Atom, + macho: MachO.Atom, plan9: Plan9.DeclBlock, c: void, wasm: Wasm.DeclBlock, @@ -267,7 +267,7 @@ pub const File = struct { pub const Export = union { elf: Elf.Export, - coff: void, + coff: Coff.Export, macho: MachO.Export, plan9: Plan9.Export, c: void, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index adfb843e048da0eb666a93c6d10f91b6f27862d8..67172abcd485ddd6f2db92f6875671cd902b381a 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1,39 +1,30 @@ const Coff = @This(); const std = @import("std"); +const build_options = @import("build_options"); const builtin = @import("builtin"); -const log = std.log.scoped(.link); -const Allocator = std.mem.Allocator; const assert = std.debug.assert; -const fs = std.fs; -const allocPrint = std.fmt.allocPrint; +const coff = std.coff; +const log = std.log.scoped(.link); +const math = std.math; const mem = std.mem; -const lldMain = @import("../main.zig").lldMain; -const trace = @import("../tracy.zig").trace; -const Module = @import("../Module.zig"); -const Compilation = @import("../Compilation.zig"); +const Allocator = std.mem.Allocator; + const codegen = @import("../codegen.zig"); const link = @import("../link.zig"); -const build_options = @import("build_options"); -const Cache = @import("../Cache.zig"); -const mingw = @import("../mingw.zig"); +const lld = @import("Coff/lld.zig"); +const trace = @import("../tracy.zig").trace; + const Air = @import("../Air.zig"); +pub const Atom = @import("Coff/Atom.zig"); +const Compilation = @import("../Compilation.zig"); const Liveness = @import("../Liveness.zig"); const LlvmObject = @import("../codegen/llvm.zig").Object; +const Module = @import("../Module.zig"); +const StringTable = @import("strtab.zig").StringTable; const TypedValue = @import("../TypedValue.zig"); -const allocation_padding = 4 / 3; -const minimum_text_block_size = 64 * allocation_padding; - -const section_alignment = 4096; -const file_alignment = 512; -const default_image_base = 0x400_000; -const section_table_size = 2 * 40; -comptime { - assert(mem.isAligned(default_image_base, section_alignment)); -} - pub const base_tag: link.File.Tag = .coff; const msdos_stub = @embedFile("msdos-stub.bin"); @@ -42,91 +33,94 @@ const msdos_stub = @embedFile("msdos-stub.bin"); llvm_object: ?*LlvmObject = null, base: link.File, -ptr_width: PtrWidth, error_flags: link.File.ErrorFlags = .{}, -text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = .{}, -last_text_block: ?*TextBlock = null, +ptr_width: PtrWidth, -/// Section table file pointer. -section_table_offset: u32 = 0, -/// Section data file pointer. -section_data_offset: u32 = 0, -/// Optional header file pointer. -optional_header_offset: u32 = 0, +sections: std.MultiArrayList(Section) = .{}, -/// Absolute virtual address of the offset table when the executable is loaded in memory. -offset_table_virtual_address: u32 = 0, -/// Current size of the offset table on disk, must be a multiple of `file_alignment` -offset_table_size: u32 = 0, -/// Contains absolute virtual addresses -offset_table: std.ArrayListUnmanaged(u64) = .{}, -/// Free list of offset table indices -offset_table_free_list: std.ArrayListUnmanaged(u32) = .{}, +text_section_index: ?u16 = null, +got_section_index: ?u16 = null, + +locals: std.ArrayListUnmanaged(coff.Symbol) = .{}, +globals: std.StringArrayHashMapUnmanaged(SymbolWithLoc) = .{}, + +locals_free_list: std.ArrayListUnmanaged(u32) = .{}, + +strtab: StringTable(.strtab) = .{}, + +got_entries: std.AutoArrayHashMapUnmanaged(SymbolWithLoc, u32) = .{}, +got_entries_free_list: std.ArrayListUnmanaged(u32) = .{}, /// Virtual address of the entry point procedure relative to image base. -entry_addr: ?u32 = null, - -/// Absolute virtual address of the text section when the executable is loaded in memory. -text_section_virtual_address: u32 = 0, -/// Current size of the `.text` section on disk, must be a multiple of `file_alignment` -text_section_size: u32 = 0, - -offset_table_size_dirty: bool = false, -text_section_size_dirty: bool = false, -/// This flag is set when the virtual size of the whole image file when loaded in memory has changed -/// and needs to be updated in the optional header. -size_of_image_dirty: bool = false, +entry_addr: ?u64 = null, + +/// Table of Decls that are currently alive. +/// We store them here so that we can properly dispose of any allocated +/// memory within the atom in the incremental linker. +/// TODO consolidate this. +decls: std.AutoHashMapUnmanaged(Module.Decl.Index, ?u16) = .{}, + +/// List of atoms that are either synthetic or map directly to the Zig source program. +managed_atoms: std.ArrayListUnmanaged(*Atom) = .{}, + +/// Table of atoms indexed by the symbol index. +atom_by_index_table: std.AutoHashMapUnmanaged(u32, *Atom) = .{}, + +const page_size: u16 = 0x1000; + +const Section = struct { + header: coff.SectionHeader, + + last_atom: ?*Atom = null, + + /// A list of atoms that have surplus capacity. This list can have false + /// positives, as functions grow and shrink over time, only sometimes being added + /// or removed from the freelist. + /// + /// An atom has surplus capacity when its overcapacity value is greater than + /// padToIdeal(minimum_atom_size). That is, when it has so + /// much extra capacity, that we could fit a small new symbol in it, itself with + /// ideal_capacity or more. + /// + /// Ideal capacity is defined by size + (size / ideal_factor). + /// + /// Overcapacity is measured by actual_capacity - ideal_capacity. Note that + /// overcapacity can be negative. A simple way to have negative overcapacity is to + /// allocate a fresh atom, which will have ideal capacity, and then grow it + /// by 1 byte. It will then have -1 overcapacity. + free_list: std.ArrayListUnmanaged(*Atom) = .{}, +}; pub const PtrWidth = enum { p32, p64 }; - -pub const TextBlock = struct { - /// Offset of the code relative to the start of the text section - text_offset: u32, - /// Used size of the text block - size: u32, - /// This field is undefined for symbols with size = 0. - offset_table_index: u32, - /// Points to the previous and next neighbors, based on the `text_offset`. - /// This can be used to find, for example, the capacity of this `TextBlock`. - prev: ?*TextBlock, - next: ?*TextBlock, - - pub const empty = TextBlock{ - .text_offset = 0, - .size = 0, - .offset_table_index = undefined, - .prev = null, - .next = null, - }; - - /// Returns how much room there is to grow in virtual address space. - fn capacity(self: TextBlock) u64 { - if (self.next) |next| { - return next.text_offset - self.text_offset; - } - // This is the last block, the capacity is only limited by the address space. - return std.math.maxInt(u32) - self.text_offset; - } - - fn freeListEligible(self: TextBlock) bool { - // No need to keep a free list node for the last block. - const next = self.next orelse return false; - const cap = next.text_offset - self.text_offset; - const ideal_cap = self.size * allocation_padding; - if (cap <= ideal_cap) return false; - const surplus = cap - ideal_cap; - return surplus >= minimum_text_block_size; - } - - /// Absolute virtual address of the text block when the file is loaded in memory. - fn getVAddr(self: TextBlock, coff: Coff) u32 { - return coff.text_section_virtual_address + self.text_offset; - } -}; - pub const SrcFn = void; +pub const Export = struct { + sym_index: ?u32 = null, +}; + +pub const SymbolWithLoc = struct { + // Index into the respective symbol table. + sym_index: u32, + + // null means it's a synthetic global or Zig source. + file: ?u32 = null, +}; + +/// When allocating, the ideal_capacity is calculated by +/// actual_capacity + (actual_capacity / ideal_factor) +const ideal_factor = 3; + +/// In order for a slice of bytes to be considered eligible to keep metadata pointing at +/// it as a possible place to put new symbols, it must have enough room for this many bytes +/// (plus extra for reserved capacity). +const minimum_text_block_size = 64; +pub const min_text_capacity = padToIdeal(minimum_text_block_size); + +/// We commit 0x1000 = 4096 bytes of space to the headers. +/// This should be plenty for any potential future extensions. +const default_headerpad_size: u32 = 0x1000; + pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Coff { assert(options.target.ofmt == .coff); @@ -144,25 +138,18 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option }); self.base.file = file; - const coff_file_header_offset: u32 = if (options.output_mode == .Exe) msdos_stub.len + 4 else 0; - const default_offset_table_size = file_alignment; - const data_directory_count = 0; - const default_size_of_code = 0; - const optional_header_size = switch (options.output_mode) { - .Exe => data_directory_count * 8 + switch (self.ptr_width) { - .p32 => @as(u16, 96), - .p64 => 112, - }, - else => 0, - }; - const section_table_offset = coff_file_header_offset + 20 + optional_header_size; - self.section_data_offset = mem.alignForwardGeneric(u32, section_table_offset + section_table_size, file_alignment); - const section_data_relative_virtual_address = mem.alignForwardGeneric(u32, section_table_offset + section_table_size, section_alignment); - self.offset_table_virtual_address = default_image_base + section_data_relative_virtual_address; - self.offset_table_size = default_offset_table_size; - self.section_table_offset = section_table_offset; - self.text_section_virtual_address = default_image_base + section_data_relative_virtual_address + section_alignment; - self.text_section_size = default_size_of_code; + // Index 0 is always a null symbol. + try self.locals.append(allocator, .{ + .name = [_]u8{0} ** 8, + .value = 0, + .section_number = @intToEnum(coff.SectionNumber, 0), + .@"type" = .{ .base_type = .NULL, .complex_type = .NULL }, + .storage_class = .NULL, + .number_of_aux_symbols = 0, + }); + try self.strtab.buffer.append(allocator, 0); + + try self.populateMissingMetadata(); return self; } @@ -193,245 +180,259 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff { return self; } +pub fn deinit(self: *Coff) void { + const gpa = self.base.allocator; + + if (build_options.have_llvm) { + if (self.llvm_object) |llvm_object| llvm_object.destroy(gpa); + } + + for (self.sections.items(.free_list)) |*free_list| { + free_list.deinit(gpa); + } + self.sections.deinit(gpa); + + for (self.managed_atoms.items) |atom| { + gpa.destroy(atom); + } + self.managed_atoms.deinit(gpa); + + self.locals.deinit(gpa); + self.globals.deinit(gpa); + self.locals_free_list.deinit(gpa); + self.strtab.deinit(gpa); + self.got_entries.deinit(gpa); + self.got_entries_free_list.deinit(gpa); + self.decls.deinit(gpa); + self.atom_by_index_table.deinit(gpa); +} + +fn populateMissingMetadata(self: *Coff) !void { + _ = self; + @panic("TODO populateMissingMetadata"); +} + pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void { if (self.llvm_object) |_| return; - - try self.offset_table.ensureUnusedCapacity(self.base.allocator, 1); - const decl = self.base.options.module.?.declPtr(decl_index); - if (self.offset_table_free_list.popOrNull()) |i| { - decl.link.coff.offset_table_index = i; - } else { - decl.link.coff.offset_table_index = @intCast(u32, self.offset_table.items.len); - _ = self.offset_table.addOneAssumeCapacity(); - - const entry_size = self.base.options.target.cpu.arch.ptrBitWidth() / 8; - if (self.offset_table.items.len > self.offset_table_size / entry_size) { - self.offset_table_size_dirty = true; - } - } - - self.offset_table.items[decl.link.coff.offset_table_index] = 0; + if (decl.link.coff.sym_index != 0) return; + decl.link.coff.sym_index = try self.allocateSymbol(); + const gpa = self.base.allocator; + try self.atom_by_index_table.putNoClobber(gpa, decl.link.coff.sym_index, &decl.link.coff); + try self.decls.putNoClobber(gpa, decl_index, null); } -fn allocateTextBlock(self: *Coff, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { - const new_block_min_capacity = new_block_size * allocation_padding; +fn allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u64, alignment: u64, sect_id: u16) !u64 { + const tracy = trace(@src()); + defer tracy.end(); - // We use these to indicate our intention to update metadata, placing the new block, + const header = &self.sections.items(.header)[sect_id]; + const free_list = &self.sections.items(.free_list)[sect_id]; + const maybe_last_atom = &self.sections.items(.last_atom)[sect_id]; + const new_atom_ideal_capacity = if (header.isCode()) padToIdeal(new_atom_size) else new_atom_size; + + // We use these to indicate our intention to update metadata, placing the new atom, // and possibly removing a free list node. // It would be simpler to do it inside the for loop below, but that would cause a // problem if an error was returned later in the function. So this action // is actually carried out at the end of the function, when errors are no longer possible. - var block_placement: ?*TextBlock = null; + var atom_placement: ?*Atom = null; var free_list_removal: ?usize = null; - const vaddr = blk: { + // First we look for an appropriately sized free list node. + // The list is unordered. We'll just take the first thing that works. + var vaddr = blk: { var i: usize = 0; - while (i < self.text_block_free_list.items.len) { - const free_block = self.text_block_free_list.items[i]; - - const next_block_text_offset = free_block.text_offset + free_block.capacity(); - const new_block_text_offset = mem.alignForwardGeneric(u64, free_block.getVAddr(self.*) + free_block.size, alignment) - self.text_section_virtual_address; - if (new_block_text_offset < next_block_text_offset and next_block_text_offset - new_block_text_offset >= new_block_min_capacity) { - block_placement = free_block; - - const remaining_capacity = next_block_text_offset - new_block_text_offset - new_block_min_capacity; - if (remaining_capacity < minimum_text_block_size) { - free_list_removal = i; - } - - break :blk new_block_text_offset + self.text_section_virtual_address; - } else { - if (!free_block.freeListEligible()) { - _ = self.text_block_free_list.swapRemove(i); + while (i < free_list.items.len) { + const big_atom = free_list.items[i]; + // We now have a pointer to a live atom that has too much capacity. + // Is it enough that we could fit this new atom? + const sym = big_atom.getSymbol(self); + const capacity = big_atom.capacity(self); + const ideal_capacity = if (header.isCode()) padToIdeal(capacity) else capacity; + const ideal_capacity_end_vaddr = math.add(u64, sym.n_value, ideal_capacity) catch ideal_capacity; + const capacity_end_vaddr = sym.n_value + capacity; + const new_start_vaddr_unaligned = capacity_end_vaddr - new_atom_ideal_capacity; + const new_start_vaddr = mem.alignBackwardGeneric(u64, new_start_vaddr_unaligned, alignment); + if (new_start_vaddr < ideal_capacity_end_vaddr) { + // Additional bookkeeping here to notice if this free list node + // should be deleted because the atom that it points to has grown to take up + // more of the extra capacity. + if (!big_atom.freeListEligible(self)) { + _ = free_list.swapRemove(i); } else { i += 1; } continue; } - } else if (self.last_text_block) |last| { - const new_block_vaddr = mem.alignForwardGeneric(u64, last.getVAddr(self.*) + last.size, alignment); - block_placement = last; - break :blk new_block_vaddr; + // At this point we know that we will place the new atom here. But the + // remaining question is whether there is still yet enough capacity left + // over for there to still be a free list node. + const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr; + const keep_free_list_node = remaining_capacity >= min_text_capacity; + + // Set up the metadata to be updated, after errors are no longer possible. + atom_placement = big_atom; + if (!keep_free_list_node) { + free_list_removal = i; + } + break :blk new_start_vaddr; + } else if (maybe_last_atom.*) |last| { + const last_symbol = last.getSymbol(self); + const ideal_capacity = if (header.isCode()) padToIdeal(last.size) else last.size; + const ideal_capacity_end_vaddr = last_symbol.n_value + ideal_capacity; + const new_start_vaddr = mem.alignForwardGeneric(u64, ideal_capacity_end_vaddr, alignment); + atom_placement = last; + break :blk new_start_vaddr; } else { - break :blk self.text_section_virtual_address; + break :blk mem.alignForwardGeneric(u64, header.addr, alignment); } }; - const expand_text_section = block_placement == null or block_placement.?.next == null; - if (expand_text_section) { - const needed_size = @intCast(u32, mem.alignForwardGeneric(u64, vaddr + new_block_size - self.text_section_virtual_address, file_alignment)); - if (needed_size > self.text_section_size) { - const current_text_section_virtual_size = mem.alignForwardGeneric(u32, self.text_section_size, section_alignment); - const new_text_section_virtual_size = mem.alignForwardGeneric(u32, needed_size, section_alignment); - if (current_text_section_virtual_size != new_text_section_virtual_size) { - self.size_of_image_dirty = true; - // Write new virtual size - var buf: [4]u8 = undefined; - mem.writeIntLittle(u32, &buf, new_text_section_virtual_size); - try self.base.file.?.pwriteAll(&buf, self.section_table_offset + 40 + 8); - } + const expand_section = atom_placement == null or atom_placement.?.next == null; + if (expand_section) { + @panic("TODO expand section in allocateAtom"); + } - self.text_section_size = needed_size; - self.text_section_size_dirty = true; - } - self.last_text_block = text_block; + if (header.getAlignment() < alignment) { + header.setAlignment(alignment); } - text_block.text_offset = @intCast(u32, vaddr - self.text_section_virtual_address); - text_block.size = @intCast(u32, new_block_size); + atom.size = new_atom_size; + atom.alignment = alignment; - // This function can also reallocate a text block. - // In this case we need to "unplug" it from its previous location before - // plugging it in to its new location. - if (text_block.prev) |prev| { - prev.next = text_block.next; + if (atom.prev) |prev| { + prev.next = atom.next; } - if (text_block.next) |next| { - next.prev = text_block.prev; + if (atom.next) |next| { + next.prev = atom.prev; } - if (block_placement) |big_block| { - text_block.prev = big_block; - text_block.next = big_block.next; - big_block.next = text_block; + if (atom_placement) |big_atom| { + atom.prev = big_atom; + atom.next = big_atom.next; + big_atom.next = atom; } else { - text_block.prev = null; - text_block.next = null; + atom.prev = null; + atom.next = null; } if (free_list_removal) |i| { - _ = self.text_block_free_list.swapRemove(i); + _ = free_list.swapRemove(i); } + return vaddr; } -fn growTextBlock(self: *Coff, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { - const block_vaddr = text_block.getVAddr(self.*); - const align_ok = mem.alignBackwardGeneric(u64, block_vaddr, alignment) == block_vaddr; - const need_realloc = !align_ok or new_block_size > text_block.capacity(); - if (!need_realloc) return @as(u64, block_vaddr); - return self.allocateTextBlock(text_block, new_block_size, alignment); +fn allocateSymbol(self: *Coff) !u32 { + const gpa = self.base.allocator; + try self.locals.ensureUnusedCapacity(gpa, 1); + + const index = blk: { + if (self.locals_free_list.popOrNull()) |index| { + log.debug(" (reusing symbol index {d})", .{index}); + break :blk index; + } else { + log.debug(" (allocating symbol index {d})", .{self.locals.items.len}); + const index = @intCast(u32, self.locals.items.len); + _ = self.locals.addOneAssumeCapacity(); + break :blk index; + } + }; + + self.locals.items[index] = .{ + .name = [_]u8{0} ** 8, + .value = 0, + .section_number = @intToEnum(coff.SectionNumber, 0), + .@"type" = .{ .base_type = .NULL, .complex_type = .NULL }, + .storage_class = .NULL, + .number_of_aux_symbols = 0, + }; + + return index; } -fn shrinkTextBlock(self: *Coff, text_block: *TextBlock, new_block_size: u64) void { - text_block.size = @intCast(u32, new_block_size); - if (text_block.capacity() - text_block.size >= minimum_text_block_size) { - self.text_block_free_list.append(self.base.allocator, text_block) catch {}; +pub fn allocateGotEntry(self: *Coff, target: SymbolWithLoc) !u32 { + const gpa = self.base.allocator; + try self.got_entries.ensureUnusedCapacity(gpa, 1); + if (self.got_entries_free_list.popOrNull()) |index| { + log.debug(" (reusing GOT entry index {d})", .{index}); + if (self.got_entries.getIndex(target)) |existing| { + assert(existing == index); + } + self.got_entries.keys()[index] = target; + return index; + } else { + log.debug(" (allocating GOT entry at index {d})", .{self.got_entries.keys().len}); + const index = @intCast(u32, self.got_entries.keys().len); + try self.got_entries.putAssumeCapacityNoClobber(target, 0); + return index; } } -fn freeTextBlock(self: *Coff, text_block: *TextBlock) void { +fn growAtom(self: *Coff, atom: *Atom, new_atom_size: u64, alignment: u64, sect_id: u16) !u64 { + const sym = atom.getSymbol(self); + const align_ok = mem.alignBackwardGeneric(u64, sym.value, alignment) == sym.value; + const need_realloc = !align_ok or new_atom_size > atom.capacity(self); + if (!need_realloc) return sym.value; + return self.allocateAtom(atom, new_atom_size, alignment, sect_id); +} + +fn shrinkAtom(self: *Coff, atom: *Atom, new_block_size: u64, sect_id: u16) void { + _ = self; + _ = atom; + _ = new_block_size; + _ = sect_id; + // TODO check the new capacity, and if it crosses the size threshold into a big enough + // capacity, insert a free list node for it. +} + +fn freeAtom(self: *Coff, atom: *Atom, sect_id: u16) void { + log.debug("freeAtom {*}", .{atom}); + + const free_list = &self.sections.items(.free_list)[sect_id]; var already_have_free_list_node = false; { var i: usize = 0; - // TODO turn text_block_free_list into a hash map - while (i < self.text_block_free_list.items.len) { - if (self.text_block_free_list.items[i] == text_block) { - _ = self.text_block_free_list.swapRemove(i); + // TODO turn free_list into a hash map + while (i < free_list.items.len) { + if (free_list.items[i] == atom) { + _ = free_list.swapRemove(i); continue; } - if (self.text_block_free_list.items[i] == text_block.prev) { + if (free_list.items[i] == atom.prev) { already_have_free_list_node = true; } i += 1; } } - if (self.last_text_block == text_block) { - self.last_text_block = text_block.prev; + + const maybe_last_atom = &self.sections.items(.last_atom)[sect_id]; + if (maybe_last_atom.*) |last_atom| { + if (last_atom == atom) { + if (atom.prev) |prev| { + // TODO shrink the section size here + maybe_last_atom.* = prev; + } else { + maybe_last_atom.* = null; + } + } } - if (text_block.prev) |prev| { - prev.next = text_block.next; - if (!already_have_free_list_node and prev.freeListEligible()) { + if (atom.prev) |prev| { + prev.next = atom.next; + + if (!already_have_free_list_node and prev.freeListEligible(self)) { // The free list is heuristics, it doesn't have to be perfect, so we can // ignore the OOM here. - self.text_block_free_list.append(self.base.allocator, prev) catch {}; + free_list.append(self.base.allocator, prev) catch {}; } + } else { + atom.prev = null; } - if (text_block.next) |next| { - next.prev = text_block.prev; - } -} - -fn writeOffsetTableEntry(self: *Coff, index: usize) !void { - const entry_size = self.base.options.target.cpu.arch.ptrBitWidth() / 8; - const endian = self.base.options.target.cpu.arch.endian(); - - const offset_table_start = self.section_data_offset; - if (self.offset_table_size_dirty) { - const current_raw_size = self.offset_table_size; - const new_raw_size = self.offset_table_size * 2; - log.debug("growing offset table from raw size {} to {}\n", .{ current_raw_size, new_raw_size }); - - // Move the text section to a new place in the executable - const current_text_section_start = self.section_data_offset + current_raw_size; - const new_text_section_start = self.section_data_offset + new_raw_size; - - const amt = try self.base.file.?.copyRangeAll(current_text_section_start, self.base.file.?, new_text_section_start, self.text_section_size); - if (amt != self.text_section_size) return error.InputOutput; - - // Write the new raw size in the .got header - var buf: [8]u8 = undefined; - mem.writeIntLittle(u32, buf[0..4], new_raw_size); - try self.base.file.?.pwriteAll(buf[0..4], self.section_table_offset + 16); - // Write the new .text section file offset in the .text section header - mem.writeIntLittle(u32, buf[0..4], new_text_section_start); - try self.base.file.?.pwriteAll(buf[0..4], self.section_table_offset + 40 + 20); - - const current_virtual_size = mem.alignForwardGeneric(u32, self.offset_table_size, section_alignment); - const new_virtual_size = mem.alignForwardGeneric(u32, new_raw_size, section_alignment); - // If we had to move in the virtual address space, we need to fix the VAs in the offset table, as well as the virtual address of the `.text` section - // and the virtual size of the `.got` section - - if (new_virtual_size != current_virtual_size) { - log.debug("growing offset table from virtual size {} to {}\n", .{ current_virtual_size, new_virtual_size }); - self.size_of_image_dirty = true; - const va_offset = new_virtual_size - current_virtual_size; - - // Write .got virtual size - mem.writeIntLittle(u32, buf[0..4], new_virtual_size); - try self.base.file.?.pwriteAll(buf[0..4], self.section_table_offset + 8); - - // Write .text new virtual address - self.text_section_virtual_address = self.text_section_virtual_address + va_offset; - mem.writeIntLittle(u32, buf[0..4], self.text_section_virtual_address - default_image_base); - try self.base.file.?.pwriteAll(buf[0..4], self.section_table_offset + 40 + 12); - - // Fix the VAs in the offset table - for (self.offset_table.items) |*va, idx| { - if (va.* != 0) { - va.* += va_offset; - - switch (entry_size) { - 4 => { - mem.writeInt(u32, buf[0..4], @intCast(u32, va.*), endian); - try self.base.file.?.pwriteAll(buf[0..4], offset_table_start + idx * entry_size); - }, - 8 => { - mem.writeInt(u64, &buf, va.*, endian); - try self.base.file.?.pwriteAll(&buf, offset_table_start + idx * entry_size); - }, - else => unreachable, - } - } - } - } - self.offset_table_size = new_raw_size; - self.offset_table_size_dirty = false; - } - // Write the new entry - switch (entry_size) { - 4 => { - var buf: [4]u8 = undefined; - mem.writeInt(u32, &buf, @intCast(u32, self.offset_table.items[index]), endian); - try self.base.file.?.pwriteAll(&buf, offset_table_start + index * entry_size); - }, - 8 => { - var buf: [8]u8 = undefined; - mem.writeInt(u64, &buf, self.offset_table.items[index], endian); - try self.base.file.?.pwriteAll(&buf, offset_table_start + index * entry_size); - }, - else => unreachable, + if (atom.next) |next| { + next.prev = atom.prev; + } else { + atom.next = null; } } @@ -470,15 +471,19 @@ pub fn updateFunc(self: *Coff, module: *Module, func: *Module.Fn, air: Air, live }, }; - return self.finishUpdateDecl(module, func.owner_decl, code); + const sym = try self.updateDeclCode(decl_index, code); + log.debug("updated decl code has sym {}", .{sym}); + + // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. + const decl_exports = module.decl_exports.get(decl_index) orelse &[0]*Module.Export{}; + return self.updateDeclExports(module, decl_index, decl_exports); } pub fn lowerUnnamedConst(self: *Coff, tv: TypedValue, decl_index: Module.Decl.Index) !u32 { _ = self; _ = tv; _ = decl_index; - log.debug("TODO lowerUnnamedConst for Coff", .{}); - return error.AnalysisFail; + @panic("TODO lowerUnnamedConst"); } pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) !void { @@ -503,9 +508,6 @@ pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) ! } } - // TODO COFF/PE debug information - // TODO Implement exports - var code_buffer = std.ArrayList(u8).init(self.base.allocator); defer code_buffer.deinit(); @@ -526,49 +528,21 @@ pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) ! }, }; - return self.finishUpdateDecl(module, decl_index, code); -} - -fn finishUpdateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index, code: []const u8) !void { - const decl = module.declPtr(decl_index); - const required_alignment = decl.ty.abiAlignment(self.base.options.target); - const curr_size = decl.link.coff.size; - if (curr_size != 0) { - const capacity = decl.link.coff.capacity(); - const need_realloc = code.len > capacity or - !mem.isAlignedGeneric(u32, decl.link.coff.text_offset, required_alignment); - if (need_realloc) { - const curr_vaddr = self.text_section_virtual_address + decl.link.coff.text_offset; - const vaddr = try self.growTextBlock(&decl.link.coff, code.len, required_alignment); - log.debug("growing {s} from 0x{x} to 0x{x}\n", .{ decl.name, curr_vaddr, vaddr }); - if (vaddr != curr_vaddr) { - log.debug(" (writing new offset table entry)\n", .{}); - self.offset_table.items[decl.link.coff.offset_table_index] = vaddr; - try self.writeOffsetTableEntry(decl.link.coff.offset_table_index); - } - } else if (code.len < curr_size) { - self.shrinkTextBlock(&decl.link.coff, code.len); - } - } else { - const vaddr = try self.allocateTextBlock(&decl.link.coff, code.len, required_alignment); - log.debug("allocated text block for {s} at 0x{x} (size: {Bi})\n", .{ - mem.sliceTo(decl.name, 0), - vaddr, - std.fmt.fmtIntSizeDec(code.len), - }); - errdefer self.freeTextBlock(&decl.link.coff); - self.offset_table.items[decl.link.coff.offset_table_index] = vaddr; - try self.writeOffsetTableEntry(decl.link.coff.offset_table_index); - } - - // Write the code into the file - try self.base.file.?.pwriteAll(code, self.section_data_offset + self.offset_table_size + decl.link.coff.text_offset); + const sym = try self.updateDeclCode(decl_index, code); + log.debug("updated decl code for {}", .{sym}); // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. const decl_exports = module.decl_exports.get(decl_index) orelse &[0]*Module.Export{}; return self.updateDeclExports(module, decl_index, decl_exports); } +fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8) !*coff.Symbol { + _ = self; + _ = decl_index; + _ = code; + @panic("TODO updateDeclCode"); +} + pub fn freeDecl(self: *Coff, decl_index: Module.Decl.Index) void { if (build_options.have_llvm) { if (self.llvm_object) |llvm_object| return llvm_object.freeDecl(decl_index); @@ -577,9 +551,31 @@ pub fn freeDecl(self: *Coff, decl_index: Module.Decl.Index) void { const mod = self.base.options.module.?; const decl = mod.declPtr(decl_index); + log.debug("freeDecl {*}", .{decl}); + + const kv = self.decls.fetchRemove(decl_index); + if (kv.?.value) |index| { + self.freeAtom(&decl.link.coff, index); + } + // Appending to free lists is allowed to fail because the free lists are heuristics based anyway. - self.freeTextBlock(&decl.link.coff); - self.offset_table_free_list.append(self.base.allocator, decl.link.coff.offset_table_index) catch {}; + const gpa = self.base.allocator; + const sym_index = decl.link.coff.sym_index; + if (sym_index != 0) { + self.locals_free_list.append(gpa, sym_index) catch {}; + + // Try freeing GOT atom if this decl had one + const got_target = SymbolWithLoc{ .sym_index = sym_index, .file = null }; + if (self.got_entries.getIndex(got_target)) |got_index| { + self.got_entries_free_list.append(gpa, @intCast(u32, got_index)) catch {}; + self.got_entries.values()[got_index] = 0; + log.debug(" adding GOT index {d} to free list (target local@{d})", .{ got_index, sym_index }); + } + + self.locals.items[sym_index].section_number = @intToEnum(coff.SectionNumber, 0); + _ = self.atom_by_index_table.remove(sym_index); + decl.link.coff.sym_index = 0; + } } pub fn updateDeclExports( @@ -625,28 +621,7 @@ pub fn updateDeclExports( if (self.llvm_object) |llvm_object| return llvm_object.updateDeclExports(module, decl_index, exports); } - const decl = module.declPtr(decl_index); - for (exports) |exp| { - if (exp.options.section) |section_name| { - if (!mem.eql(u8, section_name, ".text")) { - try module.failed_exports.ensureUnusedCapacity(module.gpa, 1); - module.failed_exports.putAssumeCapacityNoClobber( - exp, - try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: ExportOptions.section", .{}), - ); - continue; - } - } - if (mem.eql(u8, exp.options.name, "wWinMainCRTStartup")) { - self.entry_addr = decl.link.coff.getVAddr(self.*) - default_image_base; - } else { - try module.failed_exports.ensureUnusedCapacity(module.gpa, 1); - module.failed_exports.putAssumeCapacityNoClobber( - exp, - try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: Exports other than 'wWinMainCRTStartup'", .{}), - ); - } - } + @panic("TODO updateDeclExports"); } pub fn flush(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void { @@ -660,7 +635,7 @@ pub fn flush(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !vo } const use_lld = build_options.have_llvm and self.base.options.use_lld; if (use_lld) { - return self.linkWithLLD(comp, prog_node); + return lld.linkWithLLD(self, comp, prog_node); } switch (self.base.options.output_mode) { .Exe, .Obj => return self.flushModule(comp, prog_node), @@ -682,888 +657,68 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod sub_prog_node.activate(); defer sub_prog_node.end(); - const output_mode = self.base.options.output_mode; - log.debug("in flushModule with {}", .{output_mode}); - - var coff_file_header_offset: u32 = 0; - if (output_mode == .Exe) { - // Write the MS-DOS stub and the PE signature - try self.base.file.?.pwriteAll(msdos_stub ++ "PE\x00\x00", 0); - coff_file_header_offset = msdos_stub.len + 4; - } - - // COFF file header - const data_directory_count = 0; - var hdr_data: [112 + data_directory_count * 8 + section_table_size]u8 = undefined; - var index: usize = 0; - - const machine = self.base.options.target.cpu.arch.toCoffMachine(); - if (machine == .Unknown) { - return error.UnsupportedCOFFArchitecture; - } - mem.writeIntLittle(u16, hdr_data[0..2], @enumToInt(machine)); - index += 2; - - // Number of sections (we only use .got, .text) - mem.writeIntLittle(u16, hdr_data[index..][0..2], 2); - index += 2; - // TimeDateStamp (u32), PointerToSymbolTable (u32), NumberOfSymbols (u32) - mem.set(u8, hdr_data[index..][0..12], 0); - index += 12; - - const optional_header_size = switch (output_mode) { - .Exe => data_directory_count * 8 + switch (self.ptr_width) { - .p32 => @as(u16, 96), - .p64 => 112, - }, - else => 0, - }; - - const default_offset_table_size = file_alignment; - const default_size_of_code = 0; - - // Size of file when loaded in memory - const size_of_image = mem.alignForwardGeneric(u32, self.text_section_virtual_address - default_image_base + default_size_of_code, section_alignment); - - mem.writeIntLittle(u16, hdr_data[index..][0..2], optional_header_size); - index += 2; - - // Characteristics - var flags: std.coff.CoffHeaderFlags = .{ - // TODO Remove debug info stripped flag when necessary - .DEBUG_STRIPPED = 1, - .RELOCS_STRIPPED = 1, - }; - if (output_mode == .Exe) { - flags.EXECUTABLE_IMAGE = 1; - } - switch (self.ptr_width) { - .p32 => flags.@"32BIT_MACHINE" = 1, - .p64 => flags.LARGE_ADDRESS_AWARE = 1, - } - mem.writeIntLittle(u16, hdr_data[index..][0..2], @bitCast(u16, flags)); - index += 2; - - assert(index == 20); - try self.base.file.?.pwriteAll(hdr_data[0..index], coff_file_header_offset); - - if (output_mode == .Exe) { - self.optional_header_offset = coff_file_header_offset + 20; - // Optional header - index = 0; - mem.writeIntLittle(u16, hdr_data[0..2], switch (self.ptr_width) { - .p32 => @as(u16, 0x10b), - .p64 => 0x20b, - }); - index += 2; - - // Linker version (u8 + u8) - mem.set(u8, hdr_data[index..][0..2], 0); - index += 2; - - // SizeOfCode (UNUSED, u32), SizeOfInitializedData (u32), SizeOfUninitializedData (u32), AddressOfEntryPoint (u32), BaseOfCode (UNUSED, u32) - mem.set(u8, hdr_data[index..][0..20], 0); - index += 20; - - if (self.ptr_width == .p32) { - // Base of data relative to the image base (UNUSED) - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - - // Image base address - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_image_base); - index += 4; - } else { - // Image base address - mem.writeIntLittle(u64, hdr_data[index..][0..8], default_image_base); - index += 8; - } - - // Section alignment - mem.writeIntLittle(u32, hdr_data[index..][0..4], section_alignment); - index += 4; - // File alignment - mem.writeIntLittle(u32, hdr_data[index..][0..4], file_alignment); - index += 4; - // Required OS version, 6.0 is vista - mem.writeIntLittle(u16, hdr_data[index..][0..2], 6); - index += 2; - mem.writeIntLittle(u16, hdr_data[index..][0..2], 0); - index += 2; - // Image version - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - // Required subsystem version, same as OS version - mem.writeIntLittle(u16, hdr_data[index..][0..2], 6); - index += 2; - mem.writeIntLittle(u16, hdr_data[index..][0..2], 0); - index += 2; - // Reserved zeroes (u32) - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], size_of_image); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset); - index += 4; - // CheckSum (u32) - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - // Subsystem, TODO: Let users specify the subsystem, always CUI for now - mem.writeIntLittle(u16, hdr_data[index..][0..2], 3); - index += 2; - // DLL characteristics - mem.writeIntLittle(u16, hdr_data[index..][0..2], 0x0); - index += 2; - - switch (self.ptr_width) { - .p32 => { - // Size of stack reserve + commit - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000_000); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000); - index += 4; - // Size of heap reserve + commit - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x100_000); - index += 4; - mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000); - index += 4; - }, - .p64 => { - // Size of stack reserve + commit - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000_000); - index += 8; - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000); - index += 8; - // Size of heap reserve + commit - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x100_000); - index += 8; - mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000); - index += 8; - }, - } - - // Reserved zeroes - mem.set(u8, hdr_data[index..][0..4], 0); - index += 4; - - // Number of data directories - mem.writeIntLittle(u32, hdr_data[index..][0..4], data_directory_count); - index += 4; - // Initialize data directories to zero - mem.set(u8, hdr_data[index..][0 .. data_directory_count * 8], 0); - index += data_directory_count * 8; - - assert(index == optional_header_size); - } - - // Write section table. - // First, the .got section - hdr_data[index..][0..8].* = ".got\x00\x00\x00\x00".*; - index += 8; - if (output_mode == .Exe) { - // Virtual size (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size); - index += 4; - // Virtual address (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.offset_table_virtual_address - default_image_base); - index += 4; - } else { - mem.set(u8, hdr_data[index..][0..8], 0); - index += 8; - } - // Size of raw data (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size); - index += 4; - // File pointer to the start of the section - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset); - index += 4; - // Pointer to relocations (u32), PointerToLinenumbers (u32), NumberOfRelocations (u16), NumberOfLinenumbers (u16) - mem.set(u8, hdr_data[index..][0..12], 0); - index += 12; - // Section flags - mem.writeIntLittle(u32, hdr_data[index..][0..4], @bitCast(u32, std.coff.SectionHeaderFlags{ - .CNT_INITIALIZED_DATA = 1, - .MEM_READ = 1, - })); - index += 4; - // Then, the .text section - hdr_data[index..][0..8].* = ".text\x00\x00\x00".*; - index += 8; - if (output_mode == .Exe) { - // Virtual size (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code); - index += 4; - // Virtual address (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.text_section_virtual_address - default_image_base); - index += 4; - } else { - mem.set(u8, hdr_data[index..][0..8], 0); - index += 8; - } - // Size of raw data (u32) - mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code); - index += 4; - // File pointer to the start of the section - mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset + default_offset_table_size); - index += 4; - // Pointer to relocations (u32), PointerToLinenumbers (u32), NumberOfRelocations (u16), NumberOfLinenumbers (u16) - mem.set(u8, hdr_data[index..][0..12], 0); - index += 12; - // Section flags - mem.writeIntLittle(u32, hdr_data[index..][0..4], @bitCast(u32, std.coff.SectionHeaderFlags{ - .CNT_CODE = 1, - .MEM_EXECUTE = 1, - .MEM_READ = 1, - .MEM_WRITE = 1, - })); - index += 4; - - assert(index == optional_header_size + section_table_size); - try self.base.file.?.pwriteAll(hdr_data[0..index], self.optional_header_offset); - try self.base.file.?.setEndPos(self.section_data_offset + default_offset_table_size + default_size_of_code); - - if (self.text_section_size_dirty) { - // Write the new raw size in the .text header - var buf: [4]u8 = undefined; - mem.writeIntLittle(u32, &buf, self.text_section_size); - try self.base.file.?.pwriteAll(&buf, self.section_table_offset + 40 + 16); - try self.base.file.?.setEndPos(self.section_data_offset + self.offset_table_size + self.text_section_size); - self.text_section_size_dirty = false; - } - - if (self.base.options.output_mode == .Exe and self.size_of_image_dirty) { - const new_size_of_image = mem.alignForwardGeneric(u32, self.text_section_virtual_address - default_image_base + self.text_section_size, section_alignment); - var buf: [4]u8 = undefined; - mem.writeIntLittle(u32, &buf, new_size_of_image); - try self.base.file.?.pwriteAll(&buf, self.optional_header_offset + 56); - self.size_of_image_dirty = false; - } - if (self.entry_addr == null and self.base.options.output_mode == .Exe) { log.debug("flushing. no_entry_point_found = true\n", .{}); self.error_flags.no_entry_point_found = true; } else { log.debug("flushing. no_entry_point_found = false\n", .{}); self.error_flags.no_entry_point_found = false; - - if (self.base.options.output_mode == .Exe) { - // Write AddressOfEntryPoint - var buf: [4]u8 = undefined; - mem.writeIntLittle(u32, &buf, self.entry_addr.?); - try self.base.file.?.pwriteAll(&buf, self.optional_header_offset + 16); - } } } -fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void { - const tracy = trace(@src()); - defer tracy.end(); - - var arena_allocator = std.heap.ArenaAllocator.init(self.base.allocator); - defer arena_allocator.deinit(); - const arena = arena_allocator.allocator(); - - const directory = self.base.options.emit.?.directory; // Just an alias to make it shorter to type. - const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); - - // If there is no Zig code to compile, then we should skip flushing the output file because it - // will not be part of the linker line anyway. - const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: { - const use_stage1 = build_options.have_stage1 and self.base.options.use_stage1; - if (use_stage1) { - const obj_basename = try std.zig.binNameAlloc(arena, .{ - .root_name = self.base.options.root_name, - .target = self.base.options.target, - .output_mode = .Obj, - }); - switch (self.base.options.cache_mode) { - .incremental => break :blk try module.zig_cache_artifact_directory.join( - arena, - &[_][]const u8{obj_basename}, - ), - .whole => break :blk try fs.path.join(arena, &.{ - fs.path.dirname(full_out_path).?, obj_basename, - }), - } - } - - try self.flushModule(comp, prog_node); - - if (fs.path.dirname(full_out_path)) |dirname| { - break :blk try fs.path.join(arena, &.{ dirname, self.base.intermediary_basename.? }); - } else { - break :blk self.base.intermediary_basename.?; - } - } else null; - - var sub_prog_node = prog_node.start("LLD Link", 0); - sub_prog_node.activate(); - sub_prog_node.context.refresh(); - defer sub_prog_node.end(); - - const is_lib = self.base.options.output_mode == .Lib; - const is_dyn_lib = self.base.options.link_mode == .Dynamic and is_lib; - const is_exe_or_dyn_lib = is_dyn_lib or self.base.options.output_mode == .Exe; - const link_in_crt = self.base.options.link_libc and is_exe_or_dyn_lib; - const target = self.base.options.target; - - // See link/Elf.zig for comments on how this mechanism works. - const id_symlink_basename = "lld.id"; - - var man: Cache.Manifest = undefined; - defer if (!self.base.options.disable_lld_caching) man.deinit(); - - var digest: [Cache.hex_digest_len]u8 = undefined; - - if (!self.base.options.disable_lld_caching) { - man = comp.cache_parent.obtain(); - self.base.releaseLock(); - - comptime assert(Compilation.link_hash_implementation_version == 7); - - for (self.base.options.objects) |obj| { - _ = try man.addFile(obj.path, null); - man.hash.add(obj.must_link); - } - for (comp.c_object_table.keys()) |key| { - _ = try man.addFile(key.status.success.object_path, null); - } - try man.addOptionalFile(module_obj_path); - man.hash.addOptionalBytes(self.base.options.entry); - man.hash.addOptional(self.base.options.stack_size_override); - man.hash.addOptional(self.base.options.image_base_override); - man.hash.addListOfBytes(self.base.options.lib_dirs); - man.hash.add(self.base.options.skip_linker_dependencies); - if (self.base.options.link_libc) { - man.hash.add(self.base.options.libc_installation != null); - if (self.base.options.libc_installation) |libc_installation| { - man.hash.addBytes(libc_installation.crt_dir.?); - if (target.abi == .msvc) { - man.hash.addBytes(libc_installation.msvc_lib_dir.?); - man.hash.addBytes(libc_installation.kernel32_lib_dir.?); - } - } - } - link.hashAddSystemLibs(&man.hash, self.base.options.system_libs); - man.hash.addListOfBytes(self.base.options.force_undefined_symbols.keys()); - man.hash.addOptional(self.base.options.subsystem); - man.hash.add(self.base.options.is_test); - man.hash.add(self.base.options.tsaware); - man.hash.add(self.base.options.nxcompat); - man.hash.add(self.base.options.dynamicbase); - // strip does not need to go into the linker hash because it is part of the hash namespace - man.hash.addOptional(self.base.options.major_subsystem_version); - man.hash.addOptional(self.base.options.minor_subsystem_version); - - // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. - _ = try man.hit(); - digest = man.final(); - var prev_digest_buf: [digest.len]u8 = undefined; - const prev_digest: []u8 = Cache.readSmallFile( - directory.handle, - id_symlink_basename, - &prev_digest_buf, - ) catch |err| blk: { - log.debug("COFF LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); - // Handle this as a cache miss. - break :blk prev_digest_buf[0..0]; - }; - if (mem.eql(u8, prev_digest, &digest)) { - log.debug("COFF LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); - // Hot diggity dog! The output binary is already there. - self.base.lock = man.toOwnedLock(); - return; - } - log.debug("COFF LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); - - // We are about to change the output file to be different, so we invalidate the build hash now. - directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { - error.FileNotFound => {}, - else => |e| return e, - }; - } - - if (self.base.options.output_mode == .Obj) { - // LLD's COFF driver does not support the equivalent of `-r` so we do a simple file copy - // here. TODO: think carefully about how we can avoid this redundant operation when doing - // build-obj. See also the corresponding TODO in linkAsArchive. - const the_object_path = blk: { - if (self.base.options.objects.len != 0) - break :blk self.base.options.objects[0].path; - - if (comp.c_object_table.count() != 0) - break :blk comp.c_object_table.keys()[0].status.success.object_path; - - if (module_obj_path) |p| - break :blk p; - - // TODO I think this is unreachable. Audit this situation when solving the above TODO - // regarding eliding redundant object -> object transformations. - return error.NoObjectsToLink; - }; - // This can happen when using --enable-cache and using the stage1 backend. In this case - // we can skip the file copy. - if (!mem.eql(u8, the_object_path, full_out_path)) { - try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{}); - } - } else { - // Create an LLD command line and invoke it. - var argv = std.ArrayList([]const u8).init(self.base.allocator); - defer argv.deinit(); - // We will invoke ourselves as a child process to gain access to LLD. - // This is necessary because LLD does not behave properly as a library - - // it calls exit() and does not reset all global data between invocations. - try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, "lld-link" }); - - try argv.append("-ERRORLIMIT:0"); - try argv.append("-NOLOGO"); - if (!self.base.options.strip) { - try argv.append("-DEBUG"); - } - if (self.base.options.lto) { - switch (self.base.options.optimize_mode) { - .Debug => {}, - .ReleaseSmall => try argv.append("-OPT:lldlto=2"), - .ReleaseFast, .ReleaseSafe => try argv.append("-OPT:lldlto=3"), - } - } - if (self.base.options.output_mode == .Exe) { - const stack_size = self.base.options.stack_size_override orelse 16777216; - try argv.append(try allocPrint(arena, "-STACK:{d}", .{stack_size})); - } - if (self.base.options.image_base_override) |image_base| { - try argv.append(try std.fmt.allocPrint(arena, "-BASE:{d}", .{image_base})); - } - - if (target.cpu.arch == .i386) { - try argv.append("-MACHINE:X86"); - } else if (target.cpu.arch == .x86_64) { - try argv.append("-MACHINE:X64"); - } else if (target.cpu.arch.isARM()) { - if (target.cpu.arch.ptrBitWidth() == 32) { - try argv.append("-MACHINE:ARM"); - } else { - try argv.append("-MACHINE:ARM64"); - } - } - - for (self.base.options.force_undefined_symbols.keys()) |symbol| { - try argv.append(try allocPrint(arena, "-INCLUDE:{s}", .{symbol})); - } - - if (is_dyn_lib) { - try argv.append("-DLL"); - } - - if (self.base.options.entry) |entry| { - try argv.append(try allocPrint(arena, "-ENTRY:{s}", .{entry})); - } - - if (self.base.options.tsaware) { - try argv.append("-tsaware"); - } - if (self.base.options.nxcompat) { - try argv.append("-nxcompat"); - } - if (self.base.options.dynamicbase) { - try argv.append("-dynamicbase"); - } - - try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path})); - - if (self.base.options.implib_emit) |emit| { - const implib_out_path = try emit.directory.join(arena, &[_][]const u8{emit.sub_path}); - try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path})); - } - - if (self.base.options.link_libc) { - if (self.base.options.libc_installation) |libc_installation| { - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.crt_dir.?})); - - if (target.abi == .msvc) { - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.msvc_lib_dir.?})); - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.kernel32_lib_dir.?})); - } - } - } - - for (self.base.options.lib_dirs) |lib_dir| { - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{lib_dir})); - } - - try argv.ensureUnusedCapacity(self.base.options.objects.len); - for (self.base.options.objects) |obj| { - if (obj.must_link) { - argv.appendAssumeCapacity(try allocPrint(arena, "-WHOLEARCHIVE:{s}", .{obj.path})); - } else { - argv.appendAssumeCapacity(obj.path); - } - } - - for (comp.c_object_table.keys()) |key| { - try argv.append(key.status.success.object_path); - } - - if (module_obj_path) |p| { - try argv.append(p); - } - - const resolved_subsystem: ?std.Target.SubSystem = blk: { - if (self.base.options.subsystem) |explicit| break :blk explicit; - switch (target.os.tag) { - .windows => { - if (self.base.options.module) |module| { - if (module.stage1_flags.have_dllmain_crt_startup or is_dyn_lib) - break :blk null; - if (module.stage1_flags.have_c_main or self.base.options.is_test or - module.stage1_flags.have_winmain_crt_startup or - module.stage1_flags.have_wwinmain_crt_startup) - { - break :blk .Console; - } - if (module.stage1_flags.have_winmain or module.stage1_flags.have_wwinmain) - break :blk .Windows; - } - }, - .uefi => break :blk .EfiApplication, - else => {}, - } - break :blk null; - }; - - const Mode = enum { uefi, win32 }; - const mode: Mode = mode: { - if (resolved_subsystem) |subsystem| { - const subsystem_suffix = ss: { - if (self.base.options.major_subsystem_version) |major| { - if (self.base.options.minor_subsystem_version) |minor| { - break :ss try allocPrint(arena, ",{d}.{d}", .{ major, minor }); - } else { - break :ss try allocPrint(arena, ",{d}", .{major}); - } - } - break :ss ""; - }; - - switch (subsystem) { - .Console => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:console{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - .EfiApplication => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_application{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .EfiBootServiceDriver => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_boot_service_driver{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .EfiRom => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_rom{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .EfiRuntimeDriver => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_runtime_driver{s}", .{ - subsystem_suffix, - })); - break :mode .uefi; - }, - .Native => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:native{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - .Posix => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:posix{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - .Windows => { - try argv.append(try allocPrint(arena, "-SUBSYSTEM:windows{s}", .{ - subsystem_suffix, - })); - break :mode .win32; - }, - } - } else if (target.os.tag == .uefi) { - break :mode .uefi; - } else { - break :mode .win32; - } - }; - - switch (mode) { - .uefi => try argv.appendSlice(&[_][]const u8{ - "-BASE:0", - "-ENTRY:EfiMain", - "-OPT:REF", - "-SAFESEH:NO", - "-MERGE:.rdata=.data", - "-ALIGN:32", - "-NODEFAULTLIB", - "-SECTION:.xdata,D", - }), - .win32 => { - if (link_in_crt) { - if (target.abi.isGnu()) { - try argv.append("-lldmingw"); - - if (target.cpu.arch == .i386) { - try argv.append("-ALTERNATENAME:__image_base__=___ImageBase"); - } else { - try argv.append("-ALTERNATENAME:__image_base__=__ImageBase"); - } - - if (is_dyn_lib) { - try argv.append(try comp.get_libc_crt_file(arena, "dllcrt2.obj")); - if (target.cpu.arch == .i386) { - try argv.append("-ALTERNATENAME:__DllMainCRTStartup@12=_DllMainCRTStartup@12"); - } else { - try argv.append("-ALTERNATENAME:_DllMainCRTStartup=DllMainCRTStartup"); - } - } else { - try argv.append(try comp.get_libc_crt_file(arena, "crt2.obj")); - } - - try argv.append(try comp.get_libc_crt_file(arena, "mingw32.lib")); - try argv.append(try comp.get_libc_crt_file(arena, "mingwex.lib")); - try argv.append(try comp.get_libc_crt_file(arena, "msvcrt-os.lib")); - - for (mingw.always_link_libs) |name| { - if (!self.base.options.system_libs.contains(name)) { - const lib_basename = try allocPrint(arena, "{s}.lib", .{name}); - try argv.append(try comp.get_libc_crt_file(arena, lib_basename)); - } - } - } else { - const lib_str = switch (self.base.options.link_mode) { - .Dynamic => "", - .Static => "lib", - }; - const d_str = switch (self.base.options.optimize_mode) { - .Debug => "d", - else => "", - }; - switch (self.base.options.link_mode) { - .Static => try argv.append(try allocPrint(arena, "libcmt{s}.lib", .{d_str})), - .Dynamic => try argv.append(try allocPrint(arena, "msvcrt{s}.lib", .{d_str})), - } - - try argv.append(try allocPrint(arena, "{s}vcruntime{s}.lib", .{ lib_str, d_str })); - try argv.append(try allocPrint(arena, "{s}ucrt{s}.lib", .{ lib_str, d_str })); - - //Visual C++ 2015 Conformance Changes - //https://msdn.microsoft.com/en-us/library/bb531344.aspx - try argv.append("legacy_stdio_definitions.lib"); - - // msvcrt depends on kernel32 and ntdll - try argv.append("kernel32.lib"); - try argv.append("ntdll.lib"); - } - } else { - try argv.append("-NODEFAULTLIB"); - if (!is_lib) { - if (self.base.options.module) |module| { - if (module.stage1_flags.have_winmain_crt_startup) { - try argv.append("-ENTRY:WinMainCRTStartup"); - } else { - try argv.append("-ENTRY:wWinMainCRTStartup"); - } - } else { - try argv.append("-ENTRY:wWinMainCRTStartup"); - } - } - } - }, - } - - // libc++ dep - if (self.base.options.link_libcpp) { - try argv.append(comp.libcxxabi_static_lib.?.full_object_path); - try argv.append(comp.libcxx_static_lib.?.full_object_path); - } - - // libunwind dep - if (self.base.options.link_libunwind) { - try argv.append(comp.libunwind_static_lib.?.full_object_path); - } - - if (is_exe_or_dyn_lib and !self.base.options.skip_linker_dependencies) { - if (!self.base.options.link_libc) { - if (comp.libc_static_lib) |lib| { - try argv.append(lib.full_object_path); - } - } - // MinGW doesn't provide libssp symbols - if (target.abi.isGnu()) { - if (comp.libssp_static_lib) |lib| { - try argv.append(lib.full_object_path); - } - } - // MSVC compiler_rt is missing some stuff, so we build it unconditionally but - // and rely on weak linkage to allow MSVC compiler_rt functions to override ours. - if (comp.compiler_rt_lib) |lib| { - try argv.append(lib.full_object_path); - } - } - - try argv.ensureUnusedCapacity(self.base.options.system_libs.count()); - for (self.base.options.system_libs.keys()) |key| { - const lib_basename = try allocPrint(arena, "{s}.lib", .{key}); - if (comp.crt_files.get(lib_basename)) |crt_file| { - argv.appendAssumeCapacity(crt_file.full_object_path); - continue; - } - if (try self.findLib(arena, lib_basename)) |full_path| { - argv.appendAssumeCapacity(full_path); - continue; - } - if (target.abi.isGnu()) { - const fallback_name = try allocPrint(arena, "lib{s}.dll.a", .{key}); - if (try self.findLib(arena, fallback_name)) |full_path| { - argv.appendAssumeCapacity(full_path); - continue; - } - } - log.err("DLL import library for -l{s} not found", .{key}); - return error.DllImportLibraryNotFound; - } - - if (self.base.options.verbose_link) { - // Skip over our own name so that the LLD linker name is the first argv item. - Compilation.dump_argv(argv.items[1..]); - } - - if (std.process.can_spawn) { - // If possible, we run LLD as a child process because it does not always - // behave properly as a library, unfortunately. - // https://github.com/ziglang/zig/issues/3825 - var child = std.ChildProcess.init(argv.items, arena); - if (comp.clang_passthrough_mode) { - child.stdin_behavior = .Inherit; - child.stdout_behavior = .Inherit; - child.stderr_behavior = .Inherit; - - const term = child.spawnAndWait() catch |err| { - log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); - return error.UnableToSpawnSelf; - }; - switch (term) { - .Exited => |code| { - if (code != 0) { - std.process.exit(code); - } - }, - else => std.process.abort(), - } - } else { - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Ignore; - child.stderr_behavior = .Pipe; - - try child.spawn(); - - const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024); - - const term = child.wait() catch |err| { - log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); - return error.UnableToSpawnSelf; - }; - - switch (term) { - .Exited => |code| { - if (code != 0) { - // TODO parse this output and surface with the Compilation API rather than - // directly outputting to stderr here. - std.debug.print("{s}", .{stderr}); - return error.LLDReportedFailure; - } - }, - else => { - log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr }); - return error.LLDCrashed; - }, - } - - if (stderr.len != 0) { - log.warn("unexpected LLD stderr:\n{s}", .{stderr}); - } - } - } else { - const exit_code = try lldMain(arena, argv.items, false); - if (exit_code != 0) { - if (comp.clang_passthrough_mode) { - std.process.exit(exit_code); - } else { - return error.LLDReportedFailure; - } - } - } - } - - if (!self.base.options.disable_lld_caching) { - // Update the file with the digest. If it fails we can continue; it only - // means that the next invocation will have an unnecessary cache miss. - Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { - log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)}); - }; - // Again failure here only means an unnecessary cache miss. - man.writeManifest() catch |err| { - log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); - }; - // We hang on to this lock so that the output file path can be used without - // other processes clobbering it. - self.base.lock = man.toOwnedLock(); - } -} - -fn findLib(self: *Coff, arena: Allocator, name: []const u8) !?[]const u8 { - for (self.base.options.lib_dirs) |lib_dir| { - const full_path = try fs.path.join(arena, &.{ lib_dir, name }); - fs.cwd().access(full_path, .{}) catch |err| switch (err) { - error.FileNotFound => continue, - else => |e| return e, - }; - return full_path; - } - return null; -} - pub fn getDeclVAddr( self: *Coff, decl_index: Module.Decl.Index, reloc_info: link.File.RelocInfo, ) !u64 { + _ = self; + _ = decl_index; _ = reloc_info; - const mod = self.base.options.module.?; - const decl = mod.declPtr(decl_index); - assert(self.llvm_object == null); - return self.text_section_virtual_address + decl.link.coff.text_offset; + @panic("TODO getDeclVAddr"); } pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !void { _ = self; _ = module; _ = decl; - // TODO Implement this + log.debug("TODO implement updateDeclLineNumber", .{}); } -pub fn deinit(self: *Coff) void { - if (build_options.have_llvm) { - if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator); - } +pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { + // TODO https://github.com/ziglang/zig/issues/1284 + return math.add(@TypeOf(actual_size), actual_size, actual_size / ideal_factor) catch + math.maxInt(@TypeOf(actual_size)); +} + +/// Returns pointer-to-symbol described by `sym_with_loc` descriptor. +pub fn getSymbolPtr(self: *Coff, sym_loc: SymbolWithLoc) *coff.Symbol { + assert(sym_loc.file == null); // TODO linking object files + return &self.locals.items[sym_loc.sym_index]; +} + +/// Returns symbol described by `sym_with_loc` descriptor. +pub fn getSymbol(self: *Coff, sym_loc: SymbolWithLoc) coff.Symbol { + return self.getSymbolPtr(sym_loc).*; +} + +/// Returns name of the symbol described by `sym_with_loc` descriptor. +pub fn getSymbolName(self: *Coff, sym_loc: SymbolWithLoc) []const u8 { + assert(sym_loc.file == null); // TODO linking object files + const sym = self.locals.items[sym_loc.sym_index]; + const offset = sym.getNameOffset() orelse return sym.getName().?; + return self.strtab.get(offset).?; +} + +/// Returns atom if there is an atom referenced by the symbol described by `sym_with_loc` descriptor. +/// Returns null on failure. +pub fn getAtomForSymbol(self: *Coff, sym_loc: SymbolWithLoc) ?*Atom { + assert(sym_loc.file == null); // TODO linking with object files + return self.atom_by_index_table.get(sym_loc.sym_index); +} - self.text_block_free_list.deinit(self.base.allocator); - self.offset_table.deinit(self.base.allocator); - self.offset_table_free_list.deinit(self.base.allocator); +/// Returns GOT atom that references `sym_with_loc` if one exists. +/// Returns null otherwise. +pub fn getGotAtomForSymbol(self: *Coff, sym_loc: SymbolWithLoc) ?*Atom { + const got_index = self.got_entries.get(sym_loc) orelse return null; + return self.atom_by_index_table.get(got_index); } diff --git a/src/link/Coff/Atom.zig b/src/link/Coff/Atom.zig new file mode 100644 index 0000000000000000000000000000000000000000..b61e77f53e0f8e0a095d65e0635f8db2a8c84c84 --- /dev/null +++ b/src/link/Coff/Atom.zig @@ -0,0 +1,85 @@ +const Atom = @This(); + +const std = @import("std"); +const coff = std.coff; + +const Allocator = std.mem.Allocator; + +const Coff = @import("../Coff.zig"); +const SymbolWithLoc = Coff.SymbolWithLoc; + +/// Each decl always gets a local symbol with the fully qualified name. +/// The vaddr and size are found here directly. +/// The file offset is found by computing the vaddr offset from the section vaddr +/// the symbol references, and adding that to the file offset of the section. +/// If this field is 0, it means the codegen size = 0 and there is no symbol or +/// offset table entry. +sym_index: u32, + +/// null means symbol defined by Zig source. +file: ?u32, + +/// Used size of the atom +size: u64, + +/// Alignment of the atom +alignment: u32, + +/// Points to the previous and next neighbors, based on the `text_offset`. +/// This can be used to find, for example, the capacity of this `Atom`. +prev: ?*Atom, +next: ?*Atom, + +pub const empty = Atom{ + .sym_index = 0, + .file = null, + .size = 0, + .alignment = 0, + .prev = null, + .next = null, +}; + +pub fn deinit(self: *Atom, gpa: Allocator) void { + _ = self; + _ = gpa; +} + +pub fn getSymbol(self: Atom, coff_file: *Coff) coff.Symbol { + return self.getSymbolPtr(coff_file).*; +} + +pub fn getSymbolPtr(self: Atom, coff_file: *Coff) *coff.Symbol { + return coff_file.getSymbolPtr(.{ + .sym_index = self.sym_index, + .file = self.file, + }); +} + +pub fn getSymbolWithLoc(self: Atom) SymbolWithLoc { + return .{ .sym_index = self.sym_index, .file = self.file }; +} + +/// Returns how much room there is to grow in virtual address space. +pub fn capacity(self: Atom, coff_file: *Coff) u64 { + const self_sym = self.getSymbol(coff_file); + if (self.next) |next| { + const next_sym = next.getSymbol(coff_file); + return next_sym.value - self_sym.value; + } else { + // We are the last atom. + // The capacity is limited only by virtual address space. + return std.math.maxInt(u64) - self_sym.value; + } +} + +pub fn freeListEligible(self: Atom, coff_file: *Coff) bool { + // No need to keep a free list node for the last atom. + const next = self.next orelse return false; + const self_sym = self.getSymbol(coff_file); + const next_sym = next.getSymbol(coff_file); + const cap = next_sym.value - self_sym.value; + const ideal_cap = Coff.padToIdeal(self.size); + if (cap <= ideal_cap) return false; + const surplus = cap - ideal_cap; + return surplus >= Coff.min_text_capacity; +} diff --git a/src/link/Coff/lld.zig b/src/link/Coff/lld.zig new file mode 100644 index 0000000000000000000000000000000000000000..feae0134e9c58af2b975a44848485535ff5da278 --- /dev/null +++ b/src/link/Coff/lld.zig @@ -0,0 +1,602 @@ +const std = @import("std"); +const build_options = @import("build_options"); +const allocPrint = std.fmt.allocPrint; +const assert = std.debug.assert; +const fs = std.fs; +const log = std.log.scoped(.link); +const mem = std.mem; + +const mingw = @import("../../mingw.zig"); +const link = @import("../../link.zig"); +const lldMain = @import("../../main.zig").lldMain; +const trace = @import("../../tracy.zig").trace; + +const Allocator = mem.Allocator; + +const Cache = @import("../../Cache.zig"); +const Coff = @import("../Coff.zig"); +const Compilation = @import("../../Compilation.zig"); + +pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void { + const tracy = trace(@src()); + defer tracy.end(); + + var arena_allocator = std.heap.ArenaAllocator.init(self.base.allocator); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + const directory = self.base.options.emit.?.directory; // Just an alias to make it shorter to type. + const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); + + // If there is no Zig code to compile, then we should skip flushing the output file because it + // will not be part of the linker line anyway. + const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: { + const use_stage1 = build_options.have_stage1 and self.base.options.use_stage1; + if (use_stage1) { + const obj_basename = try std.zig.binNameAlloc(arena, .{ + .root_name = self.base.options.root_name, + .target = self.base.options.target, + .output_mode = .Obj, + }); + switch (self.base.options.cache_mode) { + .incremental => break :blk try module.zig_cache_artifact_directory.join( + arena, + &[_][]const u8{obj_basename}, + ), + .whole => break :blk try fs.path.join(arena, &.{ + fs.path.dirname(full_out_path).?, obj_basename, + }), + } + } + + try self.flushModule(comp, prog_node); + + if (fs.path.dirname(full_out_path)) |dirname| { + break :blk try fs.path.join(arena, &.{ dirname, self.base.intermediary_basename.? }); + } else { + break :blk self.base.intermediary_basename.?; + } + } else null; + + var sub_prog_node = prog_node.start("LLD Link", 0); + sub_prog_node.activate(); + sub_prog_node.context.refresh(); + defer sub_prog_node.end(); + + const is_lib = self.base.options.output_mode == .Lib; + const is_dyn_lib = self.base.options.link_mode == .Dynamic and is_lib; + const is_exe_or_dyn_lib = is_dyn_lib or self.base.options.output_mode == .Exe; + const link_in_crt = self.base.options.link_libc and is_exe_or_dyn_lib; + const target = self.base.options.target; + + // See link/Elf.zig for comments on how this mechanism works. + const id_symlink_basename = "lld.id"; + + var man: Cache.Manifest = undefined; + defer if (!self.base.options.disable_lld_caching) man.deinit(); + + var digest: [Cache.hex_digest_len]u8 = undefined; + + if (!self.base.options.disable_lld_caching) { + man = comp.cache_parent.obtain(); + self.base.releaseLock(); + + comptime assert(Compilation.link_hash_implementation_version == 7); + + for (self.base.options.objects) |obj| { + _ = try man.addFile(obj.path, null); + man.hash.add(obj.must_link); + } + for (comp.c_object_table.keys()) |key| { + _ = try man.addFile(key.status.success.object_path, null); + } + try man.addOptionalFile(module_obj_path); + man.hash.addOptionalBytes(self.base.options.entry); + man.hash.addOptional(self.base.options.stack_size_override); + man.hash.addOptional(self.base.options.image_base_override); + man.hash.addListOfBytes(self.base.options.lib_dirs); + man.hash.add(self.base.options.skip_linker_dependencies); + if (self.base.options.link_libc) { + man.hash.add(self.base.options.libc_installation != null); + if (self.base.options.libc_installation) |libc_installation| { + man.hash.addBytes(libc_installation.crt_dir.?); + if (target.abi == .msvc) { + man.hash.addBytes(libc_installation.msvc_lib_dir.?); + man.hash.addBytes(libc_installation.kernel32_lib_dir.?); + } + } + } + link.hashAddSystemLibs(&man.hash, self.base.options.system_libs); + man.hash.addListOfBytes(self.base.options.force_undefined_symbols.keys()); + man.hash.addOptional(self.base.options.subsystem); + man.hash.add(self.base.options.is_test); + man.hash.add(self.base.options.tsaware); + man.hash.add(self.base.options.nxcompat); + man.hash.add(self.base.options.dynamicbase); + // strip does not need to go into the linker hash because it is part of the hash namespace + man.hash.addOptional(self.base.options.major_subsystem_version); + man.hash.addOptional(self.base.options.minor_subsystem_version); + + // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. + _ = try man.hit(); + digest = man.final(); + var prev_digest_buf: [digest.len]u8 = undefined; + const prev_digest: []u8 = Cache.readSmallFile( + directory.handle, + id_symlink_basename, + &prev_digest_buf, + ) catch |err| blk: { + log.debug("COFF LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) }); + // Handle this as a cache miss. + break :blk prev_digest_buf[0..0]; + }; + if (mem.eql(u8, prev_digest, &digest)) { + log.debug("COFF LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)}); + // Hot diggity dog! The output binary is already there. + self.base.lock = man.toOwnedLock(); + return; + } + log.debug("COFF LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) }); + + // We are about to change the output file to be different, so we invalidate the build hash now. + directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) { + error.FileNotFound => {}, + else => |e| return e, + }; + } + + if (self.base.options.output_mode == .Obj) { + // LLD's COFF driver does not support the equivalent of `-r` so we do a simple file copy + // here. TODO: think carefully about how we can avoid this redundant operation when doing + // build-obj. See also the corresponding TODO in linkAsArchive. + const the_object_path = blk: { + if (self.base.options.objects.len != 0) + break :blk self.base.options.objects[0].path; + + if (comp.c_object_table.count() != 0) + break :blk comp.c_object_table.keys()[0].status.success.object_path; + + if (module_obj_path) |p| + break :blk p; + + // TODO I think this is unreachable. Audit this situation when solving the above TODO + // regarding eliding redundant object -> object transformations. + return error.NoObjectsToLink; + }; + // This can happen when using --enable-cache and using the stage1 backend. In this case + // we can skip the file copy. + if (!mem.eql(u8, the_object_path, full_out_path)) { + try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{}); + } + } else { + // Create an LLD command line and invoke it. + var argv = std.ArrayList([]const u8).init(self.base.allocator); + defer argv.deinit(); + // We will invoke ourselves as a child process to gain access to LLD. + // This is necessary because LLD does not behave properly as a library - + // it calls exit() and does not reset all global data between invocations. + try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, "lld-link" }); + + try argv.append("-ERRORLIMIT:0"); + try argv.append("-NOLOGO"); + if (!self.base.options.strip) { + try argv.append("-DEBUG"); + } + if (self.base.options.lto) { + switch (self.base.options.optimize_mode) { + .Debug => {}, + .ReleaseSmall => try argv.append("-OPT:lldlto=2"), + .ReleaseFast, .ReleaseSafe => try argv.append("-OPT:lldlto=3"), + } + } + if (self.base.options.output_mode == .Exe) { + const stack_size = self.base.options.stack_size_override orelse 16777216; + try argv.append(try allocPrint(arena, "-STACK:{d}", .{stack_size})); + } + if (self.base.options.image_base_override) |image_base| { + try argv.append(try std.fmt.allocPrint(arena, "-BASE:{d}", .{image_base})); + } + + if (target.cpu.arch == .i386) { + try argv.append("-MACHINE:X86"); + } else if (target.cpu.arch == .x86_64) { + try argv.append("-MACHINE:X64"); + } else if (target.cpu.arch.isARM()) { + if (target.cpu.arch.ptrBitWidth() == 32) { + try argv.append("-MACHINE:ARM"); + } else { + try argv.append("-MACHINE:ARM64"); + } + } + + for (self.base.options.force_undefined_symbols.keys()) |symbol| { + try argv.append(try allocPrint(arena, "-INCLUDE:{s}", .{symbol})); + } + + if (is_dyn_lib) { + try argv.append("-DLL"); + } + + if (self.base.options.entry) |entry| { + try argv.append(try allocPrint(arena, "-ENTRY:{s}", .{entry})); + } + + if (self.base.options.tsaware) { + try argv.append("-tsaware"); + } + if (self.base.options.nxcompat) { + try argv.append("-nxcompat"); + } + if (self.base.options.dynamicbase) { + try argv.append("-dynamicbase"); + } + + try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path})); + + if (self.base.options.implib_emit) |emit| { + const implib_out_path = try emit.directory.join(arena, &[_][]const u8{emit.sub_path}); + try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path})); + } + + if (self.base.options.link_libc) { + if (self.base.options.libc_installation) |libc_installation| { + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.crt_dir.?})); + + if (target.abi == .msvc) { + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.msvc_lib_dir.?})); + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.kernel32_lib_dir.?})); + } + } + } + + for (self.base.options.lib_dirs) |lib_dir| { + try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{lib_dir})); + } + + try argv.ensureUnusedCapacity(self.base.options.objects.len); + for (self.base.options.objects) |obj| { + if (obj.must_link) { + argv.appendAssumeCapacity(try allocPrint(arena, "-WHOLEARCHIVE:{s}", .{obj.path})); + } else { + argv.appendAssumeCapacity(obj.path); + } + } + + for (comp.c_object_table.keys()) |key| { + try argv.append(key.status.success.object_path); + } + + if (module_obj_path) |p| { + try argv.append(p); + } + + const resolved_subsystem: ?std.Target.SubSystem = blk: { + if (self.base.options.subsystem) |explicit| break :blk explicit; + switch (target.os.tag) { + .windows => { + if (self.base.options.module) |module| { + if (module.stage1_flags.have_dllmain_crt_startup or is_dyn_lib) + break :blk null; + if (module.stage1_flags.have_c_main or self.base.options.is_test or + module.stage1_flags.have_winmain_crt_startup or + module.stage1_flags.have_wwinmain_crt_startup) + { + break :blk .Console; + } + if (module.stage1_flags.have_winmain or module.stage1_flags.have_wwinmain) + break :blk .Windows; + } + }, + .uefi => break :blk .EfiApplication, + else => {}, + } + break :blk null; + }; + + const Mode = enum { uefi, win32 }; + const mode: Mode = mode: { + if (resolved_subsystem) |subsystem| { + const subsystem_suffix = ss: { + if (self.base.options.major_subsystem_version) |major| { + if (self.base.options.minor_subsystem_version) |minor| { + break :ss try allocPrint(arena, ",{d}.{d}", .{ major, minor }); + } else { + break :ss try allocPrint(arena, ",{d}", .{major}); + } + } + break :ss ""; + }; + + switch (subsystem) { + .Console => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:console{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + .EfiApplication => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_application{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .EfiBootServiceDriver => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_boot_service_driver{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .EfiRom => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_rom{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .EfiRuntimeDriver => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_runtime_driver{s}", .{ + subsystem_suffix, + })); + break :mode .uefi; + }, + .Native => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:native{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + .Posix => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:posix{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + .Windows => { + try argv.append(try allocPrint(arena, "-SUBSYSTEM:windows{s}", .{ + subsystem_suffix, + })); + break :mode .win32; + }, + } + } else if (target.os.tag == .uefi) { + break :mode .uefi; + } else { + break :mode .win32; + } + }; + + switch (mode) { + .uefi => try argv.appendSlice(&[_][]const u8{ + "-BASE:0", + "-ENTRY:EfiMain", + "-OPT:REF", + "-SAFESEH:NO", + "-MERGE:.rdata=.data", + "-ALIGN:32", + "-NODEFAULTLIB", + "-SECTION:.xdata,D", + }), + .win32 => { + if (link_in_crt) { + if (target.abi.isGnu()) { + try argv.append("-lldmingw"); + + if (target.cpu.arch == .i386) { + try argv.append("-ALTERNATENAME:__image_base__=___ImageBase"); + } else { + try argv.append("-ALTERNATENAME:__image_base__=__ImageBase"); + } + + if (is_dyn_lib) { + try argv.append(try comp.get_libc_crt_file(arena, "dllcrt2.obj")); + if (target.cpu.arch == .i386) { + try argv.append("-ALTERNATENAME:__DllMainCRTStartup@12=_DllMainCRTStartup@12"); + } else { + try argv.append("-ALTERNATENAME:_DllMainCRTStartup=DllMainCRTStartup"); + } + } else { + try argv.append(try comp.get_libc_crt_file(arena, "crt2.obj")); + } + + try argv.append(try comp.get_libc_crt_file(arena, "mingw32.lib")); + try argv.append(try comp.get_libc_crt_file(arena, "mingwex.lib")); + try argv.append(try comp.get_libc_crt_file(arena, "msvcrt-os.lib")); + + for (mingw.always_link_libs) |name| { + if (!self.base.options.system_libs.contains(name)) { + const lib_basename = try allocPrint(arena, "{s}.lib", .{name}); + try argv.append(try comp.get_libc_crt_file(arena, lib_basename)); + } + } + } else { + const lib_str = switch (self.base.options.link_mode) { + .Dynamic => "", + .Static => "lib", + }; + const d_str = switch (self.base.options.optimize_mode) { + .Debug => "d", + else => "", + }; + switch (self.base.options.link_mode) { + .Static => try argv.append(try allocPrint(arena, "libcmt{s}.lib", .{d_str})), + .Dynamic => try argv.append(try allocPrint(arena, "msvcrt{s}.lib", .{d_str})), + } + + try argv.append(try allocPrint(arena, "{s}vcruntime{s}.lib", .{ lib_str, d_str })); + try argv.append(try allocPrint(arena, "{s}ucrt{s}.lib", .{ lib_str, d_str })); + + //Visual C++ 2015 Conformance Changes + //https://msdn.microsoft.com/en-us/library/bb531344.aspx + try argv.append("legacy_stdio_definitions.lib"); + + // msvcrt depends on kernel32 and ntdll + try argv.append("kernel32.lib"); + try argv.append("ntdll.lib"); + } + } else { + try argv.append("-NODEFAULTLIB"); + if (!is_lib) { + if (self.base.options.module) |module| { + if (module.stage1_flags.have_winmain_crt_startup) { + try argv.append("-ENTRY:WinMainCRTStartup"); + } else { + try argv.append("-ENTRY:wWinMainCRTStartup"); + } + } else { + try argv.append("-ENTRY:wWinMainCRTStartup"); + } + } + } + }, + } + + // libc++ dep + if (self.base.options.link_libcpp) { + try argv.append(comp.libcxxabi_static_lib.?.full_object_path); + try argv.append(comp.libcxx_static_lib.?.full_object_path); + } + + // libunwind dep + if (self.base.options.link_libunwind) { + try argv.append(comp.libunwind_static_lib.?.full_object_path); + } + + if (is_exe_or_dyn_lib and !self.base.options.skip_linker_dependencies) { + if (!self.base.options.link_libc) { + if (comp.libc_static_lib) |lib| { + try argv.append(lib.full_object_path); + } + } + // MinGW doesn't provide libssp symbols + if (target.abi.isGnu()) { + if (comp.libssp_static_lib) |lib| { + try argv.append(lib.full_object_path); + } + } + // MSVC compiler_rt is missing some stuff, so we build it unconditionally but + // and rely on weak linkage to allow MSVC compiler_rt functions to override ours. + if (comp.compiler_rt_lib) |lib| { + try argv.append(lib.full_object_path); + } + } + + try argv.ensureUnusedCapacity(self.base.options.system_libs.count()); + for (self.base.options.system_libs.keys()) |key| { + const lib_basename = try allocPrint(arena, "{s}.lib", .{key}); + if (comp.crt_files.get(lib_basename)) |crt_file| { + argv.appendAssumeCapacity(crt_file.full_object_path); + continue; + } + if (try findLib(arena, lib_basename, self.base.options.lib_dirs)) |full_path| { + argv.appendAssumeCapacity(full_path); + continue; + } + if (target.abi.isGnu()) { + const fallback_name = try allocPrint(arena, "lib{s}.dll.a", .{key}); + if (try findLib(arena, fallback_name, self.base.options.lib_dirs)) |full_path| { + argv.appendAssumeCapacity(full_path); + continue; + } + } + log.err("DLL import library for -l{s} not found", .{key}); + return error.DllImportLibraryNotFound; + } + + if (self.base.options.verbose_link) { + // Skip over our own name so that the LLD linker name is the first argv item. + Compilation.dump_argv(argv.items[1..]); + } + + if (std.process.can_spawn) { + // If possible, we run LLD as a child process because it does not always + // behave properly as a library, unfortunately. + // https://github.com/ziglang/zig/issues/3825 + var child = std.ChildProcess.init(argv.items, arena); + if (comp.clang_passthrough_mode) { + child.stdin_behavior = .Inherit; + child.stdout_behavior = .Inherit; + child.stderr_behavior = .Inherit; + + const term = child.spawnAndWait() catch |err| { + log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); + return error.UnableToSpawnSelf; + }; + switch (term) { + .Exited => |code| { + if (code != 0) { + std.process.exit(code); + } + }, + else => std.process.abort(), + } + } else { + child.stdin_behavior = .Ignore; + child.stdout_behavior = .Ignore; + child.stderr_behavior = .Pipe; + + try child.spawn(); + + const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024); + + const term = child.wait() catch |err| { + log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); + return error.UnableToSpawnSelf; + }; + + switch (term) { + .Exited => |code| { + if (code != 0) { + // TODO parse this output and surface with the Compilation API rather than + // directly outputting to stderr here. + std.debug.print("{s}", .{stderr}); + return error.LLDReportedFailure; + } + }, + else => { + log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr }); + return error.LLDCrashed; + }, + } + + if (stderr.len != 0) { + log.warn("unexpected LLD stderr:\n{s}", .{stderr}); + } + } + } else { + const exit_code = try lldMain(arena, argv.items, false); + if (exit_code != 0) { + if (comp.clang_passthrough_mode) { + std.process.exit(exit_code); + } else { + return error.LLDReportedFailure; + } + } + } + } + + if (!self.base.options.disable_lld_caching) { + // Update the file with the digest. If it fails we can continue; it only + // means that the next invocation will have an unnecessary cache miss. + Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { + log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)}); + }; + // Again failure here only means an unnecessary cache miss. + man.writeManifest() catch |err| { + log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); + }; + // We hang on to this lock so that the output file path can be used without + // other processes clobbering it. + self.base.lock = man.toOwnedLock(); + } +} + +fn findLib(arena: Allocator, name: []const u8, lib_dirs: []const []const u8) !?[]const u8 { + for (lib_dirs) |lib_dir| { + const full_path = try fs.path.join(arena, &.{ lib_dir, name }); + fs.cwd().access(full_path, .{}) catch |err| switch (err) { + error.FileNotFound => continue, + else => |e| return e, + }; + return full_path; + } + return null; +} diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 764e4e71b2d38db4ac965f6835c0d263e7cbac45..af254410667bb0ac0ec39d2774ea1a56a2a17d19 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -26,7 +26,7 @@ const trace = @import("../tracy.zig").trace; const Air = @import("../Air.zig"); const Allocator = mem.Allocator; const Archive = @import("MachO/Archive.zig"); -const Atom = @import("MachO/Atom.zig"); +pub const Atom = @import("MachO/Atom.zig"); const Cache = @import("../Cache.zig"); const CodeSignature = @import("MachO/CodeSignature.zig"); const Compilation = @import("../Compilation.zig"); @@ -44,7 +44,6 @@ const Type = @import("../type.zig").Type; const TypedValue = @import("../TypedValue.zig"); const Value = @import("../value.zig").Value; -pub const TextBlock = Atom; pub const DebugSymbols = @import("MachO/DebugSymbols.zig"); pub const base_tag: File.Tag = File.Tag.macho; diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index c2aa562db5fdcf446e920099a63df8b1a689a595..a7dc6391c2c782d27eba421dc2f83b0a747ee1d6 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -18,7 +18,6 @@ const Dwarf = @import("../Dwarf.zig"); const MachO = @import("../MachO.zig"); const Module = @import("../../Module.zig"); const StringTable = @import("../strtab.zig").StringTable; -const TextBlock = MachO.TextBlock; const Type = @import("../../type.zig").Type; base: *MachO,