diff options
| author | Jakub Konka <kubkon@jakubkonka.com> | 2022-08-30 14:29:41 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-08-30 14:29:41 +0200 |
| commit | 7ef0c9d298d5645b4b6d1ffdfd34c69c04423ed2 (patch) | |
| tree | 12b54077e1533b1abcb8d9a9cd1222b24478f5fc /src/link | |
| parent | b64e4c5bf28286091ff97245e61f08b897e3eb5e (diff) | |
| parent | e57fbe8069e672483b0c9ae1fa28c00812596306 (diff) | |
| download | zig-7ef0c9d298d5645b4b6d1ffdfd34c69c04423ed2.tar.gz zig-7ef0c9d298d5645b4b6d1ffdfd34c69c04423ed2.zip | |
Merge pull request #12677 from ziglang/coff-linker
coff: initial rewrite of the COFF/PE linker
Diffstat (limited to 'src/link')
| -rw-r--r-- | src/link/Coff.zig | 2433 | ||||
| -rw-r--r-- | src/link/Coff/Atom.zig | 110 | ||||
| -rw-r--r-- | src/link/Coff/Object.zig | 12 | ||||
| -rw-r--r-- | src/link/Coff/lld.zig | 602 | ||||
| -rw-r--r-- | src/link/MachO.zig | 3 | ||||
| -rw-r--r-- | src/link/MachO/DebugSymbols.zig | 1 | ||||
| -rw-r--r-- | src/link/strtab.zig | 4 |
7 files changed, 1976 insertions, 1189 deletions
diff --git a/src/link/Coff.zig b/src/link/Coff.zig index e4f0227aec..36ddfc4e2a 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1,131 +1,179 @@ 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 fmt = std.fmt; +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 Object = @import("Coff/Object.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"); +const N_DATA_DIRS: u5 = 16; /// If this is not null, an object file is created by LLVM and linked with LLD afterwards. 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, - -/// 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, - -/// 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) = .{}, +ptr_width: PtrWidth, +page_size: u32, + +objects: std.ArrayListUnmanaged(Object) = .{}, + +sections: std.MultiArrayList(Section) = .{}, +data_directories: [N_DATA_DIRS]coff.ImageDataDirectory, + +text_section_index: ?u16 = null, +got_section_index: ?u16 = null, +rdata_section_index: ?u16 = null, +data_section_index: ?u16 = null, + +locals: std.ArrayListUnmanaged(coff.Symbol) = .{}, +globals: std.StringArrayHashMapUnmanaged(SymbolWithLoc) = .{}, + +locals_free_list: std.ArrayListUnmanaged(u32) = .{}, + +strtab: StringTable(.strtab) = .{}, +strtab_offset: ?u32 = null, + +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, +/// 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) = .{}, + +/// Table of unnamed constants associated with a parent `Decl`. +/// We store them here so that we can free the constants whenever the `Decl` +/// needs updating or is freed. +/// +/// For example, +/// +/// ```zig +/// const Foo = struct{ +/// a: u8, +/// }; +/// +/// pub fn main() void { +/// var foo = Foo{ .a = 1 }; +/// _ = foo; +/// } +/// ``` +/// +/// value assigned to label `foo` is an unnamed constant belonging/associated +/// with `Decl` `main`, and lives as long as that `Decl`. +unnamed_const_atoms: UnnamedConstTable = .{}, + +/// A table of relocations indexed by the owning them `TextBlock`. +/// Note that once we refactor `TextBlock`'s lifetime and ownership rules, +/// this will be a table indexed by index into the list of Atoms. +relocs: RelocTable = .{}, + +pub const Reloc = struct { + @"type": enum { + got, + direct, + }, + target: SymbolWithLoc, + offset: u32, + addend: u32, + pcrel: bool, + length: u2, + prev_vaddr: u32, +}; -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, +const RelocTable = std.AutoHashMapUnmanaged(*Atom, std.ArrayListUnmanaged(Reloc)); +const UnnamedConstTable = std.AutoHashMapUnmanaged(Module.Decl.Index, std.ArrayListUnmanaged(*Atom)); + +const default_file_alignment: u16 = 0x200; +const default_image_base_dll: u64 = 0x10000000; +const default_image_base_exe: u64 = 0x400000; +const default_size_of_stack_reserve: u32 = 0x1000000; +const default_size_of_stack_commit: u32 = 0x1000; +const default_size_of_heap_reserve: u32 = 0x100000; +const default_size_of_heap_commit: u32 = 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 SrcFn = void; -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; - } +pub const Export = struct { + sym_index: ?u32 = null, +}; - 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; - } +pub const SymbolWithLoc = struct { + // Index into the respective symbol table. + sym_index: u32, - /// 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; - } + // null means it's a synthetic global or Zig source. + file: ?u32 = null, }; -pub const SrcFn = void; +/// 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); pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Coff { assert(options.target.ofmt == .coff); @@ -144,257 +192,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option }); self.base.file = file; - // TODO Write object specific relocations, COFF symbol table, then enable object file output. - switch (options.output_mode) { - .Exe => {}, - .Obj => return error.TODOImplementWritingObjFiles, - .Lib => return error.TODOImplementWritingLibFiles, - } - - var coff_file_header_offset: u32 = 0; - if (options.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 (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; - const default_offset_table_size = file_alignment; - const default_size_of_code = 0; - - self.section_data_offset = mem.alignForwardGeneric(u32, self.section_table_offset + section_table_size, file_alignment); - const section_data_relative_virtual_address = mem.alignForwardGeneric(u32, self.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; - - // 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 characteristics: std.coff.CoffHeaderFlags = .{ - .DEBUG_STRIPPED = 1, // TODO remove debug info stripped flag when necessary - .RELOCS_STRIPPED = 1, - }; - if (options.output_mode == .Exe) { - characteristics.EXECUTABLE_IMAGE = 1; - } - switch (self.ptr_width) { - .p32 => characteristics.@"32BIT_MACHINE" = 1, - .p64 => characteristics.LARGE_ADDRESS_AWARE = 1, - } - mem.writeIntLittle(u16, hdr_data[index..][0..2], @bitCast(u16, characteristics)); - index += 2; - - assert(index == 20); - try self.base.file.?.pwriteAll(hdr_data[0..index], coff_file_header_offset); - - if (options.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 (options.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 (options.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); + try self.populateMissingMetadata(); return self; } @@ -405,6 +203,9 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff { 33...64 => .p64, else => return error.UnsupportedCOFFArchitecture, }; + const page_size: u32 = switch (options.target.cpu.arch) { + else => 0x1000, + }; const self = try gpa.create(Coff); errdefer gpa.destroy(self); self.* = .{ @@ -415,6 +216,8 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff { .file = null, }, .ptr_width = ptr_width, + .page_size = page_size, + .data_directories = comptime mem.zeroes([N_DATA_DIRS]coff.ImageDataDirectory), }; const use_llvm = build_options.have_llvm and options.use_llvm; @@ -425,245 +228,530 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff { return self; } -pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void { - if (self.llvm_object) |_| return; +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); + } - try self.offset_table.ensureUnusedCapacity(self.base.allocator, 1); + for (self.objects.items) |*object| { + object.deinit(gpa); + } + self.objects.deinit(gpa); - 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(); + 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); - 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.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); + + { + var it = self.unnamed_const_atoms.valueIterator(); + while (it.next()) |atoms| { + atoms.deinit(gpa); } + self.unnamed_const_atoms.deinit(gpa); } - self.offset_table.items[decl.link.coff.offset_table_index] = 0; + { + var it = self.relocs.valueIterator(); + while (it.next()) |relocs| { + relocs.deinit(gpa); + } + self.relocs.deinit(gpa); + } } -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 populateMissingMetadata(self: *Coff) !void { + assert(self.llvm_object == null); + const gpa = self.base.allocator; + + if (self.text_section_index == null) { + self.text_section_index = @intCast(u16, self.sections.slice().len); + const file_size = @intCast(u32, self.base.options.program_code_size_hint); + const off = self.findFreeSpace(file_size, self.page_size); // TODO we are over-aligning in file; we should track both in file and in memory pointers + log.debug("found .text free space 0x{x} to 0x{x}", .{ off, off + file_size }); + var header = coff.SectionHeader{ + .name = undefined, + .virtual_size = file_size, + .virtual_address = off, + .size_of_raw_data = file_size, + .pointer_to_raw_data = off, + .pointer_to_relocations = 0, + .pointer_to_linenumbers = 0, + .number_of_relocations = 0, + .number_of_linenumbers = 0, + .flags = .{ + .CNT_CODE = 1, + .MEM_EXECUTE = 1, + .MEM_READ = 1, + }, + }; + try self.setSectionName(&header, ".text"); + try self.sections.append(gpa, .{ .header = header }); + } + + if (self.got_section_index == null) { + self.got_section_index = @intCast(u16, self.sections.slice().len); + const file_size = @intCast(u32, self.base.options.symbol_count_hint); + const off = self.findFreeSpace(file_size, self.page_size); + log.debug("found .got free space 0x{x} to 0x{x}", .{ off, off + file_size }); + var header = coff.SectionHeader{ + .name = undefined, + .virtual_size = file_size, + .virtual_address = off, + .size_of_raw_data = file_size, + .pointer_to_raw_data = off, + .pointer_to_relocations = 0, + .pointer_to_linenumbers = 0, + .number_of_relocations = 0, + .number_of_linenumbers = 0, + .flags = .{ + .CNT_INITIALIZED_DATA = 1, + .MEM_READ = 1, + }, + }; + try self.setSectionName(&header, ".got"); + try self.sections.append(gpa, .{ .header = header }); + } + + if (self.rdata_section_index == null) { + self.rdata_section_index = @intCast(u16, self.sections.slice().len); + const file_size: u32 = 1024; + const off = self.findFreeSpace(file_size, self.page_size); + log.debug("found .rdata free space 0x{x} to 0x{x}", .{ off, off + file_size }); + var header = coff.SectionHeader{ + .name = undefined, + .virtual_size = file_size, + .virtual_address = off, + .size_of_raw_data = file_size, + .pointer_to_raw_data = off, + .pointer_to_relocations = 0, + .pointer_to_linenumbers = 0, + .number_of_relocations = 0, + .number_of_linenumbers = 0, + .flags = .{ + .CNT_INITIALIZED_DATA = 1, + .MEM_READ = 1, + }, + }; + try self.setSectionName(&header, ".rdata"); + try self.sections.append(gpa, .{ .header = header }); + } + + if (self.data_section_index == null) { + self.data_section_index = @intCast(u16, self.sections.slice().len); + const file_size: u32 = 1024; + const off = self.findFreeSpace(file_size, self.page_size); + log.debug("found .data free space 0x{x} to 0x{x}", .{ off, off + file_size }); + var header = coff.SectionHeader{ + .name = undefined, + .virtual_size = file_size, + .virtual_address = off, + .size_of_raw_data = file_size, + .pointer_to_raw_data = off, + .pointer_to_relocations = 0, + .pointer_to_linenumbers = 0, + .number_of_relocations = 0, + .number_of_linenumbers = 0, + .flags = .{ + .CNT_INITIALIZED_DATA = 1, + .MEM_READ = 1, + .MEM_WRITE = 1, + }, + }; + try self.setSectionName(&header, ".data"); + try self.sections.append(gpa, .{ .header = header }); + } - // We use these to indicate our intention to update metadata, placing the new block, + if (self.strtab_offset == null) { + try self.strtab.buffer.append(gpa, 0); + self.strtab_offset = self.findFreeSpace(@intCast(u32, self.strtab.len()), 1); + log.debug("found strtab free space 0x{x} to 0x{x}", .{ self.strtab_offset.?, self.strtab_offset.? + self.strtab.len() }); + } + + // Index 0 is always a null symbol. + try self.locals.append(gpa, .{ + .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, + }); + + { + // We need to find out what the max file offset is according to section headers. + // Otherwise, we may end up with an COFF binary with file size not matching the final section's + // offset + it's filesize. + // TODO I don't like this here one bit + var max_file_offset: u64 = 0; + for (self.sections.items(.header)) |header| { + if (header.pointer_to_raw_data + header.size_of_raw_data > max_file_offset) { + max_file_offset = header.pointer_to_raw_data + header.size_of_raw_data; + } + } + try self.base.file.?.pwriteAll(&[_]u8{0}, max_file_offset); + } +} + +pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void { + if (self.llvm_object) |_| return; + const decl = self.base.options.module.?.declPtr(decl_index); + 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 allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32) !u32 { + const tracy = trace(@src()); + defer tracy.end(); + + const sect_id = @enumToInt(atom.getSymbol(self).section_number) - 1; + 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(u32, sym.value, ideal_capacity) catch ideal_capacity; + const capacity_end_vaddr = sym.value + capacity; + const new_start_vaddr_unaligned = capacity_end_vaddr - new_atom_ideal_capacity; + const new_start_vaddr = mem.alignBackwardGeneric(u32, 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.value + ideal_capacity; + const new_start_vaddr = mem.alignForwardGeneric(u32, 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(u32, header.virtual_address, 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); - } - - self.text_section_size = needed_size; - self.text_section_size_dirty = true; + const expand_section = atom_placement == null or atom_placement.?.next == null; + if (expand_section) { + const sect_capacity = self.allocatedSize(header.pointer_to_raw_data); + const needed_size: u32 = (vaddr + new_atom_size) - header.virtual_address; + if (needed_size > sect_capacity) { + @panic("TODO move section"); } - self.last_text_block = text_block; + maybe_last_atom.* = atom; + // header.virtual_size = needed_size; + // header.size_of_raw_data = mem.alignForwardGeneric(u32, needed_size, default_file_alignment); } - text_block.text_offset = @intCast(u32, vaddr - self.text_section_virtual_address); - text_block.size = @intCast(u32, new_block_size); - // 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 (header.getAlignment().? < alignment) { + // header.setAlignment(alignment); + // } + atom.size = new_atom_size; + atom.alignment = alignment; + + 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); + const index: u32 = blk: { + 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); + } + break :blk index; + } else { + log.debug(" (allocating GOT entry at index {d})", .{self.got_entries.keys().len}); + const index = @intCast(u32, self.got_entries.keys().len); + self.got_entries.putAssumeCapacityNoClobber(target, 0); + break :blk index; + } + }; + self.got_entries.keys()[index] = target; + return index; +} + +fn createGotAtom(self: *Coff, target: SymbolWithLoc) !*Atom { + const gpa = self.base.allocator; + const atom = try gpa.create(Atom); + errdefer gpa.destroy(atom); + atom.* = Atom.empty; + atom.sym_index = try self.allocateSymbol(); + atom.size = @sizeOf(u64); + atom.alignment = @alignOf(u64); + + try self.managed_atoms.append(gpa, atom); + try self.atom_by_index_table.putNoClobber(gpa, atom.sym_index, atom); + self.got_entries.getPtr(target).?.* = atom.sym_index; + + const sym = atom.getSymbolPtr(self); + sym.section_number = @intToEnum(coff.SectionNumber, self.got_section_index.? + 1); + sym.value = try self.allocateAtom(atom, atom.size, atom.alignment); + + log.debug("allocated GOT atom at 0x{x}", .{sym.value}); + + try atom.addRelocation(self, .{ + .@"type" = .direct, + .target = target, + .offset = 0, + .addend = 0, + .pcrel = false, + .length = 3, + .prev_vaddr = sym.value, + }); + + return atom; +} + +fn growAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32) !u32 { + const sym = atom.getSymbol(self); + const align_ok = mem.alignBackwardGeneric(u32, 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); +} + +fn shrinkAtom(self: *Coff, atom: *Atom, new_block_size: u32) void { + _ = self; + _ = atom; + _ = new_block_size; + // 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 writeAtom(self: *Coff, atom: *Atom, code: []const u8) !void { + const sym = atom.getSymbol(self); + const section = self.sections.get(@enumToInt(sym.section_number) - 1); + const file_offset = section.header.pointer_to_raw_data + sym.value - section.header.virtual_address; + log.debug("writing atom for symbol {s} at file offset 0x{x}", .{ atom.getName(self), file_offset }); + try self.base.file.?.pwriteAll(code, file_offset); + try self.resolveRelocs(atom); +} + +fn writeGotAtom(self: *Coff, atom: *Atom) !void { + switch (self.ptr_width) { + .p32 => { + var buffer: [@sizeOf(u32)]u8 = [_]u8{0} ** @sizeOf(u32); + try self.writeAtom(atom, &buffer); + }, + .p64 => { + var buffer: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64); + try self.writeAtom(atom, &buffer); + }, } } -fn freeTextBlock(self: *Coff, text_block: *TextBlock) void { +fn resolveRelocs(self: *Coff, atom: *Atom) !void { + const relocs = self.relocs.get(atom) orelse return; + const source_sym = atom.getSymbol(self); + const source_section = self.sections.get(@enumToInt(source_sym.section_number) - 1).header; + const file_offset = source_section.pointer_to_raw_data + source_sym.value - source_section.virtual_address; + + log.debug("relocating '{s}'", .{atom.getName(self)}); + + for (relocs.items) |*reloc| { + const target_vaddr = switch (reloc.@"type") { + .got => blk: { + const got_atom = self.getGotAtomForSymbol(reloc.target) orelse continue; + break :blk got_atom.getSymbol(self).value; + }, + .direct => self.getSymbol(reloc.target).value, + }; + const target_vaddr_with_addend = target_vaddr + reloc.addend; + + if (target_vaddr_with_addend == reloc.prev_vaddr) continue; + + log.debug(" ({x}: [() => 0x{x} ({s})) ({s})", .{ + reloc.offset, + target_vaddr_with_addend, + self.getSymbolName(reloc.target), + @tagName(reloc.@"type"), + }); + + if (reloc.pcrel) { + const source_vaddr = source_sym.value + reloc.offset; + const disp = target_vaddr_with_addend - source_vaddr - 4; + try self.base.file.?.pwriteAll(mem.asBytes(&@intCast(u32, disp)), file_offset + reloc.offset); + return; + } + + switch (self.ptr_width) { + .p32 => try self.base.file.?.pwriteAll( + mem.asBytes(&@intCast(u32, target_vaddr_with_addend + default_image_base_exe)), + file_offset + reloc.offset, + ), + .p64 => switch (reloc.length) { + 2 => try self.base.file.?.pwriteAll( + mem.asBytes(&@truncate(u32, target_vaddr_with_addend + default_image_base_exe)), + file_offset + reloc.offset, + ), + 3 => try self.base.file.?.pwriteAll( + mem.asBytes(&(target_vaddr_with_addend + default_image_base_exe)), + file_offset + reloc.offset, + ), + else => unreachable, + }, + } + + reloc.prev_vaddr = target_vaddr_with_addend; + } +} + +fn freeAtom(self: *Coff, atom: *Atom) void { + log.debug("freeAtom {*}", .{atom}); + + const sym = atom.getSymbol(self); + const sect_id = @enumToInt(sym.section_number) - 1; + 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; } } @@ -702,15 +790,18 @@ pub fn updateFunc(self: *Coff, module: *Module, func: *Module.Fn, air: Air, live }, }; - return self.finishUpdateDecl(module, func.owner_decl, code); + try self.updateDeclCode(decl_index, code, .FUNCTION); + + // 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 { @@ -728,16 +819,20 @@ pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) ! if (decl.val.tag() == .extern_fn) { return; // TODO Should we do more when front-end analyzed extern decl? } - - // TODO COFF/PE debug information - // TODO Implement exports + if (decl.val.castTag(.variable)) |payload| { + const variable = payload.data; + if (variable.is_extern) { + return; // TODO Should we do more when front-end analyzed extern decl? + } + } var code_buffer = std.ArrayList(u8).init(self.base.allocator); defer code_buffer.deinit(); + const decl_val = if (decl.val.castTag(.variable)) |payload| payload.data.init else decl.val; const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), .{ .ty = decl.ty, - .val = decl.val, + .val = decl_val, }, &code_buffer, .none, .{ .parent_atom_index = 0, }); @@ -751,47 +846,98 @@ pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) ! }, }; - return self.finishUpdateDecl(module, decl_index, code); + try self.updateDeclCode(decl_index, code, .NULL); + + // 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 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); +fn getDeclOutputSection(self: *Coff, decl: *Module.Decl) u16 { + const ty = decl.ty; + const zig_ty = ty.zigTypeTag(); + const val = decl.val; + const index: u16 = blk: { + if (val.isUndefDeep()) { + // TODO in release-fast and release-small, we should put undef in .bss + break :blk self.data_section_index.?; + } + + switch (zig_ty) { + .Fn => break :blk self.text_section_index.?, + else => { + if (val.castTag(.variable)) |_| { + break :blk self.data_section_index.?; + } + break :blk self.rdata_section_index.?; + }, + } + }; + return index; +} + +fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8, complex_type: coff.ComplexType) !void { + const gpa = self.base.allocator; + const mod = self.base.options.module.?; + const decl = mod.declPtr(decl_index); + + const decl_name = try decl.getFullyQualifiedName(mod); + defer gpa.free(decl_name); + + log.debug("updateDeclCode {s}{*}", .{ decl_name, decl }); + const required_alignment = decl.getAlignment(self.base.options.target); + + const decl_ptr = self.decls.getPtr(decl_index).?; + if (decl_ptr.* == null) { + decl_ptr.* = self.getDeclOutputSection(decl); + } + const sect_index = decl_ptr.*.?; + + const code_len = @intCast(u32, code.len); + const atom = &decl.link.coff; + assert(atom.sym_index != 0); // Caller forgot to allocateDeclIndexes() + if (atom.size != 0) { + const sym = atom.getSymbolPtr(self); + try self.setSymbolName(sym, decl_name); + sym.section_number = @intToEnum(coff.SectionNumber, sect_index + 1); + sym.@"type" = .{ .complex_type = complex_type, .base_type = .NULL }; + + const capacity = atom.capacity(self); + const need_realloc = code.len > capacity or !mem.isAlignedGeneric(u64, sym.value, 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); + const vaddr = try self.growAtom(atom, code_len, required_alignment); + log.debug("growing {s} from 0x{x} to 0x{x}", .{ decl_name, sym.value, vaddr }); + log.debug(" (required alignment 0x{x}", .{required_alignment}); + + if (vaddr != sym.value) { + sym.value = vaddr; + log.debug(" (updating GOT entry)", .{}); + const got_atom = self.getGotAtomForSymbol(.{ .sym_index = atom.sym_index, .file = null }).?; + try self.writeGotAtom(got_atom); } - } else if (code.len < curr_size) { - self.shrinkTextBlock(&decl.link.coff, code.len); + } else if (code_len < atom.size) { + self.shrinkAtom(atom, code_len); } + atom.size = 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); + const sym = atom.getSymbolPtr(self); + try self.setSymbolName(sym, decl_name); + sym.section_number = @intToEnum(coff.SectionNumber, sect_index + 1); + sym.@"type" = .{ .complex_type = complex_type, .base_type = .NULL }; + + const vaddr = try self.allocateAtom(atom, code_len, required_alignment); + errdefer self.freeAtom(atom); + log.debug("allocated atom for {s} at 0x{x}", .{ decl_name, vaddr }); + atom.size = code_len; + sym.value = vaddr; + + const got_target = SymbolWithLoc{ .sym_index = atom.sym_index, .file = null }; + _ = try self.allocateGotEntry(got_target); + const got_atom = try self.createGotAtom(got_target); + try self.writeGotAtom(got_atom); } - // 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); - - // 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); + try self.writeAtom(atom, code); } pub fn freeDecl(self: *Coff, decl_index: Module.Decl.Index) void { @@ -802,9 +948,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) |_| { + self.freeAtom(&decl.link.coff); + } + // 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( @@ -817,64 +985,157 @@ pub fn updateDeclExports( @panic("Attempted to compile for object format that was disabled by build configuration"); } - // Even in the case of LLVM, we need to notice certain exported symbols in order to - // detect the default subsystem. - for (exports) |exp| { - const exported_decl = module.declPtr(exp.exported_decl); - if (exported_decl.getFunction() == null) continue; - const winapi_cc = switch (self.base.options.target.cpu.arch) { - .i386 => std.builtin.CallingConvention.Stdcall, - else => std.builtin.CallingConvention.C, - }; - const decl_cc = exported_decl.ty.fnCallingConvention(); - if (decl_cc == .C and mem.eql(u8, exp.options.name, "main") and - self.base.options.link_libc) - { - module.stage1_flags.have_c_main = true; - } else if (decl_cc == winapi_cc and self.base.options.target.os.tag == .windows) { - if (mem.eql(u8, exp.options.name, "WinMain")) { - module.stage1_flags.have_winmain = true; - } else if (mem.eql(u8, exp.options.name, "wWinMain")) { - module.stage1_flags.have_wwinmain = true; - } else if (mem.eql(u8, exp.options.name, "WinMainCRTStartup")) { - module.stage1_flags.have_winmain_crt_startup = true; - } else if (mem.eql(u8, exp.options.name, "wWinMainCRTStartup")) { - module.stage1_flags.have_wwinmain_crt_startup = true; - } else if (mem.eql(u8, exp.options.name, "DllMainCRTStartup")) { - module.stage1_flags.have_dllmain_crt_startup = true; + if (build_options.have_llvm) { + // Even in the case of LLVM, we need to notice certain exported symbols in order to + // detect the default subsystem. + for (exports) |exp| { + const exported_decl = module.declPtr(exp.exported_decl); + if (exported_decl.getFunction() == null) continue; + const winapi_cc = switch (self.base.options.target.cpu.arch) { + .i386 => std.builtin.CallingConvention.Stdcall, + else => std.builtin.CallingConvention.C, + }; + const decl_cc = exported_decl.ty.fnCallingConvention(); + if (decl_cc == .C and mem.eql(u8, exp.options.name, "main") and + self.base.options.link_libc) + { + module.stage1_flags.have_c_main = true; + } else if (decl_cc == winapi_cc and self.base.options.target.os.tag == .windows) { + if (mem.eql(u8, exp.options.name, "WinMain")) { + module.stage1_flags.have_winmain = true; + } else if (mem.eql(u8, exp.options.name, "wWinMain")) { + module.stage1_flags.have_wwinmain = true; + } else if (mem.eql(u8, exp.options.name, "WinMainCRTStartup")) { + module.stage1_flags.have_winmain_crt_startup = true; + } else if (mem.eql(u8, exp.options.name, "wWinMainCRTStartup")) { + module.stage1_flags.have_wwinmain_crt_startup = true; + } else if (mem.eql(u8, exp.options.name, "DllMainCRTStartup")) { + module.stage1_flags.have_dllmain_crt_startup = true; + } } } - } - if (build_options.have_llvm) { if (self.llvm_object) |llvm_object| return llvm_object.updateDeclExports(module, decl_index, exports); } + const tracy = trace(@src()); + defer tracy.end(); + + const gpa = self.base.allocator; + const decl = module.declPtr(decl_index); + const atom = &decl.link.coff; + if (atom.sym_index == 0) return; + const decl_sym = atom.getSymbol(self); + for (exports) |exp| { + log.debug("adding new export '{s}'", .{exp.options.name}); + 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( + try module.failed_exports.putNoClobber( + module.gpa, exp, - try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: ExportOptions.section", .{}), + try Module.ErrorMsg.create( + gpa, + decl.srcLoc(), + "Unimplemented: ExportOptions.section", + .{}, + ), ); continue; } } - if (mem.eql(u8, exp.options.name, "_start")) { - self.entry_addr = decl.link.coff.getVAddr(self.*) - default_image_base; - } else { - try module.failed_exports.ensureUnusedCapacity(module.gpa, 1); - module.failed_exports.putAssumeCapacityNoClobber( + + if (exp.options.linkage == .LinkOnce) { + try module.failed_exports.putNoClobber( + module.gpa, exp, - try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: Exports other than '_start'", .{}), + try Module.ErrorMsg.create( + gpa, + decl.srcLoc(), + "Unimplemented: GlobalLinkage.LinkOnce", + .{}, + ), ); continue; } + + const sym_index = exp.link.coff.sym_index orelse blk: { + const sym_index = try self.allocateSymbol(); + exp.link.coff.sym_index = sym_index; + break :blk sym_index; + }; + const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = null }; + const sym = self.getSymbolPtr(sym_loc); + try self.setSymbolName(sym, exp.options.name); + sym.value = decl_sym.value; + sym.section_number = @intToEnum(coff.SectionNumber, self.text_section_index.? + 1); + sym.@"type" = .{ .complex_type = .FUNCTION, .base_type = .NULL }; + + switch (exp.options.linkage) { + .Strong => { + sym.storage_class = .EXTERNAL; + }, + .Internal => @panic("TODO Internal"), + .Weak => @panic("TODO WeakExternal"), + else => unreachable, + } + + try self.resolveGlobalSymbol(sym_loc); + } +} + +pub fn deleteExport(self: *Coff, exp: Export) void { + if (self.llvm_object) |_| return; + const sym_index = exp.sym_index orelse return; + + const gpa = self.base.allocator; + + const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = null }; + const sym = self.getSymbolPtr(sym_loc); + const sym_name = self.getSymbolName(sym_loc); + log.debug("deleting export '{s}'", .{sym_name}); + assert(sym.storage_class == .EXTERNAL); + sym.* = .{ + .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, + }; + self.locals_free_list.append(gpa, sym_index) catch {}; + + if (self.globals.get(sym_name)) |global| blk: { + if (global.sym_index != sym_index) break :blk; + if (global.file != null) break :blk; + const kv = self.globals.fetchSwapRemove(sym_name); + gpa.free(kv.?.key); } } +fn resolveGlobalSymbol(self: *Coff, current: SymbolWithLoc) !void { + const gpa = self.base.allocator; + const sym = self.getSymbol(current); + _ = sym; + const sym_name = self.getSymbolName(current); + + const name = try gpa.dupe(u8, sym_name); + const global_index = @intCast(u32, self.globals.values().len); + _ = global_index; + const gop = try self.globals.getOrPut(gpa, name); + defer if (gop.found_existing) gpa.free(name); + + if (!gop.found_existing) { + gop.value_ptr.* = current; + // TODO undef + tentative + return; + } + + log.debug("TODO finish resolveGlobalSymbols implementation", .{}); +} + pub fn flush(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void { if (self.base.options.emit == null) { if (build_options.have_llvm) { @@ -884,14 +1145,13 @@ pub fn flush(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !vo } return; } - if (build_options.have_llvm and self.base.options.use_lld) { - return self.linkWithLLD(comp, prog_node); - } else { - switch (self.base.options.effectiveOutputMode()) { - .Exe, .Obj => {}, - .Lib => return error.TODOImplementWritingLibFiles, - } - return self.flushModule(comp, prog_node); + const use_lld = build_options.have_llvm and self.base.options.use_lld; + if (use_lld) { + return lld.linkWithLLD(self, comp, prog_node); + } + switch (self.base.options.output_mode) { + .Exe, .Obj => return self.flushModule(comp, prog_node), + .Lib => return error.TODOImplementWritingLibFiles, } } @@ -909,648 +1169,449 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod sub_prog_node.activate(); defer sub_prog_node.end(); - 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 (build_options.enable_logging) { + self.logSymtab(); } - 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; + { + var it = self.relocs.keyIterator(); + while (it.next()) |atom| { + try self.resolveRelocs(atom.*); + } } + if (self.getEntryPoint()) |entry_sym_loc| { + self.entry_addr = self.getSymbol(entry_sym_loc).value; + } + + try self.writeStrtab(); + try self.writeDataDirectoriesHeaders(); + try self.writeSectionHeaders(); + 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); - } + try self.writeHeader(); } } -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); +pub fn getDeclVAddr( + self: *Coff, + decl_index: Module.Decl.Index, + reloc_info: link.File.RelocInfo, +) !u64 { + _ = self; + _ = decl_index; + _ = reloc_info; + @panic("TODO getDeclVAddr"); +} - 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; +pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !void { + _ = self; + _ = module; + _ = decl; + log.debug("TODO implement updateDeclLineNumber", .{}); +} - var sub_prog_node = prog_node.start("LLD Link", 0); - sub_prog_node.activate(); - sub_prog_node.context.refresh(); - defer sub_prog_node.end(); +fn writeStrtab(self: *Coff) !void { + const allocated_size = self.allocatedSize(self.strtab_offset.?); + const needed_size = @intCast(u32, self.strtab.len()); - 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; + if (needed_size > allocated_size) { + self.strtab_offset = null; + self.strtab_offset = @intCast(u32, self.findFreeSpace(needed_size, 1)); + } - // See link/Elf.zig for comments on how this mechanism works. - const id_symlink_basename = "lld.id"; + log.debug("writing strtab from 0x{x} to 0x{x}", .{ self.strtab_offset.?, self.strtab_offset.? + needed_size }); + try self.base.file.?.pwriteAll(self.strtab.buffer.items, self.strtab_offset.?); +} - var man: Cache.Manifest = undefined; - defer if (!self.base.options.disable_lld_caching) man.deinit(); +fn writeSectionHeaders(self: *Coff) !void { + const offset = self.getSectionHeadersOffset(); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items(.header)), offset); +} - var digest: [Cache.hex_digest_len]u8 = undefined; +fn writeDataDirectoriesHeaders(self: *Coff) !void { + const offset = self.getDataDirectoryHeadersOffset(); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(&self.data_directories), offset); +} - if (!self.base.options.disable_lld_caching) { - man = comp.cache_parent.obtain(); - self.base.releaseLock(); +fn writeHeader(self: *Coff) !void { + const gpa = self.base.allocator; + var buffer = std.ArrayList(u8).init(gpa); + defer buffer.deinit(); + const writer = buffer.writer(); - comptime assert(Compilation.link_hash_implementation_version == 7); + try buffer.ensureTotalCapacity(self.getSizeOfHeaders()); + writer.writeAll(msdos_stub) catch unreachable; + mem.writeIntLittle(u32, buffer.items[0x3c..][0..4], msdos_stub.len); - 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, - }; + writer.writeAll("PE\x00\x00") catch unreachable; + var flags = coff.CoffHeaderFlags{ + .EXECUTABLE_IMAGE = 1, + .DEBUG_STRIPPED = 1, // TODO + }; + switch (self.ptr_width) { + .p32 => flags.@"32BIT_MACHINE" = 1, + .p64 => flags.LARGE_ADDRESS_AWARE = 1, + } + if (self.base.options.output_mode == .Lib and self.base.options.link_mode == .Dynamic) { + flags.DLL = 1; } - 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; + const timestamp = std.time.timestamp(); + const size_of_optional_header = @intCast(u16, self.getOptionalHeaderSize() + self.getDataDirectoryHeadersSize()); + var coff_header = coff.CoffHeader{ + .machine = coff.MachineType.fromTargetCpuArch(self.base.options.target.cpu.arch), + .number_of_sections = @intCast(u16, self.sections.slice().len), // TODO what if we prune a section + .time_date_stamp = @truncate(u32, @bitCast(u64, timestamp)), + .pointer_to_symbol_table = self.strtab_offset orelse 0, + .number_of_symbols = 0, + .size_of_optional_header = size_of_optional_header, + .flags = flags, + }; - if (comp.c_object_table.count() != 0) - break :blk comp.c_object_table.keys()[0].status.success.object_path; + writer.writeAll(mem.asBytes(&coff_header)) catch unreachable; - if (module_obj_path) |p| - break :blk p; + const dll_flags: coff.DllFlags = .{ + .HIGH_ENTROPY_VA = 0, //@boolToInt(self.base.options.pie), + .DYNAMIC_BASE = 0, + .TERMINAL_SERVER_AWARE = 1, // We are not a legacy app + .NX_COMPAT = 1, // We are compatible with Data Execution Prevention + }; + const subsystem: coff.Subsystem = .WINDOWS_CUI; + const size_of_image: u32 = self.getSizeOfImage(); + const size_of_headers: u32 = mem.alignForwardGeneric(u32, self.getSizeOfHeaders(), default_file_alignment); + const image_base = self.base.options.image_base_override orelse switch (self.base.options.output_mode) { + .Exe => default_image_base_exe, + .Lib => default_image_base_dll, + else => unreachable, + }; - // 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"), - } + const base_of_code = self.sections.get(self.text_section_index.?).header.virtual_address; + const base_of_data = self.sections.get(self.data_section_index.?).header.virtual_address; + + var size_of_code: u32 = 0; + var size_of_initialized_data: u32 = 0; + var size_of_uninitialized_data: u32 = 0; + for (self.sections.items(.header)) |header| { + if (header.flags.CNT_CODE == 1) { + size_of_code += header.size_of_raw_data; } - 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 (header.flags.CNT_INITIALIZED_DATA == 1) { + size_of_initialized_data += header.size_of_raw_data; } - if (self.base.options.image_base_override) |image_base| { - try argv.append(try std.fmt.allocPrint(arena, "-BASE:{d}", .{image_base})); + if (header.flags.CNT_UNINITIALIZED_DATA == 1) { + size_of_uninitialized_data += header.size_of_raw_data; } + } - 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"); - } - } + switch (self.ptr_width) { + .p32 => { + var opt_header = coff.OptionalHeaderPE32{ + .magic = coff.IMAGE_NT_OPTIONAL_HDR32_MAGIC, + .major_linker_version = 0, + .minor_linker_version = 0, + .size_of_code = size_of_code, + .size_of_initialized_data = size_of_initialized_data, + .size_of_uninitialized_data = size_of_uninitialized_data, + .address_of_entry_point = self.entry_addr orelse 0, + .base_of_code = base_of_code, + .base_of_data = base_of_data, + .image_base = @intCast(u32, image_base), + .section_alignment = self.page_size, + .file_alignment = default_file_alignment, + .major_operating_system_version = 6, + .minor_operating_system_version = 0, + .major_image_version = 0, + .minor_image_version = 0, + .major_subsystem_version = 6, + .minor_subsystem_version = 0, + .win32_version_value = 0, + .size_of_image = size_of_image, + .size_of_headers = size_of_headers, + .checksum = 0, + .subsystem = subsystem, + .dll_flags = dll_flags, + .size_of_stack_reserve = default_size_of_stack_reserve, + .size_of_stack_commit = default_size_of_stack_commit, + .size_of_heap_reserve = default_size_of_heap_reserve, + .size_of_heap_commit = default_size_of_heap_commit, + .loader_flags = 0, + .number_of_rva_and_sizes = @intCast(u32, self.data_directories.len), + }; + writer.writeAll(mem.asBytes(&opt_header)) catch unreachable; + }, + .p64 => { + var opt_header = coff.OptionalHeaderPE64{ + .magic = coff.IMAGE_NT_OPTIONAL_HDR64_MAGIC, + .major_linker_version = 0, + .minor_linker_version = 0, + .size_of_code = size_of_code, + .size_of_initialized_data = size_of_initialized_data, + .size_of_uninitialized_data = size_of_uninitialized_data, + .address_of_entry_point = self.entry_addr orelse 0, + .base_of_code = base_of_code, + .image_base = image_base, + .section_alignment = self.page_size, + .file_alignment = default_file_alignment, + .major_operating_system_version = 6, + .minor_operating_system_version = 0, + .major_image_version = 0, + .minor_image_version = 0, + .major_subsystem_version = 6, + .minor_subsystem_version = 0, + .win32_version_value = 0, + .size_of_image = size_of_image, + .size_of_headers = size_of_headers, + .checksum = 0, + .subsystem = subsystem, + .dll_flags = dll_flags, + .size_of_stack_reserve = default_size_of_stack_reserve, + .size_of_stack_commit = default_size_of_stack_commit, + .size_of_heap_reserve = default_size_of_heap_reserve, + .size_of_heap_commit = default_size_of_heap_commit, + .loader_flags = 0, + .number_of_rva_and_sizes = @intCast(u32, self.data_directories.len), + }; + writer.writeAll(mem.asBytes(&opt_header)) catch unreachable; + }, + } - for (self.base.options.force_undefined_symbols.keys()) |symbol| { - try argv.append(try allocPrint(arena, "-INCLUDE:{s}", .{symbol})); - } + try self.base.file.?.pwriteAll(buffer.items, 0); +} - if (is_dyn_lib) { - try argv.append("-DLL"); - } +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)); +} - if (self.base.options.entry) |entry| { - try argv.append(try allocPrint(arena, "-ENTRY:{s}", .{entry})); - } +fn detectAllocCollision(self: *Coff, start: u32, size: u32) ?u32 { + const headers_size = self.getSizeOfHeaders(); + if (start < headers_size) + return headers_size; - 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"); - } + const end = start + size; - try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path})); + if (self.strtab_offset) |off| { + const increased_size = @intCast(u32, self.strtab.len()); + const test_end = off + increased_size; + if (end > off and start < test_end) { + return test_end; + } + } - 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})); + for (self.sections.items(.header)) |header| { + const increased_size = header.size_of_raw_data; + const test_end = header.pointer_to_raw_data + increased_size; + if (end > header.pointer_to_raw_data and start < test_end) { + return test_end; } + } - 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.?})); + return null; +} - 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.?})); - } - } - } +pub fn allocatedSize(self: *Coff, start: u32) u32 { + if (start == 0) + return 0; + var min_pos: u32 = std.math.maxInt(u32); + if (self.strtab_offset) |off| { + if (off > start and off < min_pos) min_pos = off; + } + for (self.sections.items(.header)) |header| { + if (header.pointer_to_raw_data <= start) continue; + if (header.pointer_to_raw_data < min_pos) min_pos = header.pointer_to_raw_data; + } + return min_pos - start; +} - for (self.base.options.lib_dirs) |lib_dir| { - try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{lib_dir})); - } +pub fn findFreeSpace(self: *Coff, object_size: u32, min_alignment: u32) u32 { + var start: u32 = 0; + while (self.detectAllocCollision(start, object_size)) |item_end| { + start = mem.alignForwardGeneric(u32, item_end, min_alignment); + } + return start; +} - 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); - } - } +inline fn getSizeOfHeaders(self: Coff) u32 { + const msdos_hdr_size = msdos_stub.len + 4; + return @intCast(u32, msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize() + + self.getDataDirectoryHeadersSize() + self.getSectionHeadersSize()); +} - for (comp.c_object_table.keys()) |key| { - try argv.append(key.status.success.object_path); - } +inline fn getOptionalHeaderSize(self: Coff) u32 { + return switch (self.ptr_width) { + .p32 => @intCast(u32, @sizeOf(coff.OptionalHeaderPE32)), + .p64 => @intCast(u32, @sizeOf(coff.OptionalHeaderPE64)), + }; +} - if (module_obj_path) |p| { - try argv.append(p); - } +inline fn getDataDirectoryHeadersSize(self: Coff) u32 { + return @intCast(u32, self.data_directories.len * @sizeOf(coff.ImageDataDirectory)); +} - 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; - }; +inline fn getSectionHeadersSize(self: Coff) u32 { + return @intCast(u32, self.sections.slice().len * @sizeOf(coff.SectionHeader)); +} - 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; - } - }; +inline fn getDataDirectoryHeadersOffset(self: Coff) u32 { + const msdos_hdr_size = msdos_stub.len + 4; + return @intCast(u32, msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize()); +} - 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"); - } - } - } - }, - } +inline fn getSectionHeadersOffset(self: Coff) u32 { + return self.getDataDirectoryHeadersOffset() + self.getDataDirectoryHeadersSize(); +} - // 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); - } +inline fn getSizeOfImage(self: Coff) u32 { + var image_size: u32 = mem.alignForwardGeneric(u32, self.getSizeOfHeaders(), self.page_size); + for (self.sections.items(.header)) |header| { + image_size += mem.alignForwardGeneric(u32, header.virtual_size, self.page_size); + } + return image_size; +} - // libunwind dep - if (self.base.options.link_libunwind) { - try argv.append(comp.libunwind_static_lib.?.full_object_path); - } +/// Returns symbol location corresponding to the set entrypoint (if any). +pub fn getEntryPoint(self: Coff) ?SymbolWithLoc { + const entry_name = self.base.options.entry orelse "_start"; // TODO this is incomplete + return self.globals.get(entry_name); +} - 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); - } - } +/// 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]; +} - 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; - } +/// Returns symbol described by `sym_with_loc` descriptor. +pub fn getSymbol(self: *const Coff, sym_loc: SymbolWithLoc) *const coff.Symbol { + assert(sym_loc.file == null); // TODO linking object files + return &self.locals.items[sym_loc.sym_index]; +} - 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..]); - } +/// Returns name of the symbol described by `sym_with_loc` descriptor. +pub fn getSymbolName(self: *const Coff, sym_loc: SymbolWithLoc) []const u8 { + assert(sym_loc.file == null); // TODO linking object files + const sym = self.getSymbol(sym_loc); + const offset = sym.getNameOffset() orelse return sym.getName().?; + return self.strtab.get(offset).?; +} - 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; - }, - } +/// 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); +} - 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; - } - } - } - } +/// 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); +} - 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 setSectionName(self: *Coff, header: *coff.SectionHeader, name: []const u8) !void { + if (name.len <= 8) { + mem.copy(u8, &header.name, name); + mem.set(u8, header.name[name.len..], 0); + return; } + const offset = try self.strtab.insert(self.base.allocator, name); + const name_offset = fmt.bufPrint(&header.name, "/{d}", .{offset}) catch unreachable; + mem.set(u8, header.name[name_offset.len..], 0); } -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; +fn setSymbolName(self: *Coff, symbol: *coff.Symbol, name: []const u8) !void { + if (name.len <= 8) { + mem.copy(u8, &symbol.name, name); + mem.set(u8, symbol.name[name.len..], 0); + return; } - return null; + const offset = try self.strtab.insert(self.base.allocator, name); + mem.set(u8, symbol.name[0..4], 0); + mem.writeIntLittle(u32, symbol.name[4..8], offset); } -pub fn getDeclVAddr( - self: *Coff, - decl_index: Module.Decl.Index, - reloc_info: link.File.RelocInfo, -) !u64 { - _ = 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; +fn logSymAttributes(sym: *const coff.Symbol, buf: *[4]u8) []const u8 { + mem.set(u8, buf[0..4], '_'); + switch (sym.section_number) { + .UNDEFINED => { + buf[3] = 'u'; + switch (sym.storage_class) { + .EXTERNAL => buf[1] = 'e', + .WEAK_EXTERNAL => buf[1] = 'w', + .NULL => {}, + else => unreachable, + } + }, + .ABSOLUTE => unreachable, // handle ABSOLUTE + .DEBUG => unreachable, + else => { + buf[0] = 's'; + switch (sym.storage_class) { + .EXTERNAL => buf[1] = 'e', + .WEAK_EXTERNAL => buf[1] = 'w', + .NULL => {}, + else => unreachable, + } + }, + } + return buf[0..]; } -pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !void { - _ = self; - _ = module; - _ = decl; - // TODO Implement this -} +fn logSymtab(self: *Coff) void { + var buf: [4]u8 = undefined; + + log.debug("symtab:", .{}); + log.debug(" object(null)", .{}); + for (self.locals.items) |*sym, sym_id| { + const where = if (sym.section_number == .UNDEFINED) "ord" else "sect"; + const def_index: u16 = switch (sym.section_number) { + .UNDEFINED => 0, // TODO + .ABSOLUTE => unreachable, // TODO + .DEBUG => unreachable, // TODO + else => @enumToInt(sym.section_number), + }; + log.debug(" %{d}: {?s} @{x} in {s}({d}), {s}", .{ + sym_id, + self.getSymbolName(.{ .sym_index = @intCast(u32, sym_id), .file = null }), + sym.value, + where, + def_index, + logSymAttributes(sym, &buf), + }); + } -pub fn deinit(self: *Coff) void { - if (build_options.have_llvm) { - if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator); + log.debug("globals table:", .{}); + for (self.globals.keys()) |name, id| { + const value = self.globals.values()[id]; + log.debug(" {s} => %{d} in object({?d})", .{ name, value.sym_index, value.file }); } - 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); + log.debug("GOT entries:", .{}); + for (self.got_entries.keys()) |target, i| { + const got_sym = self.getSymbol(.{ .sym_index = self.got_entries.values()[i], .file = null }); + const target_sym = self.getSymbol(target); + if (target_sym.section_number == .UNDEFINED) { + log.debug(" {d}@{x} => import('{s}')", .{ + i, + got_sym.value, + self.getSymbolName(target), + }); + } else { + log.debug(" {d}@{x} => local(%{d}) in object({?d}) {s}", .{ + i, + got_sym.value, + target.sym_index, + target.file, + logSymAttributes(target_sym, &buf), + }); + } + } } diff --git a/src/link/Coff/Atom.zig b/src/link/Coff/Atom.zig new file mode 100644 index 0000000000..6c085a8f58 --- /dev/null +++ b/src/link/Coff/Atom.zig @@ -0,0 +1,110 @@ +const Atom = @This(); + +const std = @import("std"); +const coff = std.coff; + +const Allocator = std.mem.Allocator; + +const Coff = @import("../Coff.zig"); +const Reloc = Coff.Reloc; +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: u32, + +/// 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; +} + +/// Returns symbol referencing this atom. +pub fn getSymbol(self: Atom, coff_file: *const Coff) *const coff.Symbol { + return coff_file.getSymbol(.{ + .sym_index = self.sym_index, + .file = self.file, + }); +} + +/// Returns pointer-to-symbol referencing this atom. +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 the name of this atom. +pub fn getName(self: Atom, coff_file: *const Coff) []const u8 { + return coff_file.getSymbolName(.{ + .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: *const Coff) u32 { + 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(u32) - self_sym.value; + } +} + +pub fn freeListEligible(self: Atom, coff_file: *const 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; +} + +pub fn addRelocation(self: *Atom, coff_file: *Coff, reloc: Reloc) !void { + const gpa = coff_file.base.allocator; + // TODO causes a segfault on Windows + // log.debug("adding reloc of type {s} to target %{d}", .{ @tagName(reloc.@"type"), reloc.target.sym_index }); + const gop = try coff_file.relocs.getOrPut(gpa, self); + if (!gop.found_existing) { + gop.value_ptr.* = .{}; + } + try gop.value_ptr.append(gpa, reloc); +} diff --git a/src/link/Coff/Object.zig b/src/link/Coff/Object.zig new file mode 100644 index 0000000000..63bbd07463 --- /dev/null +++ b/src/link/Coff/Object.zig @@ -0,0 +1,12 @@ +const Object = @This(); + +const std = @import("std"); +const mem = std.mem; + +const Allocator = mem.Allocator; + +name: []const u8, + +pub fn deinit(self: *Object, gpa: Allocator) void { + gpa.free(self.name); +} diff --git a/src/link/Coff/lld.zig b/src/link/Coff/lld.zig new file mode 100644 index 0000000000..feae0134e9 --- /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 764e4e71b2..af25441066 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 c2aa562db5..a7dc6391c2 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, diff --git a/src/link/strtab.zig b/src/link/strtab.zig index ae9b00027e..8e314f189f 100644 --- a/src/link/strtab.zig +++ b/src/link/strtab.zig @@ -109,5 +109,9 @@ pub fn StringTable(comptime log_scope: @Type(.EnumLiteral)) type { pub fn getAssumeExists(self: Self, off: u32) []const u8 { return self.get(off) orelse unreachable; } + + pub fn len(self: Self) usize { + return self.buffer.items.len; + } }; } |
