diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2020-09-28 00:06:06 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2020-09-28 00:06:06 -0700 |
| commit | 91a73a177bc20fa0219dbb6c3cf3dda1c2a465db (patch) | |
| tree | 5f391418951620a1f28e8a8fccddbb8e33aed8f6 /src/link | |
| parent | a9082b4ec51debdbffc20b56e9b37cb82dd04750 (diff) | |
| download | zig-91a73a177bc20fa0219dbb6c3cf3dda1c2a465db.tar.gz zig-91a73a177bc20fa0219dbb6c3cf3dda1c2a465db.zip | |
stage2: building mingw-w64 and COFF LDD linking
still TODO is the task of creating import .lib files for DLLs on the fly
both for -lfoo and for e.g. `extern "kernel32"`
Diffstat (limited to 'src/link')
| -rw-r--r-- | src/link/Coff.zig | 576 | ||||
| -rw-r--r-- | src/link/Elf.zig | 25 |
2 files changed, 511 insertions, 90 deletions
diff --git a/src/link/Coff.zig b/src/link/Coff.zig index c396732bc1..e1d7a07fbc 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -5,6 +5,8 @@ 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 mem = std.mem; const trace = @import("../tracy.zig").trace; const Module = @import("../Module.zig"); @@ -12,6 +14,8 @@ const Compilation = @import("../Compilation.zig"); 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 allocation_padding = 4 / 3; const minimum_text_block_size = 64 * allocation_padding; @@ -21,7 +25,7 @@ const file_alignment = 512; const image_base = 0x400_000; const section_table_size = 2 * 40; comptime { - assert(std.mem.isAligned(image_base, section_alignment)); + assert(mem.isAligned(image_base, section_alignment)); } pub const base_tag: link.File.Tag = .coff; @@ -155,14 +159,14 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio if (machine == .Unknown) { return error.UnsupportedCOFFArchitecture; } - std.mem.writeIntLittle(u16, hdr_data[0..2], @enumToInt(machine)); + mem.writeIntLittle(u16, hdr_data[0..2], @enumToInt(machine)); index += 2; // Number of sections (we only use .got, .text) - std.mem.writeIntLittle(u16, hdr_data[index..][0..2], 2); + mem.writeIntLittle(u16, hdr_data[index..][0..2], 2); index += 2; // TimeDateStamp (u32), PointerToSymbolTable (u32), NumberOfSymbols (u32) - std.mem.set(u8, hdr_data[index..][0..12], 0); + mem.set(u8, hdr_data[index..][0..12], 0); index += 12; const optional_header_size = switch (options.output_mode) { @@ -177,8 +181,8 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio const default_offset_table_size = file_alignment; const default_size_of_code = 0; - self.section_data_offset = std.mem.alignForwardGeneric(u32, self.section_table_offset + section_table_size, file_alignment); - const section_data_relative_virtual_address = std.mem.alignForwardGeneric(u32, self.section_table_offset + section_table_size, section_alignment); + 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 = image_base + section_data_relative_virtual_address; self.offset_table_size = default_offset_table_size; self.section_table_offset = section_table_offset; @@ -186,9 +190,9 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio self.text_section_size = default_size_of_code; // Size of file when loaded in memory - const size_of_image = std.mem.alignForwardGeneric(u32, self.text_section_virtual_address - image_base + default_size_of_code, section_alignment); + const size_of_image = mem.alignForwardGeneric(u32, self.text_section_virtual_address - image_base + default_size_of_code, section_alignment); - std.mem.writeIntLittle(u16, hdr_data[index..][0..2], optional_header_size); + mem.writeIntLittle(u16, hdr_data[index..][0..2], optional_header_size); index += 2; // Characteristics @@ -200,7 +204,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio .p32 => characteristics |= std.coff.IMAGE_FILE_32BIT_MACHINE, .p64 => characteristics |= std.coff.IMAGE_FILE_LARGE_ADDRESS_AWARE, } - std.mem.writeIntLittle(u16, hdr_data[index..][0..2], characteristics); + mem.writeIntLittle(u16, hdr_data[index..][0..2], characteristics); index += 2; assert(index == 20); @@ -210,106 +214,106 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio self.optional_header_offset = coff_file_header_offset + 20; // Optional header index = 0; - std.mem.writeIntLittle(u16, hdr_data[0..2], switch (self.ptr_width) { + mem.writeIntLittle(u16, hdr_data[0..2], switch (self.ptr_width) { .p32 => @as(u16, 0x10b), .p64 => 0x20b, }); index += 2; // Linker version (u8 + u8) - std.mem.set(u8, hdr_data[index..][0..2], 0); + mem.set(u8, hdr_data[index..][0..2], 0); index += 2; // SizeOfCode (UNUSED, u32), SizeOfInitializedData (u32), SizeOfUninitializedData (u32), AddressOfEntryPoint (u32), BaseOfCode (UNUSED, u32) - std.mem.set(u8, hdr_data[index..][0..20], 0); + 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) - std.mem.set(u8, hdr_data[index..][0..4], 0); + mem.set(u8, hdr_data[index..][0..4], 0); index += 4; // Image base address - std.mem.writeIntLittle(u32, hdr_data[index..][0..4], image_base); + mem.writeIntLittle(u32, hdr_data[index..][0..4], image_base); index += 4; } else { // Image base address - std.mem.writeIntLittle(u64, hdr_data[index..][0..8], image_base); + mem.writeIntLittle(u64, hdr_data[index..][0..8], image_base); index += 8; } // Section alignment - std.mem.writeIntLittle(u32, hdr_data[index..][0..4], section_alignment); + mem.writeIntLittle(u32, hdr_data[index..][0..4], section_alignment); index += 4; // File alignment - std.mem.writeIntLittle(u32, hdr_data[index..][0..4], file_alignment); + mem.writeIntLittle(u32, hdr_data[index..][0..4], file_alignment); index += 4; // Required OS version, 6.0 is vista - std.mem.writeIntLittle(u16, hdr_data[index..][0..2], 6); + mem.writeIntLittle(u16, hdr_data[index..][0..2], 6); index += 2; - std.mem.writeIntLittle(u16, hdr_data[index..][0..2], 0); + mem.writeIntLittle(u16, hdr_data[index..][0..2], 0); index += 2; // Image version - std.mem.set(u8, hdr_data[index..][0..4], 0); + mem.set(u8, hdr_data[index..][0..4], 0); index += 4; // Required subsystem version, same as OS version - std.mem.writeIntLittle(u16, hdr_data[index..][0..2], 6); + mem.writeIntLittle(u16, hdr_data[index..][0..2], 6); index += 2; - std.mem.writeIntLittle(u16, hdr_data[index..][0..2], 0); + mem.writeIntLittle(u16, hdr_data[index..][0..2], 0); index += 2; // Reserved zeroes (u32) - std.mem.set(u8, hdr_data[index..][0..4], 0); + mem.set(u8, hdr_data[index..][0..4], 0); index += 4; - std.mem.writeIntLittle(u32, hdr_data[index..][0..4], size_of_image); + mem.writeIntLittle(u32, hdr_data[index..][0..4], size_of_image); index += 4; - std.mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset); + mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset); index += 4; // CheckSum (u32) - std.mem.set(u8, hdr_data[index..][0..4], 0); + mem.set(u8, hdr_data[index..][0..4], 0); index += 4; // Subsystem, TODO: Let users specify the subsystem, always CUI for now - std.mem.writeIntLittle(u16, hdr_data[index..][0..2], 3); + mem.writeIntLittle(u16, hdr_data[index..][0..2], 3); index += 2; // DLL characteristics - std.mem.writeIntLittle(u16, hdr_data[index..][0..2], 0x0); + mem.writeIntLittle(u16, hdr_data[index..][0..2], 0x0); index += 2; switch (self.ptr_width) { .p32 => { // Size of stack reserve + commit - std.mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000_000); + mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000_000); index += 4; - std.mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000); + mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000); index += 4; // Size of heap reserve + commit - std.mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x100_000); + mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x100_000); index += 4; - std.mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000); + mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000); index += 4; }, .p64 => { // Size of stack reserve + commit - std.mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000_000); + mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000_000); index += 8; - std.mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000); + mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000); index += 8; // Size of heap reserve + commit - std.mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x100_000); + mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x100_000); index += 8; - std.mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000); + mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000); index += 8; }, } // Reserved zeroes - std.mem.set(u8, hdr_data[index..][0..4], 0); + mem.set(u8, hdr_data[index..][0..4], 0); index += 4; // Number of data directories - std.mem.writeIntLittle(u32, hdr_data[index..][0..4], data_directory_count); + mem.writeIntLittle(u32, hdr_data[index..][0..4], data_directory_count); index += 4; // Initialize data directories to zero - std.mem.set(u8, hdr_data[index..][0 .. data_directory_count * 8], 0); + mem.set(u8, hdr_data[index..][0 .. data_directory_count * 8], 0); index += data_directory_count * 8; assert(index == optional_header_size); @@ -321,52 +325,52 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio index += 8; if (options.output_mode == .Exe) { // Virtual size (u32) - std.mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size); + mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size); index += 4; // Virtual address (u32) - std.mem.writeIntLittle(u32, hdr_data[index..][0..4], self.offset_table_virtual_address - image_base); + mem.writeIntLittle(u32, hdr_data[index..][0..4], self.offset_table_virtual_address - image_base); index += 4; } else { - std.mem.set(u8, hdr_data[index..][0..8], 0); + mem.set(u8, hdr_data[index..][0..8], 0); index += 8; } // Size of raw data (u32) - std.mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size); + mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size); index += 4; // File pointer to the start of the section - std.mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset); + mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset); index += 4; // Pointer to relocations (u32), PointerToLinenumbers (u32), NumberOfRelocations (u16), NumberOfLinenumbers (u16) - std.mem.set(u8, hdr_data[index..][0..12], 0); + mem.set(u8, hdr_data[index..][0..12], 0); index += 12; // Section flags - std.mem.writeIntLittle(u32, hdr_data[index..][0..4], std.coff.IMAGE_SCN_CNT_INITIALIZED_DATA | std.coff.IMAGE_SCN_MEM_READ); + mem.writeIntLittle(u32, hdr_data[index..][0..4], std.coff.IMAGE_SCN_CNT_INITIALIZED_DATA | std.coff.IMAGE_SCN_MEM_READ); 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) - std.mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code); + mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code); index += 4; // Virtual address (u32) - std.mem.writeIntLittle(u32, hdr_data[index..][0..4], self.text_section_virtual_address - image_base); + mem.writeIntLittle(u32, hdr_data[index..][0..4], self.text_section_virtual_address - image_base); index += 4; } else { - std.mem.set(u8, hdr_data[index..][0..8], 0); + mem.set(u8, hdr_data[index..][0..8], 0); index += 8; } // Size of raw data (u32) - std.mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code); + mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code); index += 4; // File pointer to the start of the section - std.mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset + default_offset_table_size); + 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) - std.mem.set(u8, hdr_data[index..][0..12], 0); + mem.set(u8, hdr_data[index..][0..12], 0); index += 12; // Section flags - std.mem.writeIntLittle( + mem.writeIntLittle( u32, hdr_data[index..][0..4], std.coff.IMAGE_SCN_CNT_CODE | std.coff.IMAGE_SCN_MEM_EXECUTE | std.coff.IMAGE_SCN_MEM_READ | std.coff.IMAGE_SCN_MEM_WRITE, @@ -434,7 +438,7 @@ fn allocateTextBlock(self: *Coff, text_block: *TextBlock, new_block_size: u64, a 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 = std.mem.alignForwardGeneric(u64, free_block.getVAddr(self.*) + free_block.size, alignment) - self.text_section_virtual_address; + 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; @@ -453,7 +457,7 @@ fn allocateTextBlock(self: *Coff, text_block: *TextBlock, new_block_size: u64, a continue; } } else if (self.last_text_block) |last| { - const new_block_vaddr = std.mem.alignForwardGeneric(u64, last.getVAddr(self.*) + last.size, alignment); + const new_block_vaddr = mem.alignForwardGeneric(u64, last.getVAddr(self.*) + last.size, alignment); block_placement = last; break :blk new_block_vaddr; } else { @@ -463,15 +467,15 @@ fn allocateTextBlock(self: *Coff, text_block: *TextBlock, new_block_size: u64, a const expand_text_section = block_placement == null or block_placement.?.next == null; if (expand_text_section) { - const needed_size = @intCast(u32, std.mem.alignForwardGeneric(u64, vaddr + new_block_size - self.text_section_virtual_address, file_alignment)); + 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 = std.mem.alignForwardGeneric(u32, self.text_section_size, section_alignment); - const new_text_section_virtual_size = std.mem.alignForwardGeneric(u32, needed_size, section_alignment); + 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; - std.mem.writeIntLittle(u32, &buf, new_text_section_virtual_size); + mem.writeIntLittle(u32, &buf, new_text_section_virtual_size); try self.base.file.?.pwriteAll(&buf, self.section_table_offset + 40 + 8); } @@ -509,7 +513,7 @@ fn allocateTextBlock(self: *Coff, text_block: *TextBlock, new_block_size: u64, a fn growTextBlock(self: *Coff, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { const block_vaddr = text_block.getVAddr(self.*); - const align_ok = std.mem.alignBackwardGeneric(u64, block_vaddr, alignment) == block_vaddr; + 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); @@ -575,14 +579,14 @@ fn writeOffsetTableEntry(self: *Coff, index: usize) !void { // Write the new raw size in the .got header var buf: [8]u8 = undefined; - std.mem.writeIntLittle(u32, buf[0..4], new_raw_size); + 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 - std.mem.writeIntLittle(u32, buf[0..4], new_text_section_start); + 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 = std.mem.alignForwardGeneric(u32, self.offset_table_size, section_alignment); - const new_virtual_size = std.mem.alignForwardGeneric(u32, new_raw_size, section_alignment); + 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 virutal size of the `.got` section @@ -592,12 +596,12 @@ fn writeOffsetTableEntry(self: *Coff, index: usize) !void { const va_offset = new_virtual_size - current_virtual_size; // Write .got virtual size - std.mem.writeIntLittle(u32, buf[0..4], new_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; - std.mem.writeIntLittle(u32, buf[0..4], self.text_section_virtual_address - image_base); + mem.writeIntLittle(u32, buf[0..4], self.text_section_virtual_address - image_base); try self.base.file.?.pwriteAll(buf[0..4], self.section_table_offset + 40 + 12); // Fix the VAs in the offset table @@ -607,11 +611,11 @@ fn writeOffsetTableEntry(self: *Coff, index: usize) !void { switch (entry_size) { 4 => { - std.mem.writeInt(u32, buf[0..4], @intCast(u32, va.*), endian); + 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 => { - std.mem.writeInt(u64, &buf, va.*, endian); + mem.writeInt(u64, &buf, va.*, endian); try self.base.file.?.pwriteAll(&buf, offset_table_start + idx * entry_size); }, else => unreachable, @@ -626,12 +630,12 @@ fn writeOffsetTableEntry(self: *Coff, index: usize) !void { switch (entry_size) { 4 => { var buf: [4]u8 = undefined; - std.mem.writeInt(u32, &buf, @intCast(u32, self.offset_table.items[index]), endian); + 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; - std.mem.writeInt(u64, &buf, self.offset_table.items[index], endian); + mem.writeInt(u64, &buf, self.offset_table.items[index], endian); try self.base.file.?.pwriteAll(&buf, offset_table_start + index * entry_size); }, else => unreachable, @@ -664,7 +668,7 @@ pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void { if (curr_size != 0) { const capacity = decl.link.coff.capacity(); const need_realloc = code.len > capacity or - !std.mem.isAlignedGeneric(u32, decl.link.coff.text_offset, required_alignment); + !mem.isAlignedGeneric(u32, decl.link.coff.text_offset, required_alignment); if (need_realloc) { const curr_vaddr = self.getDeclVAddr(decl); const vaddr = try self.growTextBlock(&decl.link.coff, code.len, required_alignment); @@ -679,7 +683,7 @@ pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void { } } else { const vaddr = try self.allocateTextBlock(&decl.link.coff, code.len, required_alignment); - log.debug("allocated text block for {} at 0x{x} (size: {Bi})\n", .{ std.mem.spanZ(decl.name), vaddr, code.len }); + log.debug("allocated text block for {} at 0x{x} (size: {Bi})\n", .{ mem.spanZ(decl.name), vaddr, 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); @@ -702,7 +706,7 @@ pub fn freeDecl(self: *Coff, decl: *Module.Decl) void { pub fn updateDeclExports(self: *Coff, module: *Module, decl: *const Module.Decl, exports: []const *Module.Export) !void { for (exports) |exp| { if (exp.options.section) |section_name| { - if (!std.mem.eql(u8, section_name, ".text")) { + if (!mem.eql(u8, section_name, ".text")) { try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); module.failed_exports.putAssumeCapacityNoClobber( exp, @@ -711,7 +715,7 @@ pub fn updateDeclExports(self: *Coff, module: *Module, decl: *const Module.Decl, continue; } } - if (std.mem.eql(u8, exp.options.name, "_start")) { + if (mem.eql(u8, exp.options.name, "_start")) { self.entry_addr = decl.link.coff.getVAddr(self.*) - image_base; } else { try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); @@ -726,8 +730,12 @@ pub fn updateDeclExports(self: *Coff, module: *Module, decl: *const Module.Decl, pub fn flush(self: *Coff, comp: *Compilation) !void { if (build_options.have_llvm and self.base.options.use_lld) { - return error.CoffLinkingWithLLDUnimplemented; + return self.linkWithLLD(comp); } else { + switch (self.base.options.effectiveOutputMode()) { + .Exe, .Obj => {}, + .Lib => return error.TODOImplementWritingLibFiles, + } return self.flushModule(comp); } } @@ -739,16 +747,16 @@ pub fn flushModule(self: *Coff, comp: *Compilation) !void { if (self.text_section_size_dirty) { // Write the new raw size in the .text header var buf: [4]u8 = undefined; - std.mem.writeIntLittle(u32, &buf, self.text_section_size); + mem.writeIntLittle(u32, &buf, self.text_section_size); try self.base.file.?.pwriteAll(&buf, self.section_table_offset + 40 + 16); try self.base.file.?.setEndPos(self.section_data_offset + self.offset_table_size + self.text_section_size); self.text_section_size_dirty = false; } if (self.base.options.output_mode == .Exe and self.size_of_image_dirty) { - const new_size_of_image = std.mem.alignForwardGeneric(u32, self.text_section_virtual_address - image_base + self.text_section_size, section_alignment); + const new_size_of_image = mem.alignForwardGeneric(u32, self.text_section_virtual_address - image_base + self.text_section_size, section_alignment); var buf: [4]u8 = undefined; - std.mem.writeIntLittle(u32, &buf, new_size_of_image); + 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; } @@ -763,12 +771,422 @@ pub fn flushModule(self: *Coff, comp: *Compilation) !void { if (self.base.options.output_mode == .Exe) { // Write AddressOfEntryPoint var buf: [4]u8 = undefined; - std.mem.writeIntLittle(u32, &buf, self.entry_addr.?); + mem.writeIntLittle(u32, &buf, self.entry_addr.?); try self.base.file.?.pwriteAll(&buf, self.optional_header_offset + 16); } } } +fn linkWithLLD(self: *Coff, comp: *Compilation) !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. + + // 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.is_stage1 and self.base.options.use_llvm; + 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, + }); + const o_directory = self.base.options.module.?.zig_cache_artifact_directory; + const full_obj_path = try o_directory.join(arena, &[_][]const u8{obj_basename}); + break :blk full_obj_path; + } + + try self.flushModule(comp); + const obj_basename = self.base.intermediary_basename.?; + const full_obj_path = try directory.join(arena, &[_][]const u8{obj_basename}); + break :blk full_obj_path; + } else null; + + 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 self.base.options.output_mode == .Exe; + 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(); + + try man.addListOfFiles(self.base.options.objects); + for (comp.c_object_table.items()) |entry| { + _ = try man.addFile(entry.key.status.success.object_path, null); + } + try man.addOptionalFile(module_obj_path); + man.hash.addOptional(self.base.options.stack_size_override); + man.hash.addListOfBytes(self.base.options.extra_lld_args); + man.hash.addListOfBytes(self.base.options.lib_dirs); + man.hash.add(self.base.options.is_compiler_rt_or_libc); + 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.?); + } + } + } + man.hash.addStringSet(self.base.options.system_libs); + man.hash.addOptional(self.base.options.subsystem); + man.hash.add(self.base.options.is_test); + + // 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 = directory.handle.readLink(id_symlink_basename, &prev_digest_buf) catch |err| blk: { + log.debug("COFF LLD new_digest={} readlink error: {}", .{ 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={} match - skipping invocation", .{digest}); + // Hot diggity dog! The output binary is already there. + self.base.lock = man.toOwnedLock(); + return; + } + log.debug("COFF LLD prev_digest={} new_digest={}", .{ prev_digest, 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, + }; + } + + const is_obj = self.base.options.output_mode == .Obj; + + // Create an LLD command line and invoke it. + var argv = std.ArrayList([]const u8).init(self.base.allocator); + defer argv.deinit(); + // Even though we're calling LLD as a library it thinks the first argument is its own exe name. + try argv.append("lld"); + if (is_obj) { + try argv.append("-r"); + } + + try argv.append("-ERRORLIMIT:0"); + try argv.append("-NOLOGO"); + if (!self.base.options.strip) { + try argv.append("-DEBUG"); + } + 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 (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"); + } + } + + if (is_dyn_lib) { + try argv.append("-DLL"); + } + + const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); + try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_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.appendSlice(self.base.options.objects); + + for (comp.c_object_table.items()) |entry| { + try argv.append(entry.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.have_dllmain_crt_startup or is_dyn_lib) + break :blk null; + if (module.have_c_main or self.base.options.is_test or + module.have_winmain_crt_startup or module.have_wwinmain_crt_startup) + { + break :blk .Console; + } + if (module.have_winmain or module.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| switch (subsystem) { + .Console => { + try argv.append("-SUBSYSTEM:console"); + break :mode .win32; + }, + .EfiApplication => { + try argv.append("-SUBSYSTEM:efi_application"); + break :mode .uefi; + }, + .EfiBootServiceDriver => { + try argv.append("-SUBSYSTEM:efi_boot_service_driver"); + break :mode .uefi; + }, + .EfiRom => { + try argv.append("-SUBSYSTEM:efi_rom"); + break :mode .uefi; + }, + .EfiRuntimeDriver => { + try argv.append("-SUBSYSTEM:efi_runtime_driver"); + break :mode .uefi; + }, + .Native => { + try argv.append("-SUBSYSTEM:native"); + break :mode .win32; + }, + .Posix => { + try argv.append("-SUBSYSTEM:posix"); + break :mode .win32; + }, + .Windows => { + try argv.append("-SUBSYSTEM:windows"); + 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.o")); + } else { + try argv.append(try comp.get_libc_crt_file(arena, "crt2.o")); + } + + 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.have_winmain) { + try argv.append("-ENTRY:WinMain"); + } else if (module.have_wwinmain) { + try argv.append("-ENTRY:wWinMain"); + } else if (module.have_wwinmain_crt_startup) { + try argv.append("-ENTRY:wWinMainCRTStartup"); + } else { + try argv.append("-ENTRY:WinMainCRTStartup"); + } + } else { + try argv.append("-ENTRY:WinMainCRTStartup"); + } + } + } + }, + } + + if (!is_obj) { + // 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); + try argv.append(comp.libunwind_static_lib.?.full_object_path); + } + } + + // compiler-rt and libc + if (is_exe_or_dyn_lib and !self.base.options.is_compiler_rt_or_libc) { + if (!self.base.options.link_libc) { + try argv.append(comp.libc_static_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. + try argv.append(comp.compiler_rt_static_lib.?.full_object_path); + } + + for (self.base.options.system_libs.items()) |entry| { + const lib_basename = try allocPrint(arena, "{s}.lib", .{entry.key}); + if (comp.crt_files.get(lib_basename)) |crt_file| { + try argv.append(crt_file.full_object_path); + } else { + try argv.append(lib_basename); + } + } + + if (self.base.options.verbose_link) { + Compilation.dump_argv(argv.items); + } + + const new_argv_with_sentinel = try arena.alloc(?[*:0]const u8, argv.items.len + 1); + new_argv_with_sentinel[argv.items.len] = null; + const new_argv = new_argv_with_sentinel[0..argv.items.len :null]; + for (argv.items) |arg, i| { + new_argv[i] = try arena.dupeZ(u8, arg); + } + + var stderr_context: LLDContext = .{ + .coff = self, + .data = std.ArrayList(u8).init(self.base.allocator), + }; + defer stderr_context.data.deinit(); + var stdout_context: LLDContext = .{ + .coff = self, + .data = std.ArrayList(u8).init(self.base.allocator), + }; + defer stdout_context.data.deinit(); + const llvm = @import("../llvm.zig"); + const ok = llvm.Link( + .COFF, + new_argv.ptr, + new_argv.len, + append_diagnostic, + @ptrToInt(&stdout_context), + @ptrToInt(&stderr_context), + ); + if (stderr_context.oom or stdout_context.oom) return error.OutOfMemory; + if (stdout_context.data.items.len != 0) { + std.log.warn("unexpected LLD stdout: {}", .{stdout_context.data.items}); + } + if (!ok) { + // TODO parse this output and surface with the Compilation API rather than + // directly outputting to stderr here. + std.debug.print("{}", .{stderr_context.data.items}); + return error.LLDReportedFailure; + } + if (stderr_context.data.items.len != 0) { + std.log.warn("unexpected LLD stderr: {}", .{stderr_context.data.items}); + } + + if (!self.base.options.disable_lld_caching) { + // Update the dangling symlink with the digest. If it fails we can continue; it only + // means that the next invocation will have an unnecessary cache miss. + directory.handle.symLink(&digest, id_symlink_basename, .{}) catch |err| { + std.log.warn("failed to save linking hash digest symlink: {}", .{@errorName(err)}); + }; + // Again failure here only means an unnecessary cache miss. + man.writeManifest() catch |err| { + std.log.warn("failed to write cache manifest when linking: {}", .{@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(); + } +} + +const LLDContext = struct { + data: std.ArrayList(u8), + coff: *Coff, + oom: bool = false, +}; + +fn append_diagnostic(context: usize, ptr: [*]const u8, len: usize) callconv(.C) void { + const lld_context = @intToPtr(*LLDContext, context); + const msg = ptr[0..len]; + lld_context.data.appendSlice(msg) catch |err| switch (err) { + error.OutOfMemory => lld_context.oom = true, + }; +} + pub fn getDeclVAddr(self: *Coff, decl: *const Module.Decl) u64 { return self.text_section_virtual_address + decl.link.coff.text_offset; } diff --git a/src/link/Elf.zig b/src/link/Elf.zig index c8067058d9..88f5040761 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1225,7 +1225,11 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: { const use_stage1 = build_options.is_stage1 and self.base.options.use_llvm; if (use_stage1) { - const obj_basename = try std.fmt.allocPrint(arena, "{}.o", .{self.base.options.root_name}); + const obj_basename = try std.zig.binNameAlloc(arena, .{ + .root_name = self.base.options.root_name, + .target = self.base.options.target, + .output_mode = .Obj, + }); const o_directory = self.base.options.module.?.zig_cache_artifact_directory; const full_obj_path = try o_directory.join(arena, &[_][]const u8{obj_basename}); break :blk full_obj_path; @@ -1242,6 +1246,8 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { const is_exe_or_dyn_lib = is_dyn_lib or self.base.options.output_mode == .Exe; const have_dynamic_linker = self.base.options.link_libc and self.base.options.link_mode == .Dynamic and is_exe_or_dyn_lib; + const link_in_crt = self.base.options.link_libc and self.base.options.output_mode == .Exe; + const target = self.base.options.target; // Here we want to determine whether we can save time by not invoking LLD when the // output is unchanged. None of the linker options or the object files that are being @@ -1297,7 +1303,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { man.hash.addOptionalBytes(self.base.options.override_soname); man.hash.addOptional(self.base.options.version); } - man.hash.addListOfBytes(self.base.options.system_libs); + man.hash.addStringSet(self.base.options.system_libs); man.hash.addOptional(self.base.options.allow_shlib_undefined); man.hash.add(self.base.options.bind_global_refs_locally); @@ -1326,7 +1332,6 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { }; } - const target = self.base.options.target; const is_obj = self.base.options.output_mode == .Obj; // Create an LLD command line and invoke it. @@ -1337,7 +1342,6 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { if (is_obj) { try argv.append("-r"); } - const link_in_crt = self.base.options.link_libc and self.base.options.output_mode == .Exe; try argv.append("-error-limit=0"); @@ -1440,7 +1444,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { var test_path = std.ArrayList(u8).init(self.base.allocator); defer test_path.deinit(); for (self.base.options.lib_dirs) |lib_dir_path| { - for (self.base.options.system_libs) |link_lib| { + for (self.base.options.system_libs.items()) |link_lib| { test_path.shrinkRetainingCapacity(0); const sep = fs.path.sep_str; try test_path.writer().print("{}" ++ sep ++ "lib{}.so", .{ lib_dir_path, link_lib }); @@ -1509,8 +1513,10 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { } // Shared libraries. - try argv.ensureCapacity(argv.items.len + self.base.options.system_libs.len); - for (self.base.options.system_libs) |link_lib| { + const system_libs = self.base.options.system_libs.items(); + try argv.ensureCapacity(argv.items.len + system_libs.len); + for (system_libs) |entry| { + const link_lib = entry.key; // By this time, we depend on these libs being dynamically linked libraries and not static libraries // (the check for that needs to be earlier), but they could be full paths to .so files, in which // case we want to avoid prepending "-l". @@ -1581,10 +1587,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { } if (self.base.options.verbose_link) { - for (argv.items[0 .. argv.items.len - 1]) |arg| { - std.debug.print("{} ", .{arg}); - } - std.debug.print("{}\n", .{argv.items[argv.items.len - 1]}); + Compilation.dump_argv(argv.items); } // Oh, snapplesauce! We need null terminated argv. |
