diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2020-09-21 18:38:55 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2020-09-21 18:38:55 -0700 |
| commit | 528832bd3a2e7b686ee84aef5887df740a6114db (patch) | |
| tree | 90ccff9faa2ba2604c8538aeec0a147a4b01148c /src/link | |
| parent | b9f61d401502f5d221e72c0d0e3bf448b11dcd68 (diff) | |
| download | zig-528832bd3a2e7b686ee84aef5887df740a6114db.tar.gz zig-528832bd3a2e7b686ee84aef5887df740a6114db.zip | |
rename src-self-hosted/ to src/
Diffstat (limited to 'src/link')
| -rw-r--r-- | src/link/C.zig | 113 | ||||
| -rw-r--r-- | src/link/Coff.zig | 784 | ||||
| -rw-r--r-- | src/link/Elf.zig | 3059 | ||||
| -rw-r--r-- | src/link/MachO.zig | 724 | ||||
| -rw-r--r-- | src/link/Wasm.zig | 274 | ||||
| -rw-r--r-- | src/link/cbe.h | 15 | ||||
| -rw-r--r-- | src/link/msdos-stub.bin | bin | 0 -> 128 bytes |
7 files changed, 4969 insertions, 0 deletions
diff --git a/src/link/C.zig b/src/link/C.zig new file mode 100644 index 0000000000..d5d1249244 --- /dev/null +++ b/src/link/C.zig @@ -0,0 +1,113 @@ +const std = @import("std"); +const mem = std.mem; +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const Module = @import("../Module.zig"); +const Compilation = @import("../Compilation.zig"); +const fs = std.fs; +const codegen = @import("../codegen/c.zig"); +const link = @import("../link.zig"); +const trace = @import("../tracy.zig").trace; +const File = link.File; +const C = @This(); + +pub const base_tag: File.Tag = .c; + +base: File, + +header: std.ArrayList(u8), +constants: std.ArrayList(u8), +main: std.ArrayList(u8), + +called: std.StringHashMap(void), +need_stddef: bool = false, +need_stdint: bool = false, +error_msg: *Compilation.ErrorMsg = undefined, + +pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*C { + assert(options.object_format == .c); + + if (options.use_llvm) return error.LLVMHasNoCBackend; + if (options.use_lld) return error.LLDHasNoCBackend; + + const file = try options.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true, .mode = link.determineMode(options) }); + errdefer file.close(); + + var c_file = try allocator.create(C); + errdefer allocator.destroy(c_file); + + c_file.* = C{ + .base = .{ + .tag = .c, + .options = options, + .file = file, + .allocator = allocator, + }, + .main = std.ArrayList(u8).init(allocator), + .header = std.ArrayList(u8).init(allocator), + .constants = std.ArrayList(u8).init(allocator), + .called = std.StringHashMap(void).init(allocator), + }; + + return c_file; +} + +pub fn fail(self: *C, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { + self.error_msg = try Compilation.ErrorMsg.create(self.base.allocator, src, format, args); + return error.AnalysisFail; +} + +pub fn deinit(self: *C) void { + self.main.deinit(); + self.header.deinit(); + self.constants.deinit(); + self.called.deinit(); +} + +pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { + codegen.generate(self, decl) catch |err| { + if (err == error.AnalysisFail) { + try module.failed_decls.put(module.gpa, decl, self.error_msg); + } + return err; + }; +} + +pub fn flush(self: *C, comp: *Compilation) !void { + return self.flushModule(comp); +} + +pub fn flushModule(self: *C, comp: *Compilation) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const writer = self.base.file.?.writer(); + try writer.writeAll(@embedFile("cbe.h")); + var includes = false; + if (self.need_stddef) { + try writer.writeAll("#include <stddef.h>\n"); + includes = true; + } + if (self.need_stdint) { + try writer.writeAll("#include <stdint.h>\n"); + includes = true; + } + if (includes) { + try writer.writeByte('\n'); + } + if (self.header.items.len > 0) { + try writer.print("{}\n", .{self.header.items}); + } + if (self.constants.items.len > 0) { + try writer.print("{}\n", .{self.constants.items}); + } + if (self.main.items.len > 1) { + const last_two = self.main.items[self.main.items.len - 2 ..]; + if (std.mem.eql(u8, last_two, "\n\n")) { + self.main.items.len -= 1; + } + } + try writer.writeAll(self.main.items); + self.base.file.?.close(); + self.base.file = null; +} diff --git a/src/link/Coff.zig b/src/link/Coff.zig new file mode 100644 index 0000000000..31726a5712 --- /dev/null +++ b/src/link/Coff.zig @@ -0,0 +1,784 @@ +const Coff = @This(); + +const std = @import("std"); +const log = std.log.scoped(.link); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; +const fs = std.fs; + +const trace = @import("../tracy.zig").trace; +const Module = @import("../Module.zig"); +const Compilation = @import("../Compilation.zig"); +const codegen = @import("../codegen.zig"); +const link = @import("../link.zig"); +const build_options = @import("build_options"); + +const allocation_padding = 4 / 3; +const minimum_text_block_size = 64 * allocation_padding; + +const section_alignment = 4096; +const file_alignment = 512; +const image_base = 0x400_000; +const section_table_size = 2 * 40; +comptime { + assert(std.mem.isAligned(image_base, section_alignment)); +} + +pub const base_tag: link.File.Tag = .coff; + +const msdos_stub = @embedFile("msdos-stub.bin"); + +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, +/// Optiona 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) = .{}, + +/// Virtual address of the entry point procedure relative to `image_base` +entry_addr: ?u32 = null, + +/// Absolute virtual address of the text section when the executable is loaded in memory. +text_section_virtual_address: u32 = 0, +/// Current size of the `.text` section on disk, must be a multiple of `file_alignment` +text_section_size: u32 = 0, + +offset_table_size_dirty: bool = false, +text_section_size_dirty: bool = false, +/// This flag is set when the virtual size of the whole image file when loaded in memory has changed +/// and needs to be updated in the optional header. +size_of_image_dirty: bool = false, + +pub const PtrWidth = enum { p32, p64 }; + +pub const TextBlock = struct { + /// Offset of the code relative to the start of the text section + text_offset: u32, + /// Used size of the text block + size: u32, + /// This field is undefined for symbols with size = 0. + offset_table_index: u32, + /// Points to the previous and next neighbors, based on the `text_offset`. + /// This can be used to find, for example, the capacity of this `TextBlock`. + prev: ?*TextBlock, + next: ?*TextBlock, + + pub const empty = TextBlock{ + .text_offset = 0, + .size = 0, + .offset_table_index = undefined, + .prev = null, + .next = null, + }; + + /// Returns how much room there is to grow in virtual address space. + fn capacity(self: TextBlock) u64 { + if (self.next) |next| { + return next.text_offset - self.text_offset; + } + // This is the last block, the capacity is only limited by the address space. + return std.math.maxInt(u32) - self.text_offset; + } + + fn freeListEligible(self: TextBlock) bool { + // No need to keep a free list node for the last block. + const next = self.next orelse return false; + const cap = next.text_offset - self.text_offset; + const ideal_cap = self.size * allocation_padding; + if (cap <= ideal_cap) return false; + const surplus = cap - ideal_cap; + return surplus >= minimum_text_block_size; + } + + /// Absolute virtual address of the text block when the file is loaded in memory. + fn getVAddr(self: TextBlock, coff: Coff) u32 { + return coff.text_section_virtual_address + self.text_offset; + } +}; + +pub const SrcFn = void; + +pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Coff { + assert(options.object_format == .coff); + + if (options.use_llvm) return error.LLVM_BackendIsTODO_ForCoff; // TODO + if (options.use_lld) return error.LLD_LinkingIsTODO_ForCoff; // TODO + + const file = try options.directory.handle.createFile(sub_path, .{ + .truncate = false, + .read = true, + .mode = link.determineMode(options), + }); + errdefer file.close(); + + const self = try createEmpty(allocator, options); + errdefer self.base.destroy(); + + 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; + } + std.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); + index += 2; + // TimeDateStamp (u32), PointerToSymbolTable (u32), NumberOfSymbols (u32) + std.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 = 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.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; + self.text_section_virtual_address = 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 = std.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); + index += 2; + + // Characteristics + var characteristics: u16 = std.coff.IMAGE_FILE_DEBUG_STRIPPED | std.coff.IMAGE_FILE_RELOCS_STRIPPED; // TODO Remove debug info stripped flag when necessary + if (options.output_mode == .Exe) { + characteristics |= std.coff.IMAGE_FILE_EXECUTABLE_IMAGE; + } + switch (self.ptr_width) { + .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); + 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; + std.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); + index += 2; + + // SizeOfCode (UNUSED, u32), SizeOfInitializedData (u32), SizeOfUninitializedData (u32), AddressOfEntryPoint (u32), BaseOfCode (UNUSED, u32) + std.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); + index += 4; + + // Image base address + std.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); + index += 8; + } + + // Section alignment + std.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); + index += 4; + // Required OS version, 6.0 is vista + std.mem.writeIntLittle(u16, hdr_data[index..][0..2], 6); + index += 2; + std.mem.writeIntLittle(u16, hdr_data[index..][0..2], 0); + index += 2; + // Image version + std.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); + index += 2; + std.mem.writeIntLittle(u16, hdr_data[index..][0..2], 0); + index += 2; + // Reserved zeroes (u32) + std.mem.set(u8, hdr_data[index..][0..4], 0); + index += 4; + std.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); + index += 4; + // CheckSum (u32) + std.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); + index += 2; + // DLL characteristics + std.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); + index += 4; + std.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); + index += 4; + std.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); + index += 8; + std.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); + index += 8; + std.mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000); + index += 8; + }, + } + + // Reserved zeroes + std.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); + index += 4; + // Initialize data directories to zero + std.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) + std.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); + index += 4; + } else { + std.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); + index += 4; + // File pointer to the start of the section + std.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); + 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); + 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); + index += 4; + // Virtual address (u32) + std.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); + index += 8; + } + // Size of raw data (u32) + std.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); + index += 4; + // Pointer to relocations (u32), PointerToLinenumbers (u32), NumberOfRelocations (u16), NumberOfLinenumbers (u16) + std.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_CODE | std.coff.IMAGE_SCN_MEM_EXECUTE | std.coff.IMAGE_SCN_MEM_READ | std.coff.IMAGE_SCN_MEM_WRITE, + ); + 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); + + return self; +} + +pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Coff { + const ptr_width: PtrWidth = switch (options.target.cpu.arch.ptrBitWidth()) { + 0...32 => .p32, + 33...64 => .p64, + else => return error.UnsupportedCOFFArchitecture, + }; + const self = try gpa.create(Coff); + self.* = .{ + .base = .{ + .tag = .coff, + .options = options, + .allocator = gpa, + .file = null, + }, + .ptr_width = ptr_width, + }; + return self; +} + +pub fn allocateDeclIndexes(self: *Coff, decl: *Module.Decl) !void { + try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1); + + if (self.offset_table_free_list.popOrNull()) |i| { + decl.link.coff.offset_table_index = i; + } else { + decl.link.coff.offset_table_index = @intCast(u32, self.offset_table.items.len); + _ = self.offset_table.addOneAssumeCapacity(); + + const entry_size = self.base.options.target.cpu.arch.ptrBitWidth() / 8; + if (self.offset_table.items.len > self.offset_table_size / entry_size) { + self.offset_table_size_dirty = true; + } + } + + self.offset_table.items[decl.link.coff.offset_table_index] = 0; +} + +fn allocateTextBlock(self: *Coff, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { + const new_block_min_capacity = new_block_size * allocation_padding; + + // We use these to indicate our intention to update metadata, placing the new block, + // 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 free_list_removal: ?usize = null; + + const 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 = std.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); + } else { + i += 1; + } + continue; + } + } else if (self.last_text_block) |last| { + const new_block_vaddr = std.mem.alignForwardGeneric(u64, last.getVAddr(self.*) + last.size, alignment); + block_placement = last; + break :blk new_block_vaddr; + } else { + break :blk self.text_section_virtual_address; + } + }; + + 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)); + 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); + 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); + try self.base.file.?.pwriteAll(&buf, self.section_table_offset + 40 + 8); + } + + self.text_section_size = needed_size; + self.text_section_size_dirty = true; + } + self.last_text_block = text_block; + } + 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 (text_block.next) |next| { + next.prev = text_block.prev; + } + + if (block_placement) |big_block| { + text_block.prev = big_block; + text_block.next = big_block.next; + big_block.next = text_block; + } else { + text_block.prev = null; + text_block.next = null; + } + if (free_list_removal) |i| { + _ = self.text_block_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 = std.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 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 {}; + } +} + +fn freeTextBlock(self: *Coff, text_block: *TextBlock) void { + 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); + continue; + } + if (self.text_block_free_list.items[i] == text_block.prev) { + already_have_free_list_node = true; + } + i += 1; + } + } + if (self.last_text_block == text_block) { + self.last_text_block = text_block.prev; + } + if (text_block.prev) |prev| { + prev.next = text_block.next; + + if (!already_have_free_list_node and prev.freeListEligible()) { + // 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 {}; + } + } + + 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; + std.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); + 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); + // 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 + + 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 + std.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); + 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 => { + std.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); + 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; + std.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); + try self.base.file.?.pwriteAll(&buf, offset_table_start + index * entry_size); + }, + else => unreachable, + } +} + +pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void { + // TODO COFF/PE debug information + // TODO Implement exports + const tracy = trace(@src()); + defer tracy.end(); + + var code_buffer = std.ArrayList(u8).init(self.base.allocator); + defer code_buffer.deinit(); + + const typed_value = decl.typed_value.most_recent.typed_value; + const res = try codegen.generateSymbol(&self.base, decl.src(), typed_value, &code_buffer, .none); + const code = switch (res) { + .externally_managed => |x| x, + .appended => code_buffer.items, + .fail => |em| { + decl.analysis = .codegen_failure; + try module.failed_decls.put(module.gpa, decl, em); + return; + }, + }; + + const required_alignment = typed_value.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 + !std.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); + log.debug("growing {} from 0x{x} to 0x{x}\n", .{ decl.name, curr_vaddr, vaddr }); + if (vaddr != curr_vaddr) { + log.debug(" (writing new offset table entry)\n", .{}); + self.offset_table.items[decl.link.coff.offset_table_index] = vaddr; + try self.writeOffsetTableEntry(decl.link.coff.offset_table_index); + } + } else if (code.len < curr_size) { + self.shrinkTextBlock(&decl.link.coff, code.len); + } + } else { + const vaddr = try self.allocateTextBlock(&decl.link.coff, code.len, required_alignment); + log.debug("allocated text block for {} at 0x{x} (size: {Bi})\n", .{ std.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); + } + + // 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) orelse &[0]*Module.Export{}; + return self.updateDeclExports(module, decl, decl_exports); +} + +pub fn freeDecl(self: *Coff, decl: *Module.Decl) void { + // 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 {}; +} + +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")) { + try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); + module.failed_exports.putAssumeCapacityNoClobber( + exp, + try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: ExportOptions.section", .{}), + ); + continue; + } + } + if (std.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); + module.failed_exports.putAssumeCapacityNoClobber( + exp, + try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: Exports other than '_start'", .{}), + ); + continue; + } + } +} + +pub fn flush(self: *Coff, comp: *Compilation) !void { + if (build_options.have_llvm and self.base.options.use_lld) { + return error.CoffLinkingWithLLDUnimplemented; + } else { + return self.flushModule(comp); + } +} + +pub fn flushModule(self: *Coff, comp: *Compilation) !void { + const tracy = trace(@src()); + defer tracy.end(); + + 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); + 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); + var buf: [4]u8 = undefined; + std.mem.writeIntLittle(u32, &buf, new_size_of_image); + try self.base.file.?.pwriteAll(&buf, self.optional_header_offset + 56); + self.size_of_image_dirty = false; + } + + if (self.entry_addr == null and self.base.options.output_mode == .Exe) { + log.debug("flushing. no_entry_point_found = true\n", .{}); + self.error_flags.no_entry_point_found = true; + } else { + log.debug("flushing. no_entry_point_found = false\n", .{}); + self.error_flags.no_entry_point_found = false; + + if (self.base.options.output_mode == .Exe) { + // Write AddressOfEntryPoint + var buf: [4]u8 = undefined; + std.mem.writeIntLittle(u32, &buf, self.entry_addr.?); + try self.base.file.?.pwriteAll(&buf, self.optional_header_offset + 16); + } + } +} + +pub fn getDeclVAddr(self: *Coff, decl: *const Module.Decl) u64 { + return self.text_section_virtual_address + decl.link.coff.text_offset; +} + +pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !void { + // TODO Implement this +} + +pub fn deinit(self: *Coff) void { + 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); +} diff --git a/src/link/Elf.zig b/src/link/Elf.zig new file mode 100644 index 0000000000..98deefb3bf --- /dev/null +++ b/src/link/Elf.zig @@ -0,0 +1,3059 @@ +const Elf = @This(); + +const std = @import("std"); +const mem = std.mem; +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const fs = std.fs; +const elf = std.elf; +const log = std.log.scoped(.link); +const DW = std.dwarf; +const leb128 = std.debug.leb; + +const ir = @import("../ir.zig"); +const Module = @import("../Module.zig"); +const Compilation = @import("../Compilation.zig"); +const codegen = @import("../codegen.zig"); +const trace = @import("../tracy.zig").trace; +const Package = @import("../Package.zig"); +const Value = @import("../value.zig").Value; +const Type = @import("../type.zig").Type; +const link = @import("../link.zig"); +const File = link.File; +const build_options = @import("build_options"); +const target_util = @import("../target.zig"); +const glibc = @import("../glibc.zig"); + +const default_entry_addr = 0x8000000; + +// TODO Turn back on zig fmt when https://github.com/ziglang/zig/issues/5948 is implemented. +// zig fmt: off + +pub const base_tag: File.Tag = .elf; + +base: File, + +ptr_width: PtrWidth, + +/// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. +/// Same order as in the file. +sections: std.ArrayListUnmanaged(elf.Elf64_Shdr) = std.ArrayListUnmanaged(elf.Elf64_Shdr){}, +shdr_table_offset: ?u64 = null, + +/// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. +/// Same order as in the file. +program_headers: std.ArrayListUnmanaged(elf.Elf64_Phdr) = std.ArrayListUnmanaged(elf.Elf64_Phdr){}, +phdr_table_offset: ?u64 = null, +/// The index into the program headers of a PT_LOAD program header with Read and Execute flags +phdr_load_re_index: ?u16 = null, +/// The index into the program headers of the global offset table. +/// It needs PT_LOAD and Read flags. +phdr_got_index: ?u16 = null, +entry_addr: ?u64 = null, + +debug_strtab: std.ArrayListUnmanaged(u8) = std.ArrayListUnmanaged(u8){}, +shstrtab: std.ArrayListUnmanaged(u8) = std.ArrayListUnmanaged(u8){}, +shstrtab_index: ?u16 = null, + +text_section_index: ?u16 = null, +symtab_section_index: ?u16 = null, +got_section_index: ?u16 = null, +debug_info_section_index: ?u16 = null, +debug_abbrev_section_index: ?u16 = null, +debug_str_section_index: ?u16 = null, +debug_aranges_section_index: ?u16 = null, +debug_line_section_index: ?u16 = null, + +debug_abbrev_table_offset: ?u64 = null, + +/// The same order as in the file. ELF requires global symbols to all be after the +/// local symbols, they cannot be mixed. So we must buffer all the global symbols and +/// write them at the end. These are only the local symbols. The length of this array +/// is the value used for sh_info in the .symtab section. +local_symbols: std.ArrayListUnmanaged(elf.Elf64_Sym) = .{}, +global_symbols: std.ArrayListUnmanaged(elf.Elf64_Sym) = .{}, + +local_symbol_free_list: std.ArrayListUnmanaged(u32) = .{}, +global_symbol_free_list: std.ArrayListUnmanaged(u32) = .{}, +offset_table_free_list: std.ArrayListUnmanaged(u32) = .{}, + +/// Same order as in the file. The value is the absolute vaddr value. +/// If the vaddr of the executable program header changes, the entire +/// offset table needs to be rewritten. +offset_table: std.ArrayListUnmanaged(u64) = .{}, + +phdr_table_dirty: bool = false, +shdr_table_dirty: bool = false, +shstrtab_dirty: bool = false, +debug_strtab_dirty: bool = false, +offset_table_count_dirty: bool = false, +debug_abbrev_section_dirty: bool = false, +debug_aranges_section_dirty: bool = false, + +debug_info_header_dirty: bool = false, +debug_line_header_dirty: bool = false, + +error_flags: File.ErrorFlags = File.ErrorFlags{}, + +/// A list of text blocks 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. +/// +/// A text block has surplus capacity when its overcapacity value is greater than +/// minimum_text_block_size * alloc_num / alloc_den. 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 * alloc_num / alloc_den. +/// +/// 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 text block, which will have ideal capacity, and then grow it +/// by 1 byte. It will then have -1 overcapacity. +text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = .{}, +last_text_block: ?*TextBlock = null, + +/// A list of `SrcFn` whose Line Number Programs have surplus capacity. +/// This is the same concept as `text_block_free_list`; see those doc comments. +dbg_line_fn_free_list: std.AutoHashMapUnmanaged(*SrcFn, void) = .{}, +dbg_line_fn_first: ?*SrcFn = null, +dbg_line_fn_last: ?*SrcFn = null, + +/// A list of `TextBlock` whose corresponding .debug_info tags have surplus capacity. +/// This is the same concept as `text_block_free_list`; see those doc comments. +dbg_info_decl_free_list: std.AutoHashMapUnmanaged(*TextBlock, void) = .{}, +dbg_info_decl_first: ?*TextBlock = null, +dbg_info_decl_last: ?*TextBlock = null, + +/// `alloc_num / alloc_den` is the factor of padding when allocating. +const alloc_num = 4; +const alloc_den = 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; +const min_text_capacity = minimum_text_block_size * alloc_num / alloc_den; + +pub const PtrWidth = enum { p32, p64 }; + +pub const TextBlock = struct { + /// 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. + local_sym_index: 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, + + /// Previous/next linked list pointers. This value is `next ^ prev`. + /// This is the linked list node for this Decl's corresponding .debug_info tag. + dbg_info_prev: ?*TextBlock, + dbg_info_next: ?*TextBlock, + /// Offset into .debug_info pointing to the tag for this Decl. + dbg_info_off: u32, + /// Size of the .debug_info tag for this Decl, not including padding. + dbg_info_len: u32, + + pub const empty = TextBlock{ + .local_sym_index = 0, + .offset_table_index = undefined, + .prev = null, + .next = null, + .dbg_info_prev = null, + .dbg_info_next = null, + .dbg_info_off = undefined, + .dbg_info_len = undefined, + }; + + /// Returns how much room there is to grow in virtual address space. + /// File offset relocation happens transparently, so it is not included in + /// this calculation. + fn capacity(self: TextBlock, elf_file: Elf) u64 { + const self_sym = elf_file.local_symbols.items[self.local_sym_index]; + if (self.next) |next| { + const next_sym = elf_file.local_symbols.items[next.local_sym_index]; + return next_sym.st_value - self_sym.st_value; + } else { + // We are the last block. The capacity is limited only by virtual address space. + return std.math.maxInt(u32) - self_sym.st_value; + } + } + + fn freeListEligible(self: TextBlock, elf_file: Elf) bool { + // No need to keep a free list node for the last block. + const next = self.next orelse return false; + const self_sym = elf_file.local_symbols.items[self.local_sym_index]; + const next_sym = elf_file.local_symbols.items[next.local_sym_index]; + const cap = next_sym.st_value - self_sym.st_value; + const ideal_cap = self_sym.st_size * alloc_num / alloc_den; + if (cap <= ideal_cap) return false; + const surplus = cap - ideal_cap; + return surplus >= min_text_capacity; + } +}; + +pub const Export = struct { + sym_index: ?u32 = null, +}; + +pub const SrcFn = struct { + /// Offset from the beginning of the Debug Line Program header that contains this function. + off: u32, + /// Size of the line number program component belonging to this function, not + /// including padding. + len: u32, + + /// Points to the previous and next neighbors, based on the offset from .debug_line. + /// This can be used to find, for example, the capacity of this `SrcFn`. + prev: ?*SrcFn, + next: ?*SrcFn, + + pub const empty: SrcFn = .{ + .off = 0, + .len = 0, + .prev = null, + .next = null, + }; +}; + +pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Elf { + assert(options.object_format == .elf); + + if (options.use_llvm) return error.LLVMBackendUnimplementedForELF; // TODO + + const file = try options.directory.handle.createFile(sub_path, .{ + .truncate = false, + .read = true, + .mode = link.determineMode(options), + }); + errdefer file.close(); + + const self = try createEmpty(allocator, options); + errdefer self.base.destroy(); + + self.base.file = file; + self.shdr_table_dirty = true; + + // Index 0 is always a null symbol. + try self.local_symbols.append(allocator, .{ + .st_name = 0, + .st_info = 0, + .st_other = 0, + .st_shndx = 0, + .st_value = 0, + .st_size = 0, + }); + + // There must always be a null section in index 0 + try self.sections.append(allocator, .{ + .sh_name = 0, + .sh_type = elf.SHT_NULL, + .sh_flags = 0, + .sh_addr = 0, + .sh_offset = 0, + .sh_size = 0, + .sh_link = 0, + .sh_info = 0, + .sh_addralign = 0, + .sh_entsize = 0, + }); + + try self.populateMissingMetadata(); + + return self; +} + +pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Elf { + const ptr_width: PtrWidth = switch (options.target.cpu.arch.ptrBitWidth()) { + 0 ... 32 => .p32, + 33 ... 64 => .p64, + else => return error.UnsupportedELFArchitecture, + }; + const self = try gpa.create(Elf); + self.* = .{ + .base = .{ + .tag = .elf, + .options = options, + .allocator = gpa, + .file = null, + }, + .ptr_width = ptr_width, + }; + return self; +} + +pub fn deinit(self: *Elf) void { + self.sections.deinit(self.base.allocator); + self.program_headers.deinit(self.base.allocator); + self.shstrtab.deinit(self.base.allocator); + self.debug_strtab.deinit(self.base.allocator); + self.local_symbols.deinit(self.base.allocator); + self.global_symbols.deinit(self.base.allocator); + self.global_symbol_free_list.deinit(self.base.allocator); + self.local_symbol_free_list.deinit(self.base.allocator); + self.offset_table_free_list.deinit(self.base.allocator); + self.text_block_free_list.deinit(self.base.allocator); + self.dbg_line_fn_free_list.deinit(self.base.allocator); + self.dbg_info_decl_free_list.deinit(self.base.allocator); + self.offset_table.deinit(self.base.allocator); +} + +pub fn getDeclVAddr(self: *Elf, decl: *const Module.Decl) u64 { + assert(decl.link.elf.local_sym_index != 0); + return self.local_symbols.items[decl.link.elf.local_sym_index].st_value; +} + +fn getDebugLineProgramOff(self: Elf) u32 { + return self.dbg_line_fn_first.?.off; +} + +fn getDebugLineProgramEnd(self: Elf) u32 { + return self.dbg_line_fn_last.?.off + self.dbg_line_fn_last.?.len; +} + +/// Returns end pos of collision, if any. +fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 { + const small_ptr = self.ptr_width == .p32; + const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr); + if (start < ehdr_size) + return ehdr_size; + + const end = start + satMul(size, alloc_num) / alloc_den; + + if (self.shdr_table_offset) |off| { + const shdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Shdr) else @sizeOf(elf.Elf64_Shdr); + const tight_size = self.sections.items.len * shdr_size; + const increased_size = satMul(tight_size, alloc_num) / alloc_den; + const test_end = off + increased_size; + if (end > off and start < test_end) { + return test_end; + } + } + + if (self.phdr_table_offset) |off| { + const phdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Phdr) else @sizeOf(elf.Elf64_Phdr); + const tight_size = self.sections.items.len * phdr_size; + const increased_size = satMul(tight_size, alloc_num) / alloc_den; + const test_end = off + increased_size; + if (end > off and start < test_end) { + return test_end; + } + } + + for (self.sections.items) |section| { + const increased_size = satMul(section.sh_size, alloc_num) / alloc_den; + const test_end = section.sh_offset + increased_size; + if (end > section.sh_offset and start < test_end) { + return test_end; + } + } + for (self.program_headers.items) |program_header| { + const increased_size = satMul(program_header.p_filesz, alloc_num) / alloc_den; + const test_end = program_header.p_offset + increased_size; + if (end > program_header.p_offset and start < test_end) { + return test_end; + } + } + return null; +} + +fn allocatedSize(self: *Elf, start: u64) u64 { + if (start == 0) + return 0; + var min_pos: u64 = std.math.maxInt(u64); + if (self.shdr_table_offset) |off| { + if (off > start and off < min_pos) min_pos = off; + } + if (self.phdr_table_offset) |off| { + if (off > start and off < min_pos) min_pos = off; + } + for (self.sections.items) |section| { + if (section.sh_offset <= start) continue; + if (section.sh_offset < min_pos) min_pos = section.sh_offset; + } + for (self.program_headers.items) |program_header| { + if (program_header.p_offset <= start) continue; + if (program_header.p_offset < min_pos) min_pos = program_header.p_offset; + } + return min_pos - start; +} + +fn findFreeSpace(self: *Elf, object_size: u64, min_alignment: u16) u64 { + var start: u64 = 0; + while (self.detectAllocCollision(start, object_size)) |item_end| { + start = mem.alignForwardGeneric(u64, item_end, min_alignment); + } + return start; +} + +/// TODO Improve this to use a table. +fn makeString(self: *Elf, bytes: []const u8) !u32 { + try self.shstrtab.ensureCapacity(self.base.allocator, self.shstrtab.items.len + bytes.len + 1); + const result = self.shstrtab.items.len; + self.shstrtab.appendSliceAssumeCapacity(bytes); + self.shstrtab.appendAssumeCapacity(0); + return @intCast(u32, result); +} + +/// TODO Improve this to use a table. +fn makeDebugString(self: *Elf, bytes: []const u8) !u32 { + try self.debug_strtab.ensureCapacity(self.base.allocator, self.debug_strtab.items.len + bytes.len + 1); + const result = self.debug_strtab.items.len; + self.debug_strtab.appendSliceAssumeCapacity(bytes); + self.debug_strtab.appendAssumeCapacity(0); + return @intCast(u32, result); +} + +fn getString(self: *Elf, str_off: u32) []const u8 { + assert(str_off < self.shstrtab.items.len); + return mem.spanZ(@ptrCast([*:0]const u8, self.shstrtab.items.ptr + str_off)); +} + +fn updateString(self: *Elf, old_str_off: u32, new_name: []const u8) !u32 { + const existing_name = self.getString(old_str_off); + if (mem.eql(u8, existing_name, new_name)) { + return old_str_off; + } + return self.makeString(new_name); +} + +pub fn populateMissingMetadata(self: *Elf) !void { + const small_ptr = switch (self.ptr_width) { + .p32 => true, + .p64 => false, + }; + const ptr_size: u8 = self.ptrWidthBytes(); + if (self.phdr_load_re_index == null) { + self.phdr_load_re_index = @intCast(u16, self.program_headers.items.len); + const file_size = self.base.options.program_code_size_hint; + const p_align = 0x1000; + const off = self.findFreeSpace(file_size, p_align); + log.debug("found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + const entry_addr: u64 = self.entry_addr orelse if (self.base.options.target.cpu.arch == .spu_2) @as(u64, 0) else default_entry_addr; + try self.program_headers.append(self.base.allocator, .{ + .p_type = elf.PT_LOAD, + .p_offset = off, + .p_filesz = file_size, + .p_vaddr = entry_addr, + .p_paddr = entry_addr, + .p_memsz = file_size, + .p_align = p_align, + .p_flags = elf.PF_X | elf.PF_R, + }); + self.entry_addr = null; + self.phdr_table_dirty = true; + } + if (self.phdr_got_index == null) { + self.phdr_got_index = @intCast(u16, self.program_headers.items.len); + const file_size = @as(u64, ptr_size) * self.base.options.symbol_count_hint; + // We really only need ptr alignment but since we are using PROGBITS, linux requires + // page align. + const p_align = if (self.base.options.target.os.tag == .linux) 0x1000 else @as(u16, ptr_size); + const off = self.findFreeSpace(file_size, p_align); + log.debug("found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + // TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at. + // we'll need to re-use that function anyway, in case the GOT grows and overlaps something + // else in virtual memory. + const got_addr: u32 = if (self.base.options.target.cpu.arch.ptrBitWidth() >= 32) 0x4000000 else 0x8000; + try self.program_headers.append(self.base.allocator, .{ + .p_type = elf.PT_LOAD, + .p_offset = off, + .p_filesz = file_size, + .p_vaddr = got_addr, + .p_paddr = got_addr, + .p_memsz = file_size, + .p_align = p_align, + .p_flags = elf.PF_R, + }); + self.phdr_table_dirty = true; + } + if (self.shstrtab_index == null) { + self.shstrtab_index = @intCast(u16, self.sections.items.len); + assert(self.shstrtab.items.len == 0); + try self.shstrtab.append(self.base.allocator, 0); // need a 0 at position 0 + const off = self.findFreeSpace(self.shstrtab.items.len, 1); + log.debug("found shstrtab free space 0x{x} to 0x{x}\n", .{ off, off + self.shstrtab.items.len }); + try self.sections.append(self.base.allocator, .{ + .sh_name = try self.makeString(".shstrtab"), + .sh_type = elf.SHT_STRTAB, + .sh_flags = 0, + .sh_addr = 0, + .sh_offset = off, + .sh_size = self.shstrtab.items.len, + .sh_link = 0, + .sh_info = 0, + .sh_addralign = 1, + .sh_entsize = 0, + }); + self.shstrtab_dirty = true; + self.shdr_table_dirty = true; + } + if (self.text_section_index == null) { + self.text_section_index = @intCast(u16, self.sections.items.len); + const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; + + try self.sections.append(self.base.allocator, .{ + .sh_name = try self.makeString(".text"), + .sh_type = elf.SHT_PROGBITS, + .sh_flags = elf.SHF_ALLOC | elf.SHF_EXECINSTR, + .sh_addr = phdr.p_vaddr, + .sh_offset = phdr.p_offset, + .sh_size = phdr.p_filesz, + .sh_link = 0, + .sh_info = 0, + .sh_addralign = phdr.p_align, + .sh_entsize = 0, + }); + self.shdr_table_dirty = true; + } + if (self.got_section_index == null) { + self.got_section_index = @intCast(u16, self.sections.items.len); + const phdr = &self.program_headers.items[self.phdr_got_index.?]; + + try self.sections.append(self.base.allocator, .{ + .sh_name = try self.makeString(".got"), + .sh_type = elf.SHT_PROGBITS, + .sh_flags = elf.SHF_ALLOC, + .sh_addr = phdr.p_vaddr, + .sh_offset = phdr.p_offset, + .sh_size = phdr.p_filesz, + .sh_link = 0, + .sh_info = 0, + .sh_addralign = phdr.p_align, + .sh_entsize = 0, + }); + self.shdr_table_dirty = true; + } + if (self.symtab_section_index == null) { + self.symtab_section_index = @intCast(u16, self.sections.items.len); + const min_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym); + const each_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym); + const file_size = self.base.options.symbol_count_hint * each_size; + const off = self.findFreeSpace(file_size, min_align); + log.debug("found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + + try self.sections.append(self.base.allocator, .{ + .sh_name = try self.makeString(".symtab"), + .sh_type = elf.SHT_SYMTAB, + .sh_flags = 0, + .sh_addr = 0, + .sh_offset = off, + .sh_size = file_size, + // The section header index of the associated string table. + .sh_link = self.shstrtab_index.?, + .sh_info = @intCast(u32, self.local_symbols.items.len), + .sh_addralign = min_align, + .sh_entsize = each_size, + }); + self.shdr_table_dirty = true; + try self.writeSymbol(0); + } + if (self.debug_str_section_index == null) { + self.debug_str_section_index = @intCast(u16, self.sections.items.len); + assert(self.debug_strtab.items.len == 0); + try self.sections.append(self.base.allocator, .{ + .sh_name = try self.makeString(".debug_str"), + .sh_type = elf.SHT_PROGBITS, + .sh_flags = elf.SHF_MERGE | elf.SHF_STRINGS, + .sh_addr = 0, + .sh_offset = 0, + .sh_size = self.debug_strtab.items.len, + .sh_link = 0, + .sh_info = 0, + .sh_addralign = 1, + .sh_entsize = 1, + }); + self.debug_strtab_dirty = true; + self.shdr_table_dirty = true; + } + if (self.debug_info_section_index == null) { + self.debug_info_section_index = @intCast(u16, self.sections.items.len); + + const file_size_hint = 200; + const p_align = 1; + const off = self.findFreeSpace(file_size_hint, p_align); + log.debug("found .debug_info free space 0x{x} to 0x{x}\n", .{ + off, + off + file_size_hint, + }); + try self.sections.append(self.base.allocator, .{ + .sh_name = try self.makeString(".debug_info"), + .sh_type = elf.SHT_PROGBITS, + .sh_flags = 0, + .sh_addr = 0, + .sh_offset = off, + .sh_size = file_size_hint, + .sh_link = 0, + .sh_info = 0, + .sh_addralign = p_align, + .sh_entsize = 0, + }); + self.shdr_table_dirty = true; + self.debug_info_header_dirty = true; + } + if (self.debug_abbrev_section_index == null) { + self.debug_abbrev_section_index = @intCast(u16, self.sections.items.len); + + const file_size_hint = 128; + const p_align = 1; + const off = self.findFreeSpace(file_size_hint, p_align); + log.debug("found .debug_abbrev free space 0x{x} to 0x{x}\n", .{ + off, + off + file_size_hint, + }); + try self.sections.append(self.base.allocator, .{ + .sh_name = try self.makeString(".debug_abbrev"), + .sh_type = elf.SHT_PROGBITS, + .sh_flags = 0, + .sh_addr = 0, + .sh_offset = off, + .sh_size = file_size_hint, + .sh_link = 0, + .sh_info = 0, + .sh_addralign = p_align, + .sh_entsize = 0, + }); + self.shdr_table_dirty = true; + self.debug_abbrev_section_dirty = true; + } + if (self.debug_aranges_section_index == null) { + self.debug_aranges_section_index = @intCast(u16, self.sections.items.len); + + const file_size_hint = 160; + const p_align = 16; + const off = self.findFreeSpace(file_size_hint, p_align); + log.debug("found .debug_aranges free space 0x{x} to 0x{x}\n", .{ + off, + off + file_size_hint, + }); + try self.sections.append(self.base.allocator, .{ + .sh_name = try self.makeString(".debug_aranges"), + .sh_type = elf.SHT_PROGBITS, + .sh_flags = 0, + .sh_addr = 0, + .sh_offset = off, + .sh_size = file_size_hint, + .sh_link = 0, + .sh_info = 0, + .sh_addralign = p_align, + .sh_entsize = 0, + }); + self.shdr_table_dirty = true; + self.debug_aranges_section_dirty = true; + } + if (self.debug_line_section_index == null) { + self.debug_line_section_index = @intCast(u16, self.sections.items.len); + + const file_size_hint = 250; + const p_align = 1; + const off = self.findFreeSpace(file_size_hint, p_align); + log.debug("found .debug_line free space 0x{x} to 0x{x}\n", .{ + off, + off + file_size_hint, + }); + try self.sections.append(self.base.allocator, .{ + .sh_name = try self.makeString(".debug_line"), + .sh_type = elf.SHT_PROGBITS, + .sh_flags = 0, + .sh_addr = 0, + .sh_offset = off, + .sh_size = file_size_hint, + .sh_link = 0, + .sh_info = 0, + .sh_addralign = p_align, + .sh_entsize = 0, + }); + self.shdr_table_dirty = true; + self.debug_line_header_dirty = true; + } + const shsize: u64 = switch (self.ptr_width) { + .p32 => @sizeOf(elf.Elf32_Shdr), + .p64 => @sizeOf(elf.Elf64_Shdr), + }; + const shalign: u16 = switch (self.ptr_width) { + .p32 => @alignOf(elf.Elf32_Shdr), + .p64 => @alignOf(elf.Elf64_Shdr), + }; + if (self.shdr_table_offset == null) { + self.shdr_table_offset = self.findFreeSpace(self.sections.items.len * shsize, shalign); + self.shdr_table_dirty = true; + } + const phsize: u64 = switch (self.ptr_width) { + .p32 => @sizeOf(elf.Elf32_Phdr), + .p64 => @sizeOf(elf.Elf64_Phdr), + }; + const phalign: u16 = switch (self.ptr_width) { + .p32 => @alignOf(elf.Elf32_Phdr), + .p64 => @alignOf(elf.Elf64_Phdr), + }; + if (self.phdr_table_offset == null) { + self.phdr_table_offset = self.findFreeSpace(self.program_headers.items.len * phsize, phalign); + self.phdr_table_dirty = true; + } + { + // Iterate over symbols, populating free_list and last_text_block. + if (self.local_symbols.items.len != 1) { + @panic("TODO implement setting up free_list and last_text_block from existing ELF file"); + } + // We are starting with an empty file. The default values are correct, null and empty list. + } +} + +pub const abbrev_compile_unit = 1; +pub const abbrev_subprogram = 2; +pub const abbrev_subprogram_retvoid = 3; +pub const abbrev_base_type = 4; +pub const abbrev_pad1 = 5; +pub const abbrev_parameter = 6; + +pub fn flush(self: *Elf, comp: *Compilation) !void { + if (build_options.have_llvm and self.base.options.use_lld) { + return self.linkWithLLD(comp); + } else { + switch (self.base.options.effectiveOutputMode()) { + .Exe, .Obj => {}, + .Lib => return error.TODOImplementWritingLibFiles, + } + return self.flushModule(comp); + } +} + +pub fn flushModule(self: *Elf, comp: *Compilation) !void { + const tracy = trace(@src()); + defer tracy.end(); + + // TODO This linker code currently assumes there is only 1 compilation unit and it corresponds to the + // Zig source code. + const module = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented; + + const target_endian = self.base.options.target.cpu.arch.endian(); + const foreign_endian = target_endian != std.Target.current.cpu.arch.endian(); + const ptr_width_bytes: u8 = self.ptrWidthBytes(); + const init_len_size: usize = switch (self.ptr_width) { + .p32 => 4, + .p64 => 12, + }; + + // Unfortunately these have to be buffered and done at the end because ELF does not allow + // mixing local and global symbols within a symbol table. + try self.writeAllGlobalSymbols(); + + if (self.debug_abbrev_section_dirty) { + const debug_abbrev_sect = &self.sections.items[self.debug_abbrev_section_index.?]; + + // These are LEB encoded but since the values are all less than 127 + // we can simply append these bytes. + const abbrev_buf = [_]u8{ + abbrev_compile_unit, DW.TAG_compile_unit, DW.CHILDREN_yes, // header + DW.AT_stmt_list, DW.FORM_sec_offset, DW.AT_low_pc, + DW.FORM_addr, DW.AT_high_pc, DW.FORM_addr, + DW.AT_name, DW.FORM_strp, DW.AT_comp_dir, + DW.FORM_strp, DW.AT_producer, DW.FORM_strp, + DW.AT_language, DW.FORM_data2, 0, + 0, // table sentinel + abbrev_subprogram, DW.TAG_subprogram, + DW.CHILDREN_yes, // header + DW.AT_low_pc, DW.FORM_addr, + DW.AT_high_pc, DW.FORM_data4, DW.AT_type, + DW.FORM_ref4, DW.AT_name, DW.FORM_string, + 0, 0, // table sentinel + abbrev_subprogram_retvoid, + DW.TAG_subprogram, DW.CHILDREN_yes, // header + DW.AT_low_pc, + DW.FORM_addr, DW.AT_high_pc, DW.FORM_data4, + DW.AT_name, DW.FORM_string, 0, + 0, // table sentinel + abbrev_base_type, DW.TAG_base_type, + DW.CHILDREN_no, // header + DW.AT_encoding, DW.FORM_data1, + DW.AT_byte_size, DW.FORM_data1, DW.AT_name, + DW.FORM_string, 0, 0, // table sentinel + + abbrev_pad1, DW.TAG_unspecified_type, DW.CHILDREN_no, // header + 0, 0, // table sentinel + abbrev_parameter, + DW.TAG_formal_parameter, DW.CHILDREN_no, // header + DW.AT_location, + DW.FORM_exprloc, DW.AT_type, DW.FORM_ref4, + DW.AT_name, DW.FORM_string, 0, + 0, // table sentinel + 0, 0, + 0, // section sentinel + }; + + const needed_size = abbrev_buf.len; + const allocated_size = self.allocatedSize(debug_abbrev_sect.sh_offset); + if (needed_size > allocated_size) { + debug_abbrev_sect.sh_size = 0; // free the space + debug_abbrev_sect.sh_offset = self.findFreeSpace(needed_size, 1); + } + debug_abbrev_sect.sh_size = needed_size; + log.debug(".debug_abbrev start=0x{x} end=0x{x}\n", .{ + debug_abbrev_sect.sh_offset, + debug_abbrev_sect.sh_offset + needed_size, + }); + + const abbrev_offset = 0; + self.debug_abbrev_table_offset = abbrev_offset; + try self.base.file.?.pwriteAll(&abbrev_buf, debug_abbrev_sect.sh_offset + abbrev_offset); + if (!self.shdr_table_dirty) { + // Then it won't get written with the others and we need to do it. + try self.writeSectHeader(self.debug_abbrev_section_index.?); + } + + self.debug_abbrev_section_dirty = false; + } + + if (self.debug_info_header_dirty) debug_info: { + // If this value is null it means there is an error in the module; + // leave debug_info_header_dirty=true. + const first_dbg_info_decl = self.dbg_info_decl_first orelse break :debug_info; + const last_dbg_info_decl = self.dbg_info_decl_last.?; + const debug_info_sect = &self.sections.items[self.debug_info_section_index.?]; + + var di_buf = std.ArrayList(u8).init(self.base.allocator); + defer di_buf.deinit(); + + // We have a function to compute the upper bound size, because it's needed + // for determining where to put the offset of the first `LinkBlock`. + try di_buf.ensureCapacity(self.dbgInfoNeededHeaderBytes()); + + // initial length - length of the .debug_info contribution for this compilation unit, + // not including the initial length itself. + // We have to come back and write it later after we know the size. + const after_init_len = di_buf.items.len + init_len_size; + // +1 for the final 0 that ends the compilation unit children. + const dbg_info_end = last_dbg_info_decl.dbg_info_off + last_dbg_info_decl.dbg_info_len + 1; + const init_len = dbg_info_end - after_init_len; + switch (self.ptr_width) { + .p32 => { + mem.writeInt(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, init_len), target_endian); + }, + .p64 => { + di_buf.appendNTimesAssumeCapacity(0xff, 4); + mem.writeInt(u64, di_buf.addManyAsArrayAssumeCapacity(8), init_len, target_endian); + }, + } + mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), 4, target_endian); // DWARF version + const abbrev_offset = self.debug_abbrev_table_offset.?; + switch (self.ptr_width) { + .p32 => { + mem.writeInt(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, abbrev_offset), target_endian); + di_buf.appendAssumeCapacity(4); // address size + }, + .p64 => { + mem.writeInt(u64, di_buf.addManyAsArrayAssumeCapacity(8), abbrev_offset, target_endian); + di_buf.appendAssumeCapacity(8); // address size + }, + } + // Write the form for the compile unit, which must match the abbrev table above. + const name_strp = try self.makeDebugString(module.root_pkg.root_src_path); + const comp_dir_strp = try self.makeDebugString(module.root_pkg.root_src_directory.path.?); + const producer_strp = try self.makeDebugString(link.producer_string); + // Currently only one compilation unit is supported, so the address range is simply + // identical to the main program header virtual address and memory size. + const text_phdr = &self.program_headers.items[self.phdr_load_re_index.?]; + const low_pc = text_phdr.p_vaddr; + const high_pc = text_phdr.p_vaddr + text_phdr.p_memsz; + + di_buf.appendAssumeCapacity(abbrev_compile_unit); + self.writeDwarfAddrAssumeCapacity(&di_buf, 0); // DW.AT_stmt_list, DW.FORM_sec_offset + self.writeDwarfAddrAssumeCapacity(&di_buf, low_pc); + self.writeDwarfAddrAssumeCapacity(&di_buf, high_pc); + self.writeDwarfAddrAssumeCapacity(&di_buf, name_strp); + self.writeDwarfAddrAssumeCapacity(&di_buf, comp_dir_strp); + self.writeDwarfAddrAssumeCapacity(&di_buf, producer_strp); + // We are still waiting on dwarf-std.org to assign DW_LANG_Zig a number: + // http://dwarfstd.org/ShowIssue.php?issue=171115.1 + // Until then we say it is C99. + mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), DW.LANG_C99, target_endian); + + if (di_buf.items.len > first_dbg_info_decl.dbg_info_off) { + // Move the first N decls to the end to make more padding for the header. + @panic("TODO: handle .debug_info header exceeding its padding"); + } + const jmp_amt = first_dbg_info_decl.dbg_info_off - di_buf.items.len; + try self.pwriteDbgInfoNops(0, di_buf.items, jmp_amt, false, debug_info_sect.sh_offset); + self.debug_info_header_dirty = false; + } + + if (self.debug_aranges_section_dirty) { + const debug_aranges_sect = &self.sections.items[self.debug_aranges_section_index.?]; + + var di_buf = std.ArrayList(u8).init(self.base.allocator); + defer di_buf.deinit(); + + // Enough for all the data without resizing. When support for more compilation units + // is added, the size of this section will become more variable. + try di_buf.ensureCapacity(100); + + // initial length - length of the .debug_aranges contribution for this compilation unit, + // not including the initial length itself. + // We have to come back and write it later after we know the size. + const init_len_index = di_buf.items.len; + di_buf.items.len += init_len_size; + const after_init_len = di_buf.items.len; + mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), 2, target_endian); // version + // When more than one compilation unit is supported, this will be the offset to it. + // For now it is always at offset 0 in .debug_info. + self.writeDwarfAddrAssumeCapacity(&di_buf, 0); // .debug_info offset + di_buf.appendAssumeCapacity(ptr_width_bytes); // address_size + di_buf.appendAssumeCapacity(0); // segment_selector_size + + const end_header_offset = di_buf.items.len; + const begin_entries_offset = mem.alignForward(end_header_offset, ptr_width_bytes * 2); + di_buf.appendNTimesAssumeCapacity(0, begin_entries_offset - end_header_offset); + + // Currently only one compilation unit is supported, so the address range is simply + // identical to the main program header virtual address and memory size. + const text_phdr = &self.program_headers.items[self.phdr_load_re_index.?]; + self.writeDwarfAddrAssumeCapacity(&di_buf, text_phdr.p_vaddr); + self.writeDwarfAddrAssumeCapacity(&di_buf, text_phdr.p_memsz); + + // Sentinel. + self.writeDwarfAddrAssumeCapacity(&di_buf, 0); + self.writeDwarfAddrAssumeCapacity(&di_buf, 0); + + // Go back and populate the initial length. + const init_len = di_buf.items.len - after_init_len; + switch (self.ptr_width) { + .p32 => { + mem.writeInt(u32, di_buf.items[init_len_index..][0..4], @intCast(u32, init_len), target_endian); + }, + .p64 => { + // initial length - length of the .debug_aranges contribution for this compilation unit, + // not including the initial length itself. + di_buf.items[init_len_index..][0..4].* = [_]u8{ 0xff, 0xff, 0xff, 0xff }; + mem.writeInt(u64, di_buf.items[init_len_index + 4 ..][0..8], init_len, target_endian); + }, + } + + const needed_size = di_buf.items.len; + const allocated_size = self.allocatedSize(debug_aranges_sect.sh_offset); + if (needed_size > allocated_size) { + debug_aranges_sect.sh_size = 0; // free the space + debug_aranges_sect.sh_offset = self.findFreeSpace(needed_size, 16); + } + debug_aranges_sect.sh_size = needed_size; + log.debug(".debug_aranges start=0x{x} end=0x{x}\n", .{ + debug_aranges_sect.sh_offset, + debug_aranges_sect.sh_offset + needed_size, + }); + + try self.base.file.?.pwriteAll(di_buf.items, debug_aranges_sect.sh_offset); + if (!self.shdr_table_dirty) { + // Then it won't get written with the others and we need to do it. + try self.writeSectHeader(self.debug_aranges_section_index.?); + } + + self.debug_aranges_section_dirty = false; + } + if (self.debug_line_header_dirty) debug_line: { + if (self.dbg_line_fn_first == null) { + break :debug_line; // Error in module; leave debug_line_header_dirty=true. + } + const dbg_line_prg_off = self.getDebugLineProgramOff(); + const dbg_line_prg_end = self.getDebugLineProgramEnd(); + assert(dbg_line_prg_end != 0); + + const debug_line_sect = &self.sections.items[self.debug_line_section_index.?]; + + var di_buf = std.ArrayList(u8).init(self.base.allocator); + defer di_buf.deinit(); + + // The size of this header is variable, depending on the number of directories, + // files, and padding. We have a function to compute the upper bound size, however, + // because it's needed for determining where to put the offset of the first `SrcFn`. + try di_buf.ensureCapacity(self.dbgLineNeededHeaderBytes()); + + // initial length - length of the .debug_line contribution for this compilation unit, + // not including the initial length itself. + const after_init_len = di_buf.items.len + init_len_size; + const init_len = dbg_line_prg_end - after_init_len; + switch (self.ptr_width) { + .p32 => { + mem.writeInt(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, init_len), target_endian); + }, + .p64 => { + di_buf.appendNTimesAssumeCapacity(0xff, 4); + mem.writeInt(u64, di_buf.addManyAsArrayAssumeCapacity(8), init_len, target_endian); + }, + } + + mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), 4, target_endian); // version + + // Empirically, debug info consumers do not respect this field, or otherwise + // consider it to be an error when it does not point exactly to the end of the header. + // Therefore we rely on the NOP jump at the beginning of the Line Number Program for + // padding rather than this field. + const before_header_len = di_buf.items.len; + di_buf.items.len += ptr_width_bytes; // We will come back and write this. + const after_header_len = di_buf.items.len; + + const opcode_base = DW.LNS_set_isa + 1; + di_buf.appendSliceAssumeCapacity(&[_]u8{ + 1, // minimum_instruction_length + 1, // maximum_operations_per_instruction + 1, // default_is_stmt + 1, // line_base (signed) + 1, // line_range + opcode_base, + + // Standard opcode lengths. The number of items here is based on `opcode_base`. + // The value is the number of LEB128 operands the instruction takes. + 0, // `DW.LNS_copy` + 1, // `DW.LNS_advance_pc` + 1, // `DW.LNS_advance_line` + 1, // `DW.LNS_set_file` + 1, // `DW.LNS_set_column` + 0, // `DW.LNS_negate_stmt` + 0, // `DW.LNS_set_basic_block` + 0, // `DW.LNS_const_add_pc` + 1, // `DW.LNS_fixed_advance_pc` + 0, // `DW.LNS_set_prologue_end` + 0, // `DW.LNS_set_epilogue_begin` + 1, // `DW.LNS_set_isa` + + 0, // include_directories (none except the compilation unit cwd) + }); + // file_names[0] + di_buf.appendSliceAssumeCapacity(module.root_pkg.root_src_path); // relative path name + di_buf.appendSliceAssumeCapacity(&[_]u8{ + 0, // null byte for the relative path name + 0, // directory_index + 0, // mtime (TODO supply this) + 0, // file size bytes (TODO supply this) + 0, // file_names sentinel + }); + + const header_len = di_buf.items.len - after_header_len; + switch (self.ptr_width) { + .p32 => { + mem.writeInt(u32, di_buf.items[before_header_len..][0..4], @intCast(u32, header_len), target_endian); + }, + .p64 => { + mem.writeInt(u64, di_buf.items[before_header_len..][0..8], header_len, target_endian); + }, + } + + // We use NOPs because consumers empirically do not respect the header length field. + if (di_buf.items.len > dbg_line_prg_off) { + // Move the first N files to the end to make more padding for the header. + @panic("TODO: handle .debug_line header exceeding its padding"); + } + const jmp_amt = dbg_line_prg_off - di_buf.items.len; + try self.pwriteDbgLineNops(0, di_buf.items, jmp_amt, debug_line_sect.sh_offset); + self.debug_line_header_dirty = false; + } + + if (self.phdr_table_dirty) { + const phsize: u64 = switch (self.ptr_width) { + .p32 => @sizeOf(elf.Elf32_Phdr), + .p64 => @sizeOf(elf.Elf64_Phdr), + }; + const phalign: u16 = switch (self.ptr_width) { + .p32 => @alignOf(elf.Elf32_Phdr), + .p64 => @alignOf(elf.Elf64_Phdr), + }; + const allocated_size = self.allocatedSize(self.phdr_table_offset.?); + const needed_size = self.program_headers.items.len * phsize; + + if (needed_size > allocated_size) { + self.phdr_table_offset = null; // free the space + self.phdr_table_offset = self.findFreeSpace(needed_size, phalign); + } + + switch (self.ptr_width) { + .p32 => { + const buf = try self.base.allocator.alloc(elf.Elf32_Phdr, self.program_headers.items.len); + defer self.base.allocator.free(buf); + + for (buf) |*phdr, i| { + phdr.* = progHeaderTo32(self.program_headers.items[i]); + if (foreign_endian) { + bswapAllFields(elf.Elf32_Phdr, phdr); + } + } + try self.base.file.?.pwriteAll(mem.sliceAsBytes(buf), self.phdr_table_offset.?); + }, + .p64 => { + const buf = try self.base.allocator.alloc(elf.Elf64_Phdr, self.program_headers.items.len); + defer self.base.allocator.free(buf); + + for (buf) |*phdr, i| { + phdr.* = self.program_headers.items[i]; + if (foreign_endian) { + bswapAllFields(elf.Elf64_Phdr, phdr); + } + } + try self.base.file.?.pwriteAll(mem.sliceAsBytes(buf), self.phdr_table_offset.?); + }, + } + self.phdr_table_dirty = false; + } + + { + const shstrtab_sect = &self.sections.items[self.shstrtab_index.?]; + if (self.shstrtab_dirty or self.shstrtab.items.len != shstrtab_sect.sh_size) { + const allocated_size = self.allocatedSize(shstrtab_sect.sh_offset); + const needed_size = self.shstrtab.items.len; + + if (needed_size > allocated_size) { + shstrtab_sect.sh_size = 0; // free the space + shstrtab_sect.sh_offset = self.findFreeSpace(needed_size, 1); + } + shstrtab_sect.sh_size = needed_size; + log.debug("writing shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size }); + + try self.base.file.?.pwriteAll(self.shstrtab.items, shstrtab_sect.sh_offset); + if (!self.shdr_table_dirty) { + // Then it won't get written with the others and we need to do it. + try self.writeSectHeader(self.shstrtab_index.?); + } + self.shstrtab_dirty = false; + } + } + { + const debug_strtab_sect = &self.sections.items[self.debug_str_section_index.?]; + if (self.debug_strtab_dirty or self.debug_strtab.items.len != debug_strtab_sect.sh_size) { + const allocated_size = self.allocatedSize(debug_strtab_sect.sh_offset); + const needed_size = self.debug_strtab.items.len; + + if (needed_size > allocated_size) { + debug_strtab_sect.sh_size = 0; // free the space + debug_strtab_sect.sh_offset = self.findFreeSpace(needed_size, 1); + } + debug_strtab_sect.sh_size = needed_size; + log.debug("debug_strtab start=0x{x} end=0x{x}\n", .{ debug_strtab_sect.sh_offset, debug_strtab_sect.sh_offset + needed_size }); + + try self.base.file.?.pwriteAll(self.debug_strtab.items, debug_strtab_sect.sh_offset); + if (!self.shdr_table_dirty) { + // Then it won't get written with the others and we need to do it. + try self.writeSectHeader(self.debug_str_section_index.?); + } + self.debug_strtab_dirty = false; + } + } + if (self.shdr_table_dirty) { + const shsize: u64 = switch (self.ptr_width) { + .p32 => @sizeOf(elf.Elf32_Shdr), + .p64 => @sizeOf(elf.Elf64_Shdr), + }; + const shalign: u16 = switch (self.ptr_width) { + .p32 => @alignOf(elf.Elf32_Shdr), + .p64 => @alignOf(elf.Elf64_Shdr), + }; + const allocated_size = self.allocatedSize(self.shdr_table_offset.?); + const needed_size = self.sections.items.len * shsize; + + if (needed_size > allocated_size) { + self.shdr_table_offset = null; // free the space + self.shdr_table_offset = self.findFreeSpace(needed_size, shalign); + } + + switch (self.ptr_width) { + .p32 => { + const buf = try self.base.allocator.alloc(elf.Elf32_Shdr, self.sections.items.len); + defer self.base.allocator.free(buf); + + for (buf) |*shdr, i| { + shdr.* = sectHeaderTo32(self.sections.items[i]); + log.debug("writing section {}\n", .{shdr.*}); + if (foreign_endian) { + bswapAllFields(elf.Elf32_Shdr, shdr); + } + } + try self.base.file.?.pwriteAll(mem.sliceAsBytes(buf), self.shdr_table_offset.?); + }, + .p64 => { + const buf = try self.base.allocator.alloc(elf.Elf64_Shdr, self.sections.items.len); + defer self.base.allocator.free(buf); + + for (buf) |*shdr, i| { + shdr.* = self.sections.items[i]; + log.debug("writing section {}\n", .{shdr.*}); + if (foreign_endian) { + bswapAllFields(elf.Elf64_Shdr, shdr); + } + } + try self.base.file.?.pwriteAll(mem.sliceAsBytes(buf), self.shdr_table_offset.?); + }, + } + self.shdr_table_dirty = false; + } + if (self.entry_addr == null and self.base.options.effectiveOutputMode() == .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; + try self.writeElfHeader(); + } + + // The point of flush() is to commit changes, so in theory, nothing should + // be dirty after this. However, it is possible for some things to remain + // dirty because they fail to be written in the event of compile errors, + // such as debug_line_header_dirty and debug_info_header_dirty. + assert(!self.debug_abbrev_section_dirty); + assert(!self.debug_aranges_section_dirty); + assert(!self.phdr_table_dirty); + assert(!self.shdr_table_dirty); + assert(!self.shstrtab_dirty); + assert(!self.debug_strtab_dirty); +} + +fn linkWithLLD(self: *Elf, 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.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.fmt.allocPrint(arena, "{}.o", .{self.base.options.root_name}); + const full_obj_path = try 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; + + // 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 + // linked are in the hash that namespaces the directory we are outputting to. Therefore, + // we must hash those now, and the resulting digest will form the "id" of the linking + // job we are about to perform. + // After a successful link, we store the id in the metadata of a symlink named "id.txt" in + // the artifact directory. So, now, we check if this symlink exists, and if it matches + // our digest. If so, we can skip linking. Otherwise, we proceed with invoking LLD. + const id_symlink_basename = "lld.id"; + + // We are about to obtain this lock, so here we give other processes a chance first. + self.base.releaseLock(); + + var ch = comp.cache_parent.obtain(); + defer ch.deinit(); + + 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 have_dynamic_linker = self.base.options.link_libc and + self.base.options.link_mode == .Dynamic and is_exe_or_dyn_lib; + + try ch.addOptionalFile(self.base.options.linker_script); + try ch.addOptionalFile(self.base.options.version_script); + try ch.addListOfFiles(self.base.options.objects); + for (comp.c_object_table.items()) |entry| { + _ = try ch.addFile(entry.key.status.success.object_path, null); + } + try ch.addOptionalFile(module_obj_path); + // We can skip hashing libc and libc++ components that we are in charge of building from Zig + // installation sources because they are always a product of the compiler version + target information. + ch.hash.addOptional(self.base.options.stack_size_override); + ch.hash.addOptional(self.base.options.gc_sections); + ch.hash.add(self.base.options.eh_frame_hdr); + ch.hash.add(self.base.options.rdynamic); + ch.hash.addListOfBytes(self.base.options.extra_lld_args); + ch.hash.addListOfBytes(self.base.options.lib_dirs); + ch.hash.add(self.base.options.z_nodelete); + ch.hash.add(self.base.options.z_defs); + if (self.base.options.link_libc) { + ch.hash.add(self.base.options.libc_installation != null); + if (self.base.options.libc_installation) |libc_installation| { + ch.hash.addBytes(libc_installation.crt_dir.?); + } + if (have_dynamic_linker) { + ch.hash.addOptionalBytes(self.base.options.dynamic_linker); + } + } + if (is_dyn_lib) { + ch.hash.addOptionalBytes(self.base.options.override_soname); + ch.hash.addOptional(self.base.options.version); + } + ch.hash.addListOfBytes(self.base.options.system_libs); + ch.hash.addOptional(self.base.options.allow_shlib_undefined); + ch.hash.add(self.base.options.bind_global_refs_locally); + + // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. + _ = try ch.hit(); + const digest = ch.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("ELF 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("ELF LLD digest={} match - skipping invocation", .{digest}); + // Hot diggity dog! The output binary is already there. + self.base.lock = ch.toOwnedLock(); + return; + } + log.debug("ELF 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 target = self.base.options.target; + 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"); + } + const link_in_crt = self.base.options.link_libc and self.base.options.output_mode == .Exe; + + try argv.append("-error-limit=0"); + + if (self.base.options.output_mode == .Exe) { + try argv.append("-z"); + const stack_size = self.base.options.stack_size_override orelse 16777216; + const arg = try std.fmt.allocPrint(arena, "stack-size={}", .{stack_size}); + try argv.append(arg); + } + + if (self.base.options.linker_script) |linker_script| { + try argv.append("-T"); + try argv.append(linker_script); + } + + const gc_sections = self.base.options.gc_sections orelse !is_obj; + if (gc_sections) { + try argv.append("--gc-sections"); + } + + if (self.base.options.eh_frame_hdr) { + try argv.append("--eh-frame-hdr"); + } + + if (self.base.options.rdynamic) { + try argv.append("--export-dynamic"); + } + + try argv.appendSlice(self.base.options.extra_lld_args); + + if (self.base.options.z_nodelete) { + try argv.append("-z"); + try argv.append("nodelete"); + } + if (self.base.options.z_defs) { + try argv.append("-z"); + try argv.append("defs"); + } + + if (getLDMOption(target)) |ldm| { + // Any target ELF will use the freebsd osabi if suffixed with "_fbsd". + const arg = if (target.os.tag == .freebsd) + try std.fmt.allocPrint(arena, "{}_fbsd", .{ldm}) + else + ldm; + try argv.append("-m"); + try argv.append(arg); + } + + if (self.base.options.link_mode == .Static) { + if (target.cpu.arch.isARM() or target.cpu.arch.isThumb()) { + try argv.append("-Bstatic"); + } else { + try argv.append("-static"); + } + } else if (is_dyn_lib) { + try argv.append("-shared"); + } + + if (target_util.requiresPIE(target) and self.base.options.output_mode == .Exe) { + try argv.append("-pie"); + } + + const full_out_path = if (directory.path) |dir_path| + try std.fs.path.join(arena, &[_][]const u8{dir_path, self.base.options.sub_path}) + else + self.base.options.sub_path; + try argv.append("-o"); + try argv.append(full_out_path); + + if (link_in_crt) { + const crt1o: []const u8 = o: { + if (target.os.tag == .netbsd) { + break :o "crt0.o"; + } else if (target.isAndroid()) { + if (self.base.options.link_mode == .Dynamic) { + break :o "crtbegin_dynamic.o"; + } else { + break :o "crtbegin_static.o"; + } + } else if (self.base.options.link_mode == .Static) { + break :o "crt1.o"; + } else { + break :o "Scrt1.o"; + } + }; + try argv.append(try comp.get_libc_crt_file(arena, crt1o)); + if (target_util.libc_needs_crti_crtn(target)) { + try argv.append(try comp.get_libc_crt_file(arena, "crti.o")); + } + } + + // TODO rpaths + // TODO add to cache hash above too + //for (size_t i = 0; i < g->rpath_list.length; i += 1) { + // Buf *rpath = g->rpath_list.at(i); + // add_rpath(lj, rpath); + //} + //if (g->each_lib_rpath) { + // for (size_t i = 0; i < g->lib_dirs.length; i += 1) { + // const char *lib_dir = g->lib_dirs.at(i); + // for (size_t i = 0; i < g->link_libs_list.length; i += 1) { + // LinkLib *link_lib = g->link_libs_list.at(i); + // if (buf_eql_str(link_lib->name, "c")) { + // continue; + // } + // bool does_exist; + // Buf *test_path = buf_sprintf("%s/lib%s.so", lib_dir, buf_ptr(link_lib->name)); + // if (os_file_exists(test_path, &does_exist) != ErrorNone) { + // zig_panic("link: unable to check if file exists: %s", buf_ptr(test_path)); + // } + // if (does_exist) { + // add_rpath(lj, buf_create_from_str(lib_dir)); + // break; + // } + // } + // } + //} + + for (self.base.options.lib_dirs) |lib_dir| { + try argv.append("-L"); + try argv.append(lib_dir); + } + + if (self.base.options.link_libc) { + if (self.base.options.libc_installation) |libc_installation| { + try argv.append("-L"); + try argv.append(libc_installation.crt_dir.?); + } + + if (have_dynamic_linker) { + if (self.base.options.dynamic_linker) |dynamic_linker| { + try argv.append("-dynamic-linker"); + try argv.append(dynamic_linker); + } + } + } + + if (is_dyn_lib) { + const soname = self.base.options.override_soname orelse if (self.base.options.version) |ver| + try std.fmt.allocPrint(arena, "lib{}.so.{}", .{self.base.options.root_name, ver.major}) + else + try std.fmt.allocPrint(arena, "lib{}.so", .{self.base.options.root_name}); + try argv.append("-soname"); + try argv.append(soname); + + if (self.base.options.version_script) |version_script| { + try argv.append("-version-script"); + try argv.append(version_script); + } + } + + // Positional arguments to the linker such as object files. + 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); + } + + // compiler-rt and libc + if (is_exe_or_dyn_lib) { + if (!self.base.options.link_libc) { + try argv.append(comp.libc_static_lib.?.full_object_path); + } + try argv.append(comp.compiler_rt_static_lib.?.full_object_path); + } + + // Shared libraries. + try argv.ensureCapacity(argv.items.len + self.base.options.system_libs.len); + for (self.base.options.system_libs) |link_lib| { + // 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". + const ext = Compilation.classifyFileExt(link_lib); + const arg = if (ext == .shared_library) link_lib else try std.fmt.allocPrint(arena, "-l{}", .{link_lib}); + argv.appendAssumeCapacity(arg); + } + + if (!is_obj) { + // libc++ dep + if (self.base.options.link_libcpp) { + try argv.append(comp.libcxxabi_static_lib.?); + try argv.append(comp.libcxx_static_lib.?); + } + + // libc dep + if (self.base.options.link_libc) { + if (self.base.options.libc_installation != null) { + if (self.base.options.link_mode == .Static) { + try argv.append("--start-group"); + try argv.append("-lc"); + try argv.append("-lm"); + try argv.append("--end-group"); + } else { + try argv.append("-lc"); + try argv.append("-lm"); + } + + if (target.os.tag == .freebsd or target.os.tag == .netbsd) { + try argv.append("-lpthread"); + } + } else if (target.isGnuLibC()) { + try argv.append(comp.libunwind_static_lib.?.full_object_path); + for (glibc.libs) |lib| { + const lib_path = try std.fmt.allocPrint(arena, "{s}{c}lib{s}.so.{d}", .{ + comp.glibc_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, + }); + try argv.append(lib_path); + } + try argv.append(try comp.get_libc_crt_file(arena, "libc_nonshared.a")); + } else if (target.isMusl()) { + try argv.append(comp.libunwind_static_lib.?.full_object_path); + try argv.append(comp.libc_static_lib.?.full_object_path); + } else if (self.base.options.link_libcpp) { + try argv.append(comp.libunwind_static_lib.?.full_object_path); + } else { + unreachable; // Compiler was supposed to emit an error for not being able to provide libc. + } + } + } + + // crt end + if (link_in_crt) { + if (target.isAndroid()) { + try argv.append(try comp.get_libc_crt_file(arena, "crtend_android.o")); + } else if (target_util.libc_needs_crti_crtn(target)) { + try argv.append(try comp.get_libc_crt_file(arena, "crtn.o")); + } + } + + const allow_shlib_undefined = self.base.options.allow_shlib_undefined orelse !self.base.options.is_native_os; + if (allow_shlib_undefined) { + try argv.append("--allow-shlib-undefined"); + } + + if (self.base.options.bind_global_refs_locally) { + try argv.append("-Bsymbolic"); + } + + 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]}); + } + + // Oh, snapplesauce! We need null terminated argv. + // TODO allocSentinel crashed stage1 so this is working around it. + 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); + } + + const llvm = @import("../llvm.zig"); + const ok = llvm.Link(.ELF, new_argv.ptr, new_argv.len, append_diagnostic, 0, 0); + if (!ok) return error.LLDReportedFailure; + + // 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. + ch.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 = ch.toOwnedLock(); +} + +fn append_diagnostic(context: usize, ptr: [*]const u8, len: usize) callconv(.C) void { + // TODO collect diagnostics and handle cleanly + const msg = ptr[0..len]; + std.log.err("LLD: {}", .{msg}); +} + +fn writeDwarfAddrAssumeCapacity(self: *Elf, buf: *std.ArrayList(u8), addr: u64) void { + const target_endian = self.base.options.target.cpu.arch.endian(); + switch (self.ptr_width) { + .p32 => mem.writeInt(u32, buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, addr), target_endian), + .p64 => mem.writeInt(u64, buf.addManyAsArrayAssumeCapacity(8), addr, target_endian), + } +} + +fn writeElfHeader(self: *Elf) !void { + var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined; + + var index: usize = 0; + hdr_buf[0..4].* = "\x7fELF".*; + index += 4; + + hdr_buf[index] = switch (self.ptr_width) { + .p32 => elf.ELFCLASS32, + .p64 => elf.ELFCLASS64, + }; + index += 1; + + const endian = self.base.options.target.cpu.arch.endian(); + hdr_buf[index] = switch (endian) { + .Little => elf.ELFDATA2LSB, + .Big => elf.ELFDATA2MSB, + }; + index += 1; + + hdr_buf[index] = 1; // ELF version + index += 1; + + // OS ABI, often set to 0 regardless of target platform + // ABI Version, possibly used by glibc but not by static executables + // padding + mem.set(u8, hdr_buf[index..][0..9], 0); + index += 9; + + assert(index == 16); + + const elf_type = switch (self.base.options.effectiveOutputMode()) { + .Exe => elf.ET.EXEC, + .Obj => elf.ET.REL, + .Lib => switch (self.base.options.link_mode) { + .Static => elf.ET.REL, + .Dynamic => elf.ET.DYN, + }, + }; + mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(elf_type), endian); + index += 2; + + const machine = self.base.options.target.cpu.arch.toElfMachine(); + mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(machine), endian); + index += 2; + + // ELF Version, again + mem.writeInt(u32, hdr_buf[index..][0..4], 1, endian); + index += 4; + + const e_entry = if (elf_type == .REL) 0 else self.entry_addr.?; + + switch (self.ptr_width) { + .p32 => { + mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, e_entry), endian); + index += 4; + + // e_phoff + mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, self.phdr_table_offset.?), endian); + index += 4; + + // e_shoff + mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, self.shdr_table_offset.?), endian); + index += 4; + }, + .p64 => { + // e_entry + mem.writeInt(u64, hdr_buf[index..][0..8], e_entry, endian); + index += 8; + + // e_phoff + mem.writeInt(u64, hdr_buf[index..][0..8], self.phdr_table_offset.?, endian); + index += 8; + + // e_shoff + mem.writeInt(u64, hdr_buf[index..][0..8], self.shdr_table_offset.?, endian); + index += 8; + }, + } + + const e_flags = 0; + mem.writeInt(u32, hdr_buf[index..][0..4], e_flags, endian); + index += 4; + + const e_ehsize: u16 = switch (self.ptr_width) { + .p32 => @sizeOf(elf.Elf32_Ehdr), + .p64 => @sizeOf(elf.Elf64_Ehdr), + }; + mem.writeInt(u16, hdr_buf[index..][0..2], e_ehsize, endian); + index += 2; + + const e_phentsize: u16 = switch (self.ptr_width) { + .p32 => @sizeOf(elf.Elf32_Phdr), + .p64 => @sizeOf(elf.Elf64_Phdr), + }; + mem.writeInt(u16, hdr_buf[index..][0..2], e_phentsize, endian); + index += 2; + + const e_phnum = @intCast(u16, self.program_headers.items.len); + mem.writeInt(u16, hdr_buf[index..][0..2], e_phnum, endian); + index += 2; + + const e_shentsize: u16 = switch (self.ptr_width) { + .p32 => @sizeOf(elf.Elf32_Shdr), + .p64 => @sizeOf(elf.Elf64_Shdr), + }; + mem.writeInt(u16, hdr_buf[index..][0..2], e_shentsize, endian); + index += 2; + + const e_shnum = @intCast(u16, self.sections.items.len); + mem.writeInt(u16, hdr_buf[index..][0..2], e_shnum, endian); + index += 2; + + mem.writeInt(u16, hdr_buf[index..][0..2], self.shstrtab_index.?, endian); + index += 2; + + assert(index == e_ehsize); + + try self.base.file.?.pwriteAll(hdr_buf[0..index], 0); +} + +fn freeTextBlock(self: *Elf, text_block: *TextBlock) void { + 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); + continue; + } + if (self.text_block_free_list.items[i] == text_block.prev) { + already_have_free_list_node = true; + } + i += 1; + } + } + // TODO process free list for dbg info just like we do above for vaddrs + + if (self.last_text_block == text_block) { + // TODO shrink the .text section size here + self.last_text_block = text_block.prev; + } + if (self.dbg_info_decl_first == text_block) { + self.dbg_info_decl_first = text_block.dbg_info_next; + } + if (self.dbg_info_decl_last == text_block) { + // TODO shrink the .debug_info section size here + self.dbg_info_decl_last = text_block.dbg_info_prev; + } + + if (text_block.prev) |prev| { + prev.next = text_block.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 {}; + } + } else { + text_block.prev = null; + } + + if (text_block.next) |next| { + next.prev = text_block.prev; + } else { + text_block.next = null; + } + + if (text_block.dbg_info_prev) |prev| { + prev.dbg_info_next = text_block.dbg_info_next; + + // TODO the free list logic like we do for text blocks above + } else { + text_block.dbg_info_prev = null; + } + + if (text_block.dbg_info_next) |next| { + next.dbg_info_prev = text_block.dbg_info_prev; + } else { + text_block.dbg_info_next = null; + } +} + +fn shrinkTextBlock(self: *Elf, text_block: *TextBlock, new_block_size: u64) void { + // 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 growTextBlock(self: *Elf, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { + const sym = self.local_symbols.items[text_block.local_sym_index]; + const align_ok = mem.alignBackwardGeneric(u64, sym.st_value, alignment) == sym.st_value; + const need_realloc = !align_ok or new_block_size > text_block.capacity(self.*); + if (!need_realloc) return sym.st_value; + return self.allocateTextBlock(text_block, new_block_size, alignment); +} + +fn allocateTextBlock(self: *Elf, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { + const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; + const shdr = &self.sections.items[self.text_section_index.?]; + const new_block_ideal_capacity = new_block_size * alloc_num / alloc_den; + + // We use these to indicate our intention to update metadata, placing the new block, + // 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 free_list_removal: ?usize = null; + + // First we look for an appropriately sized free list node. + // The list is unordered. We'll just take the first thing that works. + const vaddr = blk: { + var i: usize = 0; + while (i < self.text_block_free_list.items.len) { + const big_block = self.text_block_free_list.items[i]; + // We now have a pointer to a live text block that has too much capacity. + // Is it enough that we could fit this new text block? + const sym = self.local_symbols.items[big_block.local_sym_index]; + const capacity = big_block.capacity(self.*); + const ideal_capacity = capacity * alloc_num / alloc_den; + const ideal_capacity_end_vaddr = sym.st_value + ideal_capacity; + const capacity_end_vaddr = sym.st_value + capacity; + const new_start_vaddr_unaligned = capacity_end_vaddr - new_block_ideal_capacity; + const new_start_vaddr = mem.alignBackwardGeneric(u64, new_start_vaddr_unaligned, alignment); + if (new_start_vaddr < ideal_capacity_end_vaddr) { + // Additional bookkeeping here to notice if this free list node + // should be deleted because the block that it points to has grown to take up + // more of the extra capacity. + if (!big_block.freeListEligible(self.*)) { + _ = self.text_block_free_list.swapRemove(i); + } else { + i += 1; + } + continue; + } + // At this point we know that we will place the new block 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. + block_placement = big_block; + if (!keep_free_list_node) { + free_list_removal = i; + } + break :blk new_start_vaddr; + } else if (self.last_text_block) |last| { + const sym = self.local_symbols.items[last.local_sym_index]; + const ideal_capacity = sym.st_size * alloc_num / alloc_den; + const ideal_capacity_end_vaddr = sym.st_value + ideal_capacity; + const new_start_vaddr = mem.alignForwardGeneric(u64, ideal_capacity_end_vaddr, alignment); + // Set up the metadata to be updated, after errors are no longer possible. + block_placement = last; + break :blk new_start_vaddr; + } else { + break :blk phdr.p_vaddr; + } + }; + + const expand_text_section = block_placement == null or block_placement.?.next == null; + if (expand_text_section) { + const text_capacity = self.allocatedSize(shdr.sh_offset); + const needed_size = (vaddr + new_block_size) - phdr.p_vaddr; + if (needed_size > text_capacity) { + // Must move the entire text section. + const new_offset = self.findFreeSpace(needed_size, 0x1000); + const text_size = if (self.last_text_block) |last| blk: { + const sym = self.local_symbols.items[last.local_sym_index]; + break :blk (sym.st_value + sym.st_size) - phdr.p_vaddr; + } else 0; + const amt = try self.base.file.?.copyRangeAll(shdr.sh_offset, self.base.file.?, new_offset, text_size); + if (amt != text_size) return error.InputOutput; + shdr.sh_offset = new_offset; + phdr.p_offset = new_offset; + } + self.last_text_block = text_block; + + shdr.sh_size = needed_size; + phdr.p_memsz = needed_size; + phdr.p_filesz = needed_size; + + // The .debug_info section has `low_pc` and `high_pc` values which is the virtual address + // range of the compilation unit. When we expand the text section, this range changes, + // so the DW_TAG_compile_unit tag of the .debug_info section becomes dirty. + self.debug_info_header_dirty = true; + // This becomes dirty for the same reason. We could potentially make this more + // fine-grained with the addition of support for more compilation units. It is planned to + // model each package as a different compilation unit. + self.debug_aranges_section_dirty = true; + + self.phdr_table_dirty = true; // TODO look into making only the one program header dirty + self.shdr_table_dirty = true; // TODO look into making only the one section dirty + } + + // 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 (text_block.next) |next| { + next.prev = text_block.prev; + } + + if (block_placement) |big_block| { + text_block.prev = big_block; + text_block.next = big_block.next; + big_block.next = text_block; + } else { + text_block.prev = null; + text_block.next = null; + } + if (free_list_removal) |i| { + _ = self.text_block_free_list.swapRemove(i); + } + return vaddr; +} + +pub fn allocateDeclIndexes(self: *Elf, decl: *Module.Decl) !void { + if (decl.link.elf.local_sym_index != 0) return; + + try self.local_symbols.ensureCapacity(self.base.allocator, self.local_symbols.items.len + 1); + try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1); + + if (self.local_symbol_free_list.popOrNull()) |i| { + log.debug("reusing symbol index {} for {}\n", .{ i, decl.name }); + decl.link.elf.local_sym_index = i; + } else { + log.debug("allocating symbol index {} for {}\n", .{ self.local_symbols.items.len, decl.name }); + decl.link.elf.local_sym_index = @intCast(u32, self.local_symbols.items.len); + _ = self.local_symbols.addOneAssumeCapacity(); + } + + if (self.offset_table_free_list.popOrNull()) |i| { + decl.link.elf.offset_table_index = i; + } else { + decl.link.elf.offset_table_index = @intCast(u32, self.offset_table.items.len); + _ = self.offset_table.addOneAssumeCapacity(); + self.offset_table_count_dirty = true; + } + + const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; + + self.local_symbols.items[decl.link.elf.local_sym_index] = .{ + .st_name = 0, + .st_info = 0, + .st_other = 0, + .st_shndx = 0, + .st_value = phdr.p_vaddr, + .st_size = 0, + }; + self.offset_table.items[decl.link.elf.offset_table_index] = 0; +} + +pub fn freeDecl(self: *Elf, decl: *Module.Decl) void { + // Appending to free lists is allowed to fail because the free lists are heuristics based anyway. + self.freeTextBlock(&decl.link.elf); + if (decl.link.elf.local_sym_index != 0) { + self.local_symbol_free_list.append(self.base.allocator, decl.link.elf.local_sym_index) catch {}; + self.offset_table_free_list.append(self.base.allocator, decl.link.elf.offset_table_index) catch {}; + + self.local_symbols.items[decl.link.elf.local_sym_index].st_info = 0; + + decl.link.elf.local_sym_index = 0; + } + // TODO make this logic match freeTextBlock. Maybe abstract the logic out since the same thing + // is desired for both. + _ = self.dbg_line_fn_free_list.remove(&decl.fn_link.elf); + if (decl.fn_link.elf.prev) |prev| { + _ = self.dbg_line_fn_free_list.put(self.base.allocator, prev, {}) catch {}; + prev.next = decl.fn_link.elf.next; + if (decl.fn_link.elf.next) |next| { + next.prev = prev; + } else { + self.dbg_line_fn_last = prev; + } + } else if (decl.fn_link.elf.next) |next| { + self.dbg_line_fn_first = next; + next.prev = null; + } + if (self.dbg_line_fn_first == &decl.fn_link.elf) { + self.dbg_line_fn_first = decl.fn_link.elf.next; + } + if (self.dbg_line_fn_last == &decl.fn_link.elf) { + self.dbg_line_fn_last = decl.fn_link.elf.prev; + } +} + +pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { + const tracy = trace(@src()); + defer tracy.end(); + + var code_buffer = std.ArrayList(u8).init(self.base.allocator); + defer code_buffer.deinit(); + + var dbg_line_buffer = std.ArrayList(u8).init(self.base.allocator); + defer dbg_line_buffer.deinit(); + + var dbg_info_buffer = std.ArrayList(u8).init(self.base.allocator); + defer dbg_info_buffer.deinit(); + + var dbg_info_type_relocs: File.DbgInfoTypeRelocsTable = .{}; + defer { + var it = dbg_info_type_relocs.iterator(); + while (it.next()) |entry| { + entry.value.relocs.deinit(self.base.allocator); + } + dbg_info_type_relocs.deinit(self.base.allocator); + } + + const typed_value = decl.typed_value.most_recent.typed_value; + const is_fn: bool = switch (typed_value.ty.zigTypeTag()) { + .Fn => true, + else => false, + }; + if (is_fn) { + const zir_dumps = if (std.builtin.is_test) &[0][]const u8{} else build_options.zir_dumps; + if (zir_dumps.len != 0) { + for (zir_dumps) |fn_name| { + if (mem.eql(u8, mem.spanZ(decl.name), fn_name)) { + std.debug.print("\n{}\n", .{decl.name}); + typed_value.val.cast(Value.Payload.Function).?.func.dump(module.*); + } + } + } + + // For functions we need to add a prologue to the debug line program. + try dbg_line_buffer.ensureCapacity(26); + + const line_off: u28 = blk: { + if (decl.scope.cast(Module.Scope.Container)) |container_scope| { + const tree = container_scope.file_scope.contents.tree; + const file_ast_decls = tree.root_node.decls(); + // TODO Look into improving the performance here by adding a token-index-to-line + // lookup table. Currently this involves scanning over the source code for newlines. + const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?; + const block = fn_proto.getBodyNode().?.castTag(.Block).?; + const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start); + break :blk @intCast(u28, line_delta); + } else if (decl.scope.cast(Module.Scope.ZIRModule)) |zir_module| { + const byte_off = zir_module.contents.module.decls[decl.src_index].inst.src; + const line_delta = std.zig.lineDelta(zir_module.source.bytes, 0, byte_off); + break :blk @intCast(u28, line_delta); + } else { + unreachable; + } + }; + + const ptr_width_bytes = self.ptrWidthBytes(); + dbg_line_buffer.appendSliceAssumeCapacity(&[_]u8{ + DW.LNS_extended_op, + ptr_width_bytes + 1, + DW.LNE_set_address, + }); + // This is the "relocatable" vaddr, corresponding to `code_buffer` index `0`. + assert(dbg_line_vaddr_reloc_index == dbg_line_buffer.items.len); + dbg_line_buffer.items.len += ptr_width_bytes; + + dbg_line_buffer.appendAssumeCapacity(DW.LNS_advance_line); + // This is the "relocatable" relative line offset from the previous function's end curly + // to this function's begin curly. + assert(self.getRelocDbgLineOff() == dbg_line_buffer.items.len); + // Here we use a ULEB128-fixed-4 to make sure this field can be overwritten later. + leb128.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), line_off); + + dbg_line_buffer.appendAssumeCapacity(DW.LNS_set_file); + assert(self.getRelocDbgFileIndex() == dbg_line_buffer.items.len); + // Once we support more than one source file, this will have the ability to be more + // than one possible value. + const file_index = 1; + leb128.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), file_index); + + // Emit a line for the begin curly with prologue_end=false. The codegen will + // do the work of setting prologue_end=true and epilogue_begin=true. + dbg_line_buffer.appendAssumeCapacity(DW.LNS_copy); + + // .debug_info subprogram + const decl_name_with_null = decl.name[0 .. mem.lenZ(decl.name) + 1]; + try dbg_info_buffer.ensureCapacity(dbg_info_buffer.items.len + 25 + decl_name_with_null.len); + + const fn_ret_type = typed_value.ty.fnReturnType(); + const fn_ret_has_bits = fn_ret_type.hasCodeGenBits(); + if (fn_ret_has_bits) { + dbg_info_buffer.appendAssumeCapacity(abbrev_subprogram); + } else { + dbg_info_buffer.appendAssumeCapacity(abbrev_subprogram_retvoid); + } + // These get overwritten after generating the machine code. These values are + // "relocations" and have to be in this fixed place so that functions can be + // moved in virtual address space. + assert(dbg_info_low_pc_reloc_index == dbg_info_buffer.items.len); + dbg_info_buffer.items.len += ptr_width_bytes; // DW.AT_low_pc, DW.FORM_addr + assert(self.getRelocDbgInfoSubprogramHighPC() == dbg_info_buffer.items.len); + dbg_info_buffer.items.len += 4; // DW.AT_high_pc, DW.FORM_data4 + if (fn_ret_has_bits) { + const gop = try dbg_info_type_relocs.getOrPut(self.base.allocator, fn_ret_type); + if (!gop.found_existing) { + gop.entry.value = .{ + .off = undefined, + .relocs = .{}, + }; + } + try gop.entry.value.relocs.append(self.base.allocator, @intCast(u32, dbg_info_buffer.items.len)); + dbg_info_buffer.items.len += 4; // DW.AT_type, DW.FORM_ref4 + } + dbg_info_buffer.appendSliceAssumeCapacity(decl_name_with_null); // DW.AT_name, DW.FORM_string + } else { + // TODO implement .debug_info for global variables + } + const res = try codegen.generateSymbol(&self.base, decl.src(), typed_value, &code_buffer, .{ + .dwarf = .{ + .dbg_line = &dbg_line_buffer, + .dbg_info = &dbg_info_buffer, + .dbg_info_type_relocs = &dbg_info_type_relocs, + }, + }); + const code = switch (res) { + .externally_managed => |x| x, + .appended => code_buffer.items, + .fail => |em| { + decl.analysis = .codegen_failure; + try module.failed_decls.put(module.gpa, decl, em); + return; + }, + }; + + const required_alignment = typed_value.ty.abiAlignment(self.base.options.target); + + const stt_bits: u8 = if (is_fn) elf.STT_FUNC else elf.STT_OBJECT; + + assert(decl.link.elf.local_sym_index != 0); // Caller forgot to allocateDeclIndexes() + const local_sym = &self.local_symbols.items[decl.link.elf.local_sym_index]; + if (local_sym.st_size != 0) { + const capacity = decl.link.elf.capacity(self.*); + const need_realloc = code.len > capacity or + !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment); + if (need_realloc) { + const vaddr = try self.growTextBlock(&decl.link.elf, code.len, required_alignment); + log.debug("growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr }); + if (vaddr != local_sym.st_value) { + local_sym.st_value = vaddr; + + log.debug(" (writing new offset table entry)\n", .{}); + self.offset_table.items[decl.link.elf.offset_table_index] = vaddr; + try self.writeOffsetTableEntry(decl.link.elf.offset_table_index); + } + } else if (code.len < local_sym.st_size) { + self.shrinkTextBlock(&decl.link.elf, code.len); + } + local_sym.st_size = code.len; + local_sym.st_name = try self.updateString(local_sym.st_name, mem.spanZ(decl.name)); + local_sym.st_info = (elf.STB_LOCAL << 4) | stt_bits; + local_sym.st_other = 0; + local_sym.st_shndx = self.text_section_index.?; + // TODO this write could be avoided if no fields of the symbol were changed. + try self.writeSymbol(decl.link.elf.local_sym_index); + } else { + const decl_name = mem.spanZ(decl.name); + const name_str_index = try self.makeString(decl_name); + const vaddr = try self.allocateTextBlock(&decl.link.elf, code.len, required_alignment); + log.debug("allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr }); + errdefer self.freeTextBlock(&decl.link.elf); + + local_sym.* = .{ + .st_name = name_str_index, + .st_info = (elf.STB_LOCAL << 4) | stt_bits, + .st_other = 0, + .st_shndx = self.text_section_index.?, + .st_value = vaddr, + .st_size = code.len, + }; + self.offset_table.items[decl.link.elf.offset_table_index] = vaddr; + + try self.writeSymbol(decl.link.elf.local_sym_index); + try self.writeOffsetTableEntry(decl.link.elf.offset_table_index); + } + + const section_offset = local_sym.st_value - self.program_headers.items[self.phdr_load_re_index.?].p_vaddr; + const file_offset = self.sections.items[self.text_section_index.?].sh_offset + section_offset; + try self.base.file.?.pwriteAll(code, file_offset); + + const target_endian = self.base.options.target.cpu.arch.endian(); + + const text_block = &decl.link.elf; + + // If the Decl is a function, we need to update the .debug_line program. + if (is_fn) { + // Perform the relocations based on vaddr. + switch (self.ptr_width) { + .p32 => { + { + const ptr = dbg_line_buffer.items[dbg_line_vaddr_reloc_index..][0..4]; + mem.writeInt(u32, ptr, @intCast(u32, local_sym.st_value), target_endian); + } + { + const ptr = dbg_info_buffer.items[dbg_info_low_pc_reloc_index..][0..4]; + mem.writeInt(u32, ptr, @intCast(u32, local_sym.st_value), target_endian); + } + }, + .p64 => { + { + const ptr = dbg_line_buffer.items[dbg_line_vaddr_reloc_index..][0..8]; + mem.writeInt(u64, ptr, local_sym.st_value, target_endian); + } + { + const ptr = dbg_info_buffer.items[dbg_info_low_pc_reloc_index..][0..8]; + mem.writeInt(u64, ptr, local_sym.st_value, target_endian); + } + }, + } + { + const ptr = dbg_info_buffer.items[self.getRelocDbgInfoSubprogramHighPC()..][0..4]; + mem.writeInt(u32, ptr, @intCast(u32, local_sym.st_size), target_endian); + } + + try dbg_line_buffer.appendSlice(&[_]u8{ DW.LNS_extended_op, 1, DW.LNE_end_sequence }); + + // Now we have the full contents and may allocate a region to store it. + + // This logic is nearly identical to the logic below in `updateDeclDebugInfo` for + // `TextBlock` and the .debug_info. If you are editing this logic, you + // probably need to edit that logic too. + + const debug_line_sect = &self.sections.items[self.debug_line_section_index.?]; + const src_fn = &decl.fn_link.elf; + src_fn.len = @intCast(u32, dbg_line_buffer.items.len); + if (self.dbg_line_fn_last) |last| { + if (src_fn.next) |next| { + // Update existing function - non-last item. + if (src_fn.off + src_fn.len + min_nop_size > next.off) { + // It grew too big, so we move it to a new location. + if (src_fn.prev) |prev| { + _ = self.dbg_line_fn_free_list.put(self.base.allocator, prev, {}) catch {}; + prev.next = src_fn.next; + } + next.prev = src_fn.prev; + src_fn.next = null; + // Populate where it used to be with NOPs. + const file_pos = debug_line_sect.sh_offset + src_fn.off; + try self.pwriteDbgLineNops(0, &[0]u8{}, src_fn.len, file_pos); + // TODO Look at the free list before appending at the end. + src_fn.prev = last; + last.next = src_fn; + self.dbg_line_fn_last = src_fn; + + src_fn.off = last.off + (last.len * alloc_num / alloc_den); + } + } else if (src_fn.prev == null) { + // Append new function. + // TODO Look at the free list before appending at the end. + src_fn.prev = last; + last.next = src_fn; + self.dbg_line_fn_last = src_fn; + + src_fn.off = last.off + (last.len * alloc_num / alloc_den); + } + } else { + // This is the first function of the Line Number Program. + self.dbg_line_fn_first = src_fn; + self.dbg_line_fn_last = src_fn; + + src_fn.off = self.dbgLineNeededHeaderBytes() * alloc_num / alloc_den; + } + + const last_src_fn = self.dbg_line_fn_last.?; + const needed_size = last_src_fn.off + last_src_fn.len; + if (needed_size != debug_line_sect.sh_size) { + if (needed_size > self.allocatedSize(debug_line_sect.sh_offset)) { + const new_offset = self.findFreeSpace(needed_size, 1); + const existing_size = last_src_fn.off; + log.debug("moving .debug_line section: {} bytes from 0x{x} to 0x{x}\n", .{ + existing_size, + debug_line_sect.sh_offset, + new_offset, + }); + const amt = try self.base.file.?.copyRangeAll(debug_line_sect.sh_offset, self.base.file.?, new_offset, existing_size); + if (amt != existing_size) return error.InputOutput; + debug_line_sect.sh_offset = new_offset; + } + debug_line_sect.sh_size = needed_size; + self.shdr_table_dirty = true; // TODO look into making only the one section dirty + self.debug_line_header_dirty = true; + } + const prev_padding_size: u32 = if (src_fn.prev) |prev| src_fn.off - (prev.off + prev.len) else 0; + const next_padding_size: u32 = if (src_fn.next) |next| next.off - (src_fn.off + src_fn.len) else 0; + + // We only have support for one compilation unit so far, so the offsets are directly + // from the .debug_line section. + const file_pos = debug_line_sect.sh_offset + src_fn.off; + try self.pwriteDbgLineNops(prev_padding_size, dbg_line_buffer.items, next_padding_size, file_pos); + + // .debug_info - End the TAG_subprogram children. + try dbg_info_buffer.append(0); + } + + // Now we emit the .debug_info types of the Decl. These will count towards the size of + // the buffer, so we have to do it before computing the offset, and we can't perform the actual + // relocations yet. + var it = dbg_info_type_relocs.iterator(); + while (it.next()) |entry| { + entry.value.off = @intCast(u32, dbg_info_buffer.items.len); + try self.addDbgInfoType(entry.key, &dbg_info_buffer); + } + + try self.updateDeclDebugInfoAllocation(text_block, @intCast(u32, dbg_info_buffer.items.len)); + + // Now that we have the offset assigned we can finally perform type relocations. + it = dbg_info_type_relocs.iterator(); + while (it.next()) |entry| { + for (entry.value.relocs.items) |off| { + mem.writeInt( + u32, + dbg_info_buffer.items[off..][0..4], + text_block.dbg_info_off + entry.value.off, + target_endian, + ); + } + } + + try self.writeDeclDebugInfo(text_block, dbg_info_buffer.items); + + // 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) orelse &[0]*Module.Export{}; + return self.updateDeclExports(module, decl, decl_exports); +} + +/// Asserts the type has codegen bits. +fn addDbgInfoType(self: *Elf, ty: Type, dbg_info_buffer: *std.ArrayList(u8)) !void { + switch (ty.zigTypeTag()) { + .Void => unreachable, + .NoReturn => unreachable, + .Bool => { + try dbg_info_buffer.appendSlice(&[_]u8{ + abbrev_base_type, + DW.ATE_boolean, // DW.AT_encoding , DW.FORM_data1 + 1, // DW.AT_byte_size, DW.FORM_data1 + 'b', + 'o', + 'o', + 'l', + 0, // DW.AT_name, DW.FORM_string + }); + }, + .Int => { + const info = ty.intInfo(self.base.options.target); + try dbg_info_buffer.ensureCapacity(dbg_info_buffer.items.len + 12); + dbg_info_buffer.appendAssumeCapacity(abbrev_base_type); + // DW.AT_encoding, DW.FORM_data1 + dbg_info_buffer.appendAssumeCapacity(if (info.signed) DW.ATE_signed else DW.ATE_unsigned); + // DW.AT_byte_size, DW.FORM_data1 + dbg_info_buffer.appendAssumeCapacity(@intCast(u8, ty.abiSize(self.base.options.target))); + // DW.AT_name, DW.FORM_string + try dbg_info_buffer.writer().print("{}\x00", .{ty}); + }, + else => { + std.log.scoped(.compiler).err("TODO implement .debug_info for type '{}'", .{ty}); + try dbg_info_buffer.append(abbrev_pad1); + }, + } +} + +fn updateDeclDebugInfoAllocation(self: *Elf, text_block: *TextBlock, len: u32) !void { + const tracy = trace(@src()); + defer tracy.end(); + + // This logic is nearly identical to the logic above in `updateDecl` for + // `SrcFn` and the line number programs. If you are editing this logic, you + // probably need to edit that logic too. + + const debug_info_sect = &self.sections.items[self.debug_info_section_index.?]; + text_block.dbg_info_len = len; + if (self.dbg_info_decl_last) |last| { + if (text_block.dbg_info_next) |next| { + // Update existing Decl - non-last item. + if (text_block.dbg_info_off + text_block.dbg_info_len + min_nop_size > next.dbg_info_off) { + // It grew too big, so we move it to a new location. + if (text_block.dbg_info_prev) |prev| { + _ = self.dbg_info_decl_free_list.put(self.base.allocator, prev, {}) catch {}; + prev.dbg_info_next = text_block.dbg_info_next; + } + next.dbg_info_prev = text_block.dbg_info_prev; + text_block.dbg_info_next = null; + // Populate where it used to be with NOPs. + const file_pos = debug_info_sect.sh_offset + text_block.dbg_info_off; + try self.pwriteDbgInfoNops(0, &[0]u8{}, text_block.dbg_info_len, false, file_pos); + // TODO Look at the free list before appending at the end. + text_block.dbg_info_prev = last; + last.dbg_info_next = text_block; + self.dbg_info_decl_last = text_block; + + text_block.dbg_info_off = last.dbg_info_off + (last.dbg_info_len * alloc_num / alloc_den); + } + } else if (text_block.dbg_info_prev == null) { + // Append new Decl. + // TODO Look at the free list before appending at the end. + text_block.dbg_info_prev = last; + last.dbg_info_next = text_block; + self.dbg_info_decl_last = text_block; + + text_block.dbg_info_off = last.dbg_info_off + (last.dbg_info_len * alloc_num / alloc_den); + } + } else { + // This is the first Decl of the .debug_info + self.dbg_info_decl_first = text_block; + self.dbg_info_decl_last = text_block; + + text_block.dbg_info_off = self.dbgInfoNeededHeaderBytes() * alloc_num / alloc_den; + } +} + +fn writeDeclDebugInfo(self: *Elf, text_block: *TextBlock, dbg_info_buf: []const u8) !void { + const tracy = trace(@src()); + defer tracy.end(); + + // This logic is nearly identical to the logic above in `updateDecl` for + // `SrcFn` and the line number programs. If you are editing this logic, you + // probably need to edit that logic too. + + const debug_info_sect = &self.sections.items[self.debug_info_section_index.?]; + + const last_decl = self.dbg_info_decl_last.?; + // +1 for a trailing zero to end the children of the decl tag. + const needed_size = last_decl.dbg_info_off + last_decl.dbg_info_len + 1; + if (needed_size != debug_info_sect.sh_size) { + if (needed_size > self.allocatedSize(debug_info_sect.sh_offset)) { + const new_offset = self.findFreeSpace(needed_size, 1); + const existing_size = last_decl.dbg_info_off; + log.debug("moving .debug_info section: {} bytes from 0x{x} to 0x{x}\n", .{ + existing_size, + debug_info_sect.sh_offset, + new_offset, + }); + const amt = try self.base.file.?.copyRangeAll(debug_info_sect.sh_offset, self.base.file.?, new_offset, existing_size); + if (amt != existing_size) return error.InputOutput; + debug_info_sect.sh_offset = new_offset; + } + debug_info_sect.sh_size = needed_size; + self.shdr_table_dirty = true; // TODO look into making only the one section dirty + self.debug_info_header_dirty = true; + } + const prev_padding_size: u32 = if (text_block.dbg_info_prev) |prev| + text_block.dbg_info_off - (prev.dbg_info_off + prev.dbg_info_len) + else + 0; + const next_padding_size: u32 = if (text_block.dbg_info_next) |next| + next.dbg_info_off - (text_block.dbg_info_off + text_block.dbg_info_len) + else + 0; + + // To end the children of the decl tag. + const trailing_zero = text_block.dbg_info_next == null; + + // We only have support for one compilation unit so far, so the offsets are directly + // from the .debug_info section. + const file_pos = debug_info_sect.sh_offset + text_block.dbg_info_off; + try self.pwriteDbgInfoNops(prev_padding_size, dbg_info_buf, next_padding_size, trailing_zero, file_pos); +} + +pub fn updateDeclExports( + self: *Elf, + module: *Module, + decl: *const Module.Decl, + exports: []const *Module.Export, +) !void { + const tracy = trace(@src()); + defer tracy.end(); + + try self.global_symbols.ensureCapacity(self.base.allocator, self.global_symbols.items.len + exports.len); + const typed_value = decl.typed_value.most_recent.typed_value; + if (decl.link.elf.local_sym_index == 0) return; + const decl_sym = self.local_symbols.items[decl.link.elf.local_sym_index]; + + for (exports) |exp| { + if (exp.options.section) |section_name| { + 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, + try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: ExportOptions.section", .{}), + ); + continue; + } + } + const stb_bits: u8 = switch (exp.options.linkage) { + .Internal => elf.STB_LOCAL, + .Strong => blk: { + if (mem.eql(u8, exp.options.name, "_start")) { + self.entry_addr = decl_sym.st_value; + } + break :blk elf.STB_GLOBAL; + }, + .Weak => elf.STB_WEAK, + .LinkOnce => { + try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); + module.failed_exports.putAssumeCapacityNoClobber( + exp, + try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: GlobalLinkage.LinkOnce", .{}), + ); + continue; + }, + }; + const stt_bits: u8 = @truncate(u4, decl_sym.st_info); + if (exp.link.sym_index) |i| { + const sym = &self.global_symbols.items[i]; + sym.* = .{ + .st_name = try self.updateString(sym.st_name, exp.options.name), + .st_info = (stb_bits << 4) | stt_bits, + .st_other = 0, + .st_shndx = self.text_section_index.?, + .st_value = decl_sym.st_value, + .st_size = decl_sym.st_size, + }; + } else { + const name = try self.makeString(exp.options.name); + const i = if (self.global_symbol_free_list.popOrNull()) |i| i else blk: { + _ = self.global_symbols.addOneAssumeCapacity(); + break :blk self.global_symbols.items.len - 1; + }; + self.global_symbols.items[i] = .{ + .st_name = name, + .st_info = (stb_bits << 4) | stt_bits, + .st_other = 0, + .st_shndx = self.text_section_index.?, + .st_value = decl_sym.st_value, + .st_size = decl_sym.st_size, + }; + + exp.link.sym_index = @intCast(u32, i); + } + } +} + +/// Must be called only after a successful call to `updateDecl`. +pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Decl) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const container_scope = decl.scope.cast(Module.Scope.Container).?; + const tree = container_scope.file_scope.contents.tree; + const file_ast_decls = tree.root_node.decls(); + // TODO Look into improving the performance here by adding a token-index-to-line + // lookup table. Currently this involves scanning over the source code for newlines. + const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?; + const block = fn_proto.getBodyNode().?.castTag(.Block).?; + const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start); + const casted_line_off = @intCast(u28, line_delta); + + const shdr = &self.sections.items[self.debug_line_section_index.?]; + const file_pos = shdr.sh_offset + decl.fn_link.elf.off + self.getRelocDbgLineOff(); + var data: [4]u8 = undefined; + leb128.writeUnsignedFixed(4, &data, casted_line_off); + try self.base.file.?.pwriteAll(&data, file_pos); +} + +pub fn deleteExport(self: *Elf, exp: Export) void { + const sym_index = exp.sym_index orelse return; + self.global_symbol_free_list.append(self.base.allocator, sym_index) catch {}; + self.global_symbols.items[sym_index].st_info = 0; +} + +fn writeProgHeader(self: *Elf, index: usize) !void { + const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); + const offset = self.program_headers.items[index].p_offset; + switch (self.ptr_width) { + .p32 => { + var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])}; + if (foreign_endian) { + bswapAllFields(elf.Elf32_Phdr, &phdr[0]); + } + return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset); + }, + .p64 => { + var phdr = [1]elf.Elf64_Phdr{self.program_headers.items[index]}; + if (foreign_endian) { + bswapAllFields(elf.Elf64_Phdr, &phdr[0]); + } + return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset); + }, + } +} + +fn writeSectHeader(self: *Elf, index: usize) !void { + const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); + switch (self.ptr_width) { + .p32 => { + var shdr: [1]elf.Elf32_Shdr = undefined; + shdr[0] = sectHeaderTo32(self.sections.items[index]); + if (foreign_endian) { + bswapAllFields(elf.Elf32_Shdr, &shdr[0]); + } + const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf32_Shdr); + return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset); + }, + .p64 => { + var shdr = [1]elf.Elf64_Shdr{self.sections.items[index]}; + if (foreign_endian) { + bswapAllFields(elf.Elf64_Shdr, &shdr[0]); + } + const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf64_Shdr); + return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset); + }, + } +} + +fn writeOffsetTableEntry(self: *Elf, index: usize) !void { + const shdr = &self.sections.items[self.got_section_index.?]; + const phdr = &self.program_headers.items[self.phdr_got_index.?]; + const entry_size: u16 = self.archPtrWidthBytes(); + if (self.offset_table_count_dirty) { + // TODO Also detect virtual address collisions. + const allocated_size = self.allocatedSize(shdr.sh_offset); + const needed_size = self.local_symbols.items.len * entry_size; + if (needed_size > allocated_size) { + // Must move the entire got section. + const new_offset = self.findFreeSpace(needed_size, entry_size); + const amt = try self.base.file.?.copyRangeAll(shdr.sh_offset, self.base.file.?, new_offset, shdr.sh_size); + if (amt != shdr.sh_size) return error.InputOutput; + shdr.sh_offset = new_offset; + phdr.p_offset = new_offset; + } + shdr.sh_size = needed_size; + phdr.p_memsz = needed_size; + phdr.p_filesz = needed_size; + + self.shdr_table_dirty = true; // TODO look into making only the one section dirty + self.phdr_table_dirty = true; // TODO look into making only the one program header dirty + + self.offset_table_count_dirty = false; + } + const endian = self.base.options.target.cpu.arch.endian(); + const off = shdr.sh_offset + @as(u64, entry_size) * index; + switch (entry_size) { + 2 => { + var buf: [2]u8 = undefined; + mem.writeInt(u16, &buf, @intCast(u16, self.offset_table.items[index]), endian); + try self.base.file.?.pwriteAll(&buf, off); + }, + 4 => { + var buf: [4]u8 = undefined; + mem.writeInt(u32, &buf, @intCast(u32, self.offset_table.items[index]), endian); + try self.base.file.?.pwriteAll(&buf, off); + }, + 8 => { + var buf: [8]u8 = undefined; + mem.writeInt(u64, &buf, self.offset_table.items[index], endian); + try self.base.file.?.pwriteAll(&buf, off); + }, + else => unreachable, + } +} + +fn writeSymbol(self: *Elf, index: usize) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const syms_sect = &self.sections.items[self.symtab_section_index.?]; + // Make sure we are not pointlessly writing symbol data that will have to get relocated + // due to running out of space. + if (self.local_symbols.items.len != syms_sect.sh_info) { + const sym_size: u64 = switch (self.ptr_width) { + .p32 => @sizeOf(elf.Elf32_Sym), + .p64 => @sizeOf(elf.Elf64_Sym), + }; + const sym_align: u16 = switch (self.ptr_width) { + .p32 => @alignOf(elf.Elf32_Sym), + .p64 => @alignOf(elf.Elf64_Sym), + }; + const needed_size = (self.local_symbols.items.len + self.global_symbols.items.len) * sym_size; + if (needed_size > self.allocatedSize(syms_sect.sh_offset)) { + // Move all the symbols to a new file location. + const new_offset = self.findFreeSpace(needed_size, sym_align); + const existing_size = @as(u64, syms_sect.sh_info) * sym_size; + const amt = try self.base.file.?.copyRangeAll(syms_sect.sh_offset, self.base.file.?, new_offset, existing_size); + if (amt != existing_size) return error.InputOutput; + syms_sect.sh_offset = new_offset; + } + syms_sect.sh_info = @intCast(u32, self.local_symbols.items.len); + syms_sect.sh_size = needed_size; // anticipating adding the global symbols later + self.shdr_table_dirty = true; // TODO look into only writing one section + } + const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); + switch (self.ptr_width) { + .p32 => { + var sym = [1]elf.Elf32_Sym{ + .{ + .st_name = self.local_symbols.items[index].st_name, + .st_value = @intCast(u32, self.local_symbols.items[index].st_value), + .st_size = @intCast(u32, self.local_symbols.items[index].st_size), + .st_info = self.local_symbols.items[index].st_info, + .st_other = self.local_symbols.items[index].st_other, + .st_shndx = self.local_symbols.items[index].st_shndx, + }, + }; + if (foreign_endian) { + bswapAllFields(elf.Elf32_Sym, &sym[0]); + } + const off = syms_sect.sh_offset + @sizeOf(elf.Elf32_Sym) * index; + try self.base.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off); + }, + .p64 => { + var sym = [1]elf.Elf64_Sym{self.local_symbols.items[index]}; + if (foreign_endian) { + bswapAllFields(elf.Elf64_Sym, &sym[0]); + } + const off = syms_sect.sh_offset + @sizeOf(elf.Elf64_Sym) * index; + try self.base.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off); + }, + } +} + +fn writeAllGlobalSymbols(self: *Elf) !void { + const syms_sect = &self.sections.items[self.symtab_section_index.?]; + const sym_size: u64 = switch (self.ptr_width) { + .p32 => @sizeOf(elf.Elf32_Sym), + .p64 => @sizeOf(elf.Elf64_Sym), + }; + const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); + const global_syms_off = syms_sect.sh_offset + self.local_symbols.items.len * sym_size; + switch (self.ptr_width) { + .p32 => { + const buf = try self.base.allocator.alloc(elf.Elf32_Sym, self.global_symbols.items.len); + defer self.base.allocator.free(buf); + + for (buf) |*sym, i| { + sym.* = .{ + .st_name = self.global_symbols.items[i].st_name, + .st_value = @intCast(u32, self.global_symbols.items[i].st_value), + .st_size = @intCast(u32, self.global_symbols.items[i].st_size), + .st_info = self.global_symbols.items[i].st_info, + .st_other = self.global_symbols.items[i].st_other, + .st_shndx = self.global_symbols.items[i].st_shndx, + }; + if (foreign_endian) { + bswapAllFields(elf.Elf32_Sym, sym); + } + } + try self.base.file.?.pwriteAll(mem.sliceAsBytes(buf), global_syms_off); + }, + .p64 => { + const buf = try self.base.allocator.alloc(elf.Elf64_Sym, self.global_symbols.items.len); + defer self.base.allocator.free(buf); + + for (buf) |*sym, i| { + sym.* = .{ + .st_name = self.global_symbols.items[i].st_name, + .st_value = self.global_symbols.items[i].st_value, + .st_size = self.global_symbols.items[i].st_size, + .st_info = self.global_symbols.items[i].st_info, + .st_other = self.global_symbols.items[i].st_other, + .st_shndx = self.global_symbols.items[i].st_shndx, + }; + if (foreign_endian) { + bswapAllFields(elf.Elf64_Sym, sym); + } + } + try self.base.file.?.pwriteAll(mem.sliceAsBytes(buf), global_syms_off); + }, + } +} + +/// Always 4 or 8 depending on whether this is 32-bit ELF or 64-bit ELF. +fn ptrWidthBytes(self: Elf) u8 { + return switch (self.ptr_width) { + .p32 => 4, + .p64 => 8, + }; +} + +/// Does not necessarily match `ptrWidthBytes` for example can be 2 bytes +/// in a 32-bit ELF file. +fn archPtrWidthBytes(self: Elf) u8 { + return @intCast(u8, self.base.options.target.cpu.arch.ptrBitWidth() / 8); +} + +/// The reloc offset for the virtual address of a function in its Line Number Program. +/// Size is a virtual address integer. +const dbg_line_vaddr_reloc_index = 3; +/// The reloc offset for the virtual address of a function in its .debug_info TAG_subprogram. +/// Size is a virtual address integer. +const dbg_info_low_pc_reloc_index = 1; + +/// The reloc offset for the line offset of a function from the previous function's line. +/// It's a fixed-size 4-byte ULEB128. +fn getRelocDbgLineOff(self: Elf) usize { + return dbg_line_vaddr_reloc_index + self.ptrWidthBytes() + 1; +} + +fn getRelocDbgFileIndex(self: Elf) usize { + return self.getRelocDbgLineOff() + 5; +} + +fn getRelocDbgInfoSubprogramHighPC(self: Elf) u32 { + return dbg_info_low_pc_reloc_index + self.ptrWidthBytes(); +} + +fn dbgLineNeededHeaderBytes(self: Elf) u32 { + const directory_entry_format_count = 1; + const file_name_entry_format_count = 1; + const directory_count = 1; + const file_name_count = 1; + return @intCast(u32, 53 + directory_entry_format_count * 2 + file_name_entry_format_count * 2 + + directory_count * 8 + file_name_count * 8 + + // These are encoded as DW.FORM_string rather than DW.FORM_strp as we would like + // because of a workaround for readelf and gdb failing to understand DWARFv5 correctly. + self.base.options.module.?.root_pkg.root_src_directory.path.?.len + + self.base.options.module.?.root_pkg.root_src_path.len); +} + +fn dbgInfoNeededHeaderBytes(self: Elf) u32 { + return 120; +} + +const min_nop_size = 2; + +/// Writes to the file a buffer, prefixed and suffixed by the specified number of +/// bytes of NOPs. Asserts each padding size is at least `min_nop_size` and total padding bytes +/// are less than 126,976 bytes (if this limit is ever reached, this function can be +/// improved to make more than one pwritev call, or the limit can be raised by a fixed +/// amount by increasing the length of `vecs`). +fn pwriteDbgLineNops( + self: *Elf, + prev_padding_size: usize, + buf: []const u8, + next_padding_size: usize, + offset: usize, +) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const page_of_nops = [1]u8{DW.LNS_negate_stmt} ** 4096; + const three_byte_nop = [3]u8{ DW.LNS_advance_pc, 0b1000_0000, 0 }; + var vecs: [32]std.os.iovec_const = undefined; + var vec_index: usize = 0; + { + var padding_left = prev_padding_size; + if (padding_left % 2 != 0) { + vecs[vec_index] = .{ + .iov_base = &three_byte_nop, + .iov_len = three_byte_nop.len, + }; + vec_index += 1; + padding_left -= three_byte_nop.len; + } + while (padding_left > page_of_nops.len) { + vecs[vec_index] = .{ + .iov_base = &page_of_nops, + .iov_len = page_of_nops.len, + }; + vec_index += 1; + padding_left -= page_of_nops.len; + } + if (padding_left > 0) { + vecs[vec_index] = .{ + .iov_base = &page_of_nops, + .iov_len = padding_left, + }; + vec_index += 1; + } + } + + vecs[vec_index] = .{ + .iov_base = buf.ptr, + .iov_len = buf.len, + }; + vec_index += 1; + + { + var padding_left = next_padding_size; + if (padding_left % 2 != 0) { + vecs[vec_index] = .{ + .iov_base = &three_byte_nop, + .iov_len = three_byte_nop.len, + }; + vec_index += 1; + padding_left -= three_byte_nop.len; + } + while (padding_left > page_of_nops.len) { + vecs[vec_index] = .{ + .iov_base = &page_of_nops, + .iov_len = page_of_nops.len, + }; + vec_index += 1; + padding_left -= page_of_nops.len; + } + if (padding_left > 0) { + vecs[vec_index] = .{ + .iov_base = &page_of_nops, + .iov_len = padding_left, + }; + vec_index += 1; + } + } + try self.base.file.?.pwritevAll(vecs[0..vec_index], offset - prev_padding_size); +} + +/// Writes to the file a buffer, prefixed and suffixed by the specified number of +/// bytes of padding. +fn pwriteDbgInfoNops( + self: *Elf, + prev_padding_size: usize, + buf: []const u8, + next_padding_size: usize, + trailing_zero: bool, + offset: usize, +) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const page_of_nops = [1]u8{abbrev_pad1} ** 4096; + var vecs: [32]std.os.iovec_const = undefined; + var vec_index: usize = 0; + { + var padding_left = prev_padding_size; + while (padding_left > page_of_nops.len) { + vecs[vec_index] = .{ + .iov_base = &page_of_nops, + .iov_len = page_of_nops.len, + }; + vec_index += 1; + padding_left -= page_of_nops.len; + } + if (padding_left > 0) { + vecs[vec_index] = .{ + .iov_base = &page_of_nops, + .iov_len = padding_left, + }; + vec_index += 1; + } + } + + vecs[vec_index] = .{ + .iov_base = buf.ptr, + .iov_len = buf.len, + }; + vec_index += 1; + + { + var padding_left = next_padding_size; + while (padding_left > page_of_nops.len) { + vecs[vec_index] = .{ + .iov_base = &page_of_nops, + .iov_len = page_of_nops.len, + }; + vec_index += 1; + padding_left -= page_of_nops.len; + } + if (padding_left > 0) { + vecs[vec_index] = .{ + .iov_base = &page_of_nops, + .iov_len = padding_left, + }; + vec_index += 1; + } + } + + if (trailing_zero) { + var zbuf = [1]u8{0}; + vecs[vec_index] = .{ + .iov_base = &zbuf, + .iov_len = zbuf.len, + }; + vec_index += 1; + } + + try self.base.file.?.pwritevAll(vecs[0..vec_index], offset - prev_padding_size); +} + +/// Saturating multiplication +fn satMul(a: anytype, b: anytype) @TypeOf(a, b) { + const T = @TypeOf(a, b); + return std.math.mul(T, a, b) catch std.math.maxInt(T); +} + +fn bswapAllFields(comptime S: type, ptr: *S) void { + @panic("TODO implement bswapAllFields"); +} + +fn progHeaderTo32(phdr: elf.Elf64_Phdr) elf.Elf32_Phdr { + return .{ + .p_type = phdr.p_type, + .p_flags = phdr.p_flags, + .p_offset = @intCast(u32, phdr.p_offset), + .p_vaddr = @intCast(u32, phdr.p_vaddr), + .p_paddr = @intCast(u32, phdr.p_paddr), + .p_filesz = @intCast(u32, phdr.p_filesz), + .p_memsz = @intCast(u32, phdr.p_memsz), + .p_align = @intCast(u32, phdr.p_align), + }; +} + +fn sectHeaderTo32(shdr: elf.Elf64_Shdr) elf.Elf32_Shdr { + return .{ + .sh_name = shdr.sh_name, + .sh_type = shdr.sh_type, + .sh_flags = @intCast(u32, shdr.sh_flags), + .sh_addr = @intCast(u32, shdr.sh_addr), + .sh_offset = @intCast(u32, shdr.sh_offset), + .sh_size = @intCast(u32, shdr.sh_size), + .sh_link = shdr.sh_link, + .sh_info = shdr.sh_info, + .sh_addralign = @intCast(u32, shdr.sh_addralign), + .sh_entsize = @intCast(u32, shdr.sh_entsize), + }; +} + +fn getLDMOption(target: std.Target) ?[]const u8 { + switch (target.cpu.arch) { + .i386 => return "elf_i386", + .aarch64 => return "aarch64linux", + .aarch64_be => return "aarch64_be_linux", + .arm, .thumb => return "armelf_linux_eabi", + .armeb, .thumbeb => return "armebelf_linux_eabi", + .powerpc => return "elf32ppclinux", + .powerpc64 => return "elf64ppc", + .powerpc64le => return "elf64lppc", + .sparc, .sparcel => return "elf32_sparc", + .sparcv9 => return "elf64_sparc", + .mips => return "elf32btsmip", + .mipsel => return "elf32ltsmip", + .mips64 => return "elf64btsmip", + .mips64el => return "elf64ltsmip", + .s390x => return "elf64_s390", + .x86_64 => { + if (target.abi == .gnux32) { + return "elf32_x86_64"; + } + // Any target elf will use the freebsd osabi if suffixed with "_fbsd". + if (target.os.tag == .freebsd) { + return "elf_x86_64_fbsd"; + } + return "elf_x86_64"; + }, + .riscv32 => return "elf32lriscv", + .riscv64 => return "elf64lriscv", + else => return null, + } +} diff --git a/src/link/MachO.zig b/src/link/MachO.zig new file mode 100644 index 0000000000..3b70a0d710 --- /dev/null +++ b/src/link/MachO.zig @@ -0,0 +1,724 @@ +const MachO = @This(); + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; +const fs = std.fs; +const log = std.log.scoped(.link); +const macho = std.macho; +const codegen = @import("../codegen.zig"); +const math = std.math; +const mem = std.mem; + +const trace = @import("../tracy.zig").trace; +const Type = @import("../type.zig").Type; +const build_options = @import("build_options"); +const Module = @import("../Module.zig"); +const Compilation = @import("../Compilation.zig"); +const link = @import("../link.zig"); +const File = link.File; + +pub const base_tag: File.Tag = File.Tag.macho; + +const LoadCommand = union(enum) { + Segment: macho.segment_command_64, + LinkeditData: macho.linkedit_data_command, + Symtab: macho.symtab_command, + Dysymtab: macho.dysymtab_command, + + pub fn cmdsize(self: LoadCommand) u32 { + return switch (self) { + .Segment => |x| x.cmdsize, + .LinkeditData => |x| x.cmdsize, + .Symtab => |x| x.cmdsize, + .Dysymtab => |x| x.cmdsize, + }; + } + + pub fn write(self: LoadCommand, file: *fs.File, offset: u64) !void { + return switch (self) { + .Segment => |cmd| writeGeneric(cmd, file, offset), + .LinkeditData => |cmd| writeGeneric(cmd, file, offset), + .Symtab => |cmd| writeGeneric(cmd, file, offset), + .Dysymtab => |cmd| writeGeneric(cmd, file, offset), + }; + } + + fn writeGeneric(cmd: anytype, file: *fs.File, offset: u64) !void { + const slice = [1]@TypeOf(cmd){cmd}; + return file.pwriteAll(mem.sliceAsBytes(slice[0..1]), offset); + } +}; + +base: File, + +/// Table of all load commands +load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, +segment_cmd_index: ?u16 = null, +symtab_cmd_index: ?u16 = null, +dysymtab_cmd_index: ?u16 = null, +data_in_code_cmd_index: ?u16 = null, + +/// Table of all sections +sections: std.ArrayListUnmanaged(macho.section_64) = .{}, + +/// __TEXT segment sections +text_section_index: ?u16 = null, +cstring_section_index: ?u16 = null, +const_text_section_index: ?u16 = null, +stubs_section_index: ?u16 = null, +stub_helper_section_index: ?u16 = null, + +/// __DATA segment sections +got_section_index: ?u16 = null, +const_data_section_index: ?u16 = null, + +entry_addr: ?u64 = null, + +/// Table of all symbols used. +/// Internally references string table for names (which are optional). +symbol_table: std.ArrayListUnmanaged(macho.nlist_64) = .{}, + +/// Table of symbol names aka the string table. +string_table: std.ArrayListUnmanaged(u8) = .{}, + +/// Table of symbol vaddr values. The values is the absolute vaddr value. +/// If the vaddr of the executable __TEXT segment vaddr changes, the entire offset +/// table needs to be rewritten. +offset_table: std.ArrayListUnmanaged(u64) = .{}, + +error_flags: File.ErrorFlags = File.ErrorFlags{}, + +cmd_table_dirty: bool = false, + +/// Pointer to the last allocated text block +last_text_block: ?*TextBlock = null, + +/// `alloc_num / alloc_den` is the factor of padding when allocating. +const alloc_num = 4; +const alloc_den = 3; + +/// Default path to dyld +/// TODO instead of hardcoding it, we should probably look through some env vars and search paths +/// instead but this will do for now. +const DEFAULT_DYLD_PATH: [*:0]const u8 = "/usr/lib/dyld"; + +/// Default lib search path +/// TODO instead of hardcoding it, we should probably look through some env vars and search paths +/// instead but this will do for now. +const DEFAULT_LIB_SEARCH_PATH: []const u8 = "/usr/lib"; + +const LIB_SYSTEM_NAME: [*:0]const u8 = "System"; +/// TODO we should search for libSystem and fail if it doesn't exist, instead of hardcoding it +const LIB_SYSTEM_PATH: [*:0]const u8 = DEFAULT_LIB_SEARCH_PATH ++ "/libSystem.B.dylib"; + +pub const TextBlock = struct { + /// Index into the symbol table + symbol_table_index: ?u32, + /// Index into offset table + offset_table_index: ?u32, + /// Size of this text block + size: u64, + /// Points to the previous and next neighbours + prev: ?*TextBlock, + next: ?*TextBlock, + + pub const empty = TextBlock{ + .symbol_table_index = null, + .offset_table_index = null, + .size = 0, + .prev = null, + .next = null, + }; +}; + +pub const SrcFn = struct { + pub const empty = SrcFn{}; +}; + +pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*MachO { + assert(options.object_format == .macho); + + if (options.use_llvm) return error.LLVM_BackendIsTODO_ForMachO; // TODO + if (options.use_lld) return error.LLD_LinkingIsTODO_ForMachO; // TODO + + const file = try options.directory.handle.createFile(sub_path, .{ + .truncate = false, + .read = true, + .mode = link.determineMode(options), + }); + errdefer file.close(); + + const self = try createEmpty(allocator, options); + errdefer self.base.destroy(); + + self.base.file = file; + + switch (options.output_mode) { + .Exe => {}, + .Obj => {}, + .Lib => return error.TODOImplementWritingLibFiles, + } + + try self.populateMissingMetadata(); + + return self; +} + +pub fn createEmpty(gpa: *Allocator, options: link.Options) !*MachO { + const self = try gpa.create(MachO); + self.* = .{ + .base = .{ + .tag = .macho, + .options = options, + .allocator = gpa, + .file = null, + }, + }; + return self; +} + +pub fn flush(self: *MachO, comp: *Compilation) !void { + if (build_options.have_llvm and self.base.options.use_lld) { + return error.MachOLLDLinkingUnimplemented; + } else { + return self.flushModule(comp); + } +} + +pub fn flushModule(self: *MachO, comp: *Compilation) !void { + const tracy = trace(@src()); + defer tracy.end(); + + switch (self.base.options.output_mode) { + .Exe => { + var last_cmd_offset: usize = @sizeOf(macho.mach_header_64); + { + // Specify path to dynamic linker dyld + const cmdsize = commandSize(@sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH)); + const load_dylinker = [1]macho.dylinker_command{ + .{ + .cmd = macho.LC_LOAD_DYLINKER, + .cmdsize = cmdsize, + .name = @sizeOf(macho.dylinker_command), + }, + }; + + try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylinker[0..1]), last_cmd_offset); + + const file_offset = last_cmd_offset + @sizeOf(macho.dylinker_command); + try self.addPadding(cmdsize - @sizeOf(macho.dylinker_command), file_offset); + + try self.base.file.?.pwriteAll(mem.spanZ(DEFAULT_DYLD_PATH), file_offset); + last_cmd_offset += cmdsize; + } + + { + // Link against libSystem + const cmdsize = commandSize(@sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH)); + // TODO Find a way to work out runtime version from the OS version triple stored in std.Target. + // In the meantime, we're gonna hardcode to the minimum compatibility version of 1.0.0. + const min_version = 0x10000; + const dylib = .{ + .name = @sizeOf(macho.dylib_command), + .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files + .current_version = min_version, + .compatibility_version = min_version, + }; + const load_dylib = [1]macho.dylib_command{ + .{ + .cmd = macho.LC_LOAD_DYLIB, + .cmdsize = cmdsize, + .dylib = dylib, + }, + }; + + try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylib[0..1]), last_cmd_offset); + + const file_offset = last_cmd_offset + @sizeOf(macho.dylib_command); + try self.addPadding(cmdsize - @sizeOf(macho.dylib_command), file_offset); + + try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), file_offset); + last_cmd_offset += cmdsize; + } + }, + .Obj => { + { + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + symtab.nsyms = @intCast(u32, self.symbol_table.items.len); + const allocated_size = self.allocatedSize(symtab.stroff); + const needed_size = self.string_table.items.len; + log.debug("allocated_size = 0x{x}, needed_size = 0x{x}\n", .{ allocated_size, needed_size }); + + if (needed_size > allocated_size) { + symtab.strsize = 0; + symtab.stroff = @intCast(u32, self.findFreeSpace(needed_size, 1)); + } + symtab.strsize = @intCast(u32, needed_size); + + log.debug("writing string table from 0x{x} to 0x{x}\n", .{ symtab.stroff, symtab.stroff + symtab.strsize }); + + try self.base.file.?.pwriteAll(self.string_table.items, symtab.stroff); + } + + var last_cmd_offset: usize = @sizeOf(macho.mach_header_64); + for (self.load_commands.items) |cmd| { + try cmd.write(&self.base.file.?, last_cmd_offset); + last_cmd_offset += cmd.cmdsize(); + } + const off = @sizeOf(macho.mach_header_64) + @sizeOf(macho.segment_command_64); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items), off); + }, + .Lib => return error.TODOImplementWritingLibFiles, + } + + 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; + try self.writeMachOHeader(); + } +} + +pub fn deinit(self: *MachO) void { + self.offset_table.deinit(self.base.allocator); + self.string_table.deinit(self.base.allocator); + self.symbol_table.deinit(self.base.allocator); + self.sections.deinit(self.base.allocator); + self.load_commands.deinit(self.base.allocator); +} + +pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void { + if (decl.link.macho.symbol_table_index) |_| return; + + try self.symbol_table.ensureCapacity(self.base.allocator, self.symbol_table.items.len + 1); + try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1); + + log.debug("allocating symbol index {} for {}\n", .{ self.symbol_table.items.len, decl.name }); + decl.link.macho.symbol_table_index = @intCast(u32, self.symbol_table.items.len); + _ = self.symbol_table.addOneAssumeCapacity(); + + decl.link.macho.offset_table_index = @intCast(u32, self.offset_table.items.len); + _ = self.offset_table.addOneAssumeCapacity(); + + self.symbol_table.items[decl.link.macho.symbol_table_index.?] = .{ + .n_strx = 0, + .n_type = 0, + .n_sect = 0, + .n_desc = 0, + .n_value = 0, + }; + self.offset_table.items[decl.link.macho.offset_table_index.?] = 0; +} + +pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { + const tracy = trace(@src()); + defer tracy.end(); + + var code_buffer = std.ArrayList(u8).init(self.base.allocator); + defer code_buffer.deinit(); + + const typed_value = decl.typed_value.most_recent.typed_value; + const res = try codegen.generateSymbol(&self.base, decl.src(), typed_value, &code_buffer, .none); + + const code = switch (res) { + .externally_managed => |x| x, + .appended => code_buffer.items, + .fail => |em| { + decl.analysis = .codegen_failure; + try module.failed_decls.put(module.gpa, decl, em); + return; + }, + }; + log.debug("generated code {}\n", .{code}); + + const required_alignment = typed_value.ty.abiAlignment(self.base.options.target); + const symbol = &self.symbol_table.items[decl.link.macho.symbol_table_index.?]; + + const decl_name = mem.spanZ(decl.name); + const name_str_index = try self.makeString(decl_name); + const addr = try self.allocateTextBlock(&decl.link.macho, code.len, required_alignment); + log.debug("allocated text block for {} at 0x{x}\n", .{ decl_name, addr }); + log.debug("updated text section {}\n", .{self.sections.items[self.text_section_index.?]}); + + symbol.* = .{ + .n_strx = name_str_index, + .n_type = macho.N_SECT, + .n_sect = @intCast(u8, self.text_section_index.?) + 1, + .n_desc = 0, + .n_value = addr, + }; + + // 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) orelse &[0]*Module.Export{}; + try self.updateDeclExports(module, decl, decl_exports); + try self.writeSymbol(decl.link.macho.symbol_table_index.?); + + const text_section = self.sections.items[self.text_section_index.?]; + const section_offset = symbol.n_value - text_section.addr; + const file_offset = text_section.offset + section_offset; + log.debug("file_offset 0x{x}\n", .{file_offset}); + + try self.base.file.?.pwriteAll(code, file_offset); +} + +pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl: *const Module.Decl) !void {} + +pub fn updateDeclExports( + self: *MachO, + module: *Module, + decl: *const Module.Decl, + exports: []const *Module.Export, +) !void { + const tracy = trace(@src()); + defer tracy.end(); + + if (decl.link.macho.symbol_table_index == null) return; + + const decl_sym = &self.symbol_table.items[decl.link.macho.symbol_table_index.?]; + // TODO implement + if (exports.len == 0) return; + + const exp = exports[0]; + self.entry_addr = decl_sym.n_value; + decl_sym.n_type |= macho.N_EXT; + exp.link.sym_index = 0; +} + +pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {} + +pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 { + return self.symbol_table.items[decl.link.macho.symbol_table_index.?].n_value; +} + +pub fn populateMissingMetadata(self: *MachO) !void { + if (self.segment_cmd_index == null) { + self.segment_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.base.allocator, .{ + .Segment = .{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = @sizeOf(macho.segment_command_64), + .segname = makeStaticString(""), + .vmaddr = 0, + .vmsize = 0, + .fileoff = 0, + .filesize = 0, + .maxprot = 0, + .initprot = 0, + .nsects = 0, + .flags = 0, + }, + }); + self.cmd_table_dirty = true; + } + if (self.symtab_cmd_index == null) { + self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.base.allocator, .{ + .Symtab = .{ + .cmd = macho.LC_SYMTAB, + .cmdsize = @sizeOf(macho.symtab_command), + .symoff = 0, + .nsyms = 0, + .stroff = 0, + .strsize = 0, + }, + }); + self.cmd_table_dirty = true; + } + if (self.text_section_index == null) { + self.text_section_index = @intCast(u16, self.sections.items.len); + const segment = &self.load_commands.items[self.segment_cmd_index.?].Segment; + segment.cmdsize += @sizeOf(macho.section_64); + segment.nsects += 1; + + const file_size = self.base.options.program_code_size_hint; + const off = @intCast(u32, self.findFreeSpace(file_size, 1)); + const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; + + log.debug("found __text section free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + + try self.sections.append(self.base.allocator, .{ + .sectname = makeStaticString("__text"), + .segname = makeStaticString("__TEXT"), + .addr = 0, + .size = file_size, + .offset = off, + .@"align" = 0x1000, + .reloff = 0, + .nreloc = 0, + .flags = flags, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + + segment.vmsize += file_size; + segment.filesize += file_size; + segment.fileoff = off; + + log.debug("initial text section {}\n", .{self.sections.items[self.text_section_index.?]}); + } + { + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + if (symtab.symoff == 0) { + const p_align = @sizeOf(macho.nlist_64); + const nsyms = self.base.options.symbol_count_hint; + const file_size = p_align * nsyms; + const off = @intCast(u32, self.findFreeSpace(file_size, p_align)); + log.debug("found symbol table free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + symtab.symoff = off; + symtab.nsyms = @intCast(u32, nsyms); + } + if (symtab.stroff == 0) { + try self.string_table.append(self.base.allocator, 0); + const file_size = @intCast(u32, self.string_table.items.len); + const off = @intCast(u32, self.findFreeSpace(file_size, 1)); + log.debug("found string table free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + symtab.stroff = off; + symtab.strsize = file_size; + } + } +} + +fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { + const segment = &self.load_commands.items[self.segment_cmd_index.?].Segment; + const text_section = &self.sections.items[self.text_section_index.?]; + const new_block_ideal_capacity = new_block_size * alloc_num / alloc_den; + + var block_placement: ?*TextBlock = null; + const addr = blk: { + if (self.last_text_block) |last| { + const last_symbol = self.symbol_table.items[last.symbol_table_index.?]; + const end_addr = last_symbol.n_value + last.size; + const new_start_addr = mem.alignForwardGeneric(u64, end_addr, alignment); + block_placement = last; + break :blk new_start_addr; + } else { + break :blk text_section.addr; + } + }; + log.debug("computed symbol address 0x{x}\n", .{addr}); + + const expand_text_section = block_placement == null or block_placement.?.next == null; + if (expand_text_section) { + const text_capacity = self.allocatedSize(text_section.offset); + const needed_size = (addr + new_block_size) - text_section.addr; + log.debug("text capacity 0x{x}, needed size 0x{x}\n", .{ text_capacity, needed_size }); + assert(needed_size <= text_capacity); // TODO handle growth + + self.last_text_block = text_block; + text_section.size = needed_size; + segment.vmsize = needed_size; + segment.filesize = needed_size; + if (alignment < text_section.@"align") { + text_section.@"align" = @intCast(u32, alignment); + } + } + text_block.size = new_block_size; + + if (text_block.prev) |prev| { + prev.next = text_block.next; + } + if (text_block.next) |next| { + next.prev = text_block.prev; + } + + if (block_placement) |big_block| { + text_block.prev = big_block; + text_block.next = big_block.next; + big_block.next = text_block; + } else { + text_block.prev = null; + text_block.next = null; + } + + return addr; +} + +fn makeStaticString(comptime bytes: []const u8) [16]u8 { + var buf = [_]u8{0} ** 16; + if (bytes.len > buf.len) @compileError("string too long; max 16 bytes"); + mem.copy(u8, buf[0..], bytes); + return buf; +} + +fn makeString(self: *MachO, bytes: []const u8) !u32 { + try self.string_table.ensureCapacity(self.base.allocator, self.string_table.items.len + bytes.len + 1); + const result = self.string_table.items.len; + self.string_table.appendSliceAssumeCapacity(bytes); + self.string_table.appendAssumeCapacity(0); + return @intCast(u32, result); +} + +fn alignSize(comptime Int: type, min_size: anytype, alignment: Int) Int { + const size = @intCast(Int, min_size); + if (size % alignment == 0) return size; + + const div = size / alignment; + return (div + 1) * alignment; +} + +fn commandSize(min_size: anytype) u32 { + return alignSize(u32, min_size, @sizeOf(u64)); +} + +fn addPadding(self: *MachO, size: u64, file_offset: u64) !void { + if (size == 0) return; + + const buf = try self.base.allocator.alloc(u8, size); + defer self.base.allocator.free(buf); + + mem.set(u8, buf[0..], 0); + + try self.base.file.?.pwriteAll(buf, file_offset); +} + +fn detectAllocCollision(self: *MachO, start: u64, size: u64) ?u64 { + const hdr_size: u64 = @sizeOf(macho.mach_header_64); + if (start < hdr_size) + return hdr_size; + + const end = start + satMul(size, alloc_num) / alloc_den; + + { + const off = @sizeOf(macho.mach_header_64); + var tight_size: u64 = 0; + for (self.load_commands.items) |cmd| { + tight_size += cmd.cmdsize(); + } + const increased_size = satMul(tight_size, alloc_num) / alloc_den; + const test_end = off + increased_size; + if (end > off and start < test_end) { + return test_end; + } + } + + for (self.sections.items) |section| { + const increased_size = satMul(section.size, alloc_num) / alloc_den; + const test_end = section.offset + increased_size; + if (end > section.offset and start < test_end) { + return test_end; + } + } + + if (self.symtab_cmd_index) |symtab_index| { + const symtab = self.load_commands.items[symtab_index].Symtab; + { + const tight_size = @sizeOf(macho.nlist_64) * symtab.nsyms; + const increased_size = satMul(tight_size, alloc_num) / alloc_den; + const test_end = symtab.symoff + increased_size; + if (end > symtab.symoff and start < test_end) { + return test_end; + } + } + { + const increased_size = satMul(symtab.strsize, alloc_num) / alloc_den; + const test_end = symtab.stroff + increased_size; + if (end > symtab.stroff and start < test_end) { + return test_end; + } + } + } + + return null; +} + +fn allocatedSize(self: *MachO, start: u64) u64 { + if (start == 0) + return 0; + var min_pos: u64 = std.math.maxInt(u64); + { + const off = @sizeOf(macho.mach_header_64); + if (off > start and off < min_pos) min_pos = off; + } + for (self.sections.items) |section| { + if (section.offset <= start) continue; + if (section.offset < min_pos) min_pos = section.offset; + } + if (self.symtab_cmd_index) |symtab_index| { + const symtab = self.load_commands.items[symtab_index].Symtab; + if (symtab.symoff > start and symtab.symoff < min_pos) min_pos = symtab.symoff; + if (symtab.stroff > start and symtab.stroff < min_pos) min_pos = symtab.stroff; + } + return min_pos - start; +} + +fn findFreeSpace(self: *MachO, object_size: u64, min_alignment: u16) u64 { + var start: u64 = 0; + while (self.detectAllocCollision(start, object_size)) |item_end| { + start = mem.alignForwardGeneric(u64, item_end, min_alignment); + } + return start; +} + +fn writeSymbol(self: *MachO, index: usize) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + const sym = [1]macho.nlist_64{self.symbol_table.items[index]}; + const off = symtab.symoff + @sizeOf(macho.nlist_64) * index; + log.debug("writing symbol {} at 0x{x}\n", .{ sym[0], off }); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off); +} + +/// Writes Mach-O file header. +/// Should be invoked last as it needs up-to-date values of ncmds and sizeof_cmds bookkeeping +/// variables. +fn writeMachOHeader(self: *MachO) !void { + var hdr: macho.mach_header_64 = undefined; + hdr.magic = macho.MH_MAGIC_64; + + const CpuInfo = struct { + cpu_type: macho.cpu_type_t, + cpu_subtype: macho.cpu_subtype_t, + }; + + const cpu_info: CpuInfo = switch (self.base.options.target.cpu.arch) { + .aarch64 => .{ + .cpu_type = macho.CPU_TYPE_ARM64, + .cpu_subtype = macho.CPU_SUBTYPE_ARM_ALL, + }, + .x86_64 => .{ + .cpu_type = macho.CPU_TYPE_X86_64, + .cpu_subtype = macho.CPU_SUBTYPE_X86_64_ALL, + }, + else => return error.UnsupportedMachOArchitecture, + }; + hdr.cputype = cpu_info.cpu_type; + hdr.cpusubtype = cpu_info.cpu_subtype; + + const filetype: u32 = switch (self.base.options.output_mode) { + .Exe => macho.MH_EXECUTE, + .Obj => macho.MH_OBJECT, + .Lib => switch (self.base.options.link_mode) { + .Static => return error.TODOStaticLibMachOType, + .Dynamic => macho.MH_DYLIB, + }, + }; + hdr.filetype = filetype; + hdr.ncmds = @intCast(u32, self.load_commands.items.len); + + var sizeofcmds: u32 = 0; + for (self.load_commands.items) |cmd| { + sizeofcmds += cmd.cmdsize(); + } + + hdr.sizeofcmds = sizeofcmds; + + // TODO should these be set to something else? + hdr.flags = 0; + hdr.reserved = 0; + + log.debug("writing Mach-O header {}\n", .{hdr}); + + try self.base.file.?.pwriteAll(@ptrCast([*]const u8, &hdr)[0..@sizeOf(macho.mach_header_64)], 0); +} + +/// Saturating multiplication +fn satMul(a: anytype, b: anytype) @TypeOf(a, b) { + const T = @TypeOf(a, b); + return std.math.mul(T, a, b) catch std.math.maxInt(T); +} diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig new file mode 100644 index 0000000000..1160e471fe --- /dev/null +++ b/src/link/Wasm.zig @@ -0,0 +1,274 @@ +const Wasm = @This(); + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; +const fs = std.fs; +const leb = std.debug.leb; + +const Module = @import("../Module.zig"); +const Compilation = @import("../Compilation.zig"); +const codegen = @import("../codegen/wasm.zig"); +const link = @import("../link.zig"); +const trace = @import("../tracy.zig").trace; +const build_options = @import("build_options"); + +/// Various magic numbers defined by the wasm spec +const spec = struct { + const magic = [_]u8{ 0x00, 0x61, 0x73, 0x6D }; // \0asm + const version = [_]u8{ 0x01, 0x00, 0x00, 0x00 }; // version 1 + + const custom_id = 0; + const types_id = 1; + const imports_id = 2; + const funcs_id = 3; + const tables_id = 4; + const memories_id = 5; + const globals_id = 6; + const exports_id = 7; + const start_id = 8; + const elements_id = 9; + const code_id = 10; + const data_id = 11; +}; + +pub const base_tag = link.File.Tag.wasm; + +pub const FnData = struct { + /// Generated code for the type of the function + functype: std.ArrayListUnmanaged(u8) = .{}, + /// Generated code for the body of the function + code: std.ArrayListUnmanaged(u8) = .{}, + /// Locations in the generated code where function indexes must be filled in. + /// This must be kept ordered by offset. + idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: *Module.Decl }) = .{}, +}; + +base: link.File, + +/// List of all function Decls to be written to the output file. The index of +/// each Decl in this list at the time of writing the binary is used as the +/// function index. +/// TODO: can/should we access some data structure in Module directly? +funcs: std.ArrayListUnmanaged(*Module.Decl) = .{}, + +pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm { + assert(options.object_format == .wasm); + + if (options.use_llvm) return error.LLVM_BackendIsTODO_ForWasm; // TODO + if (options.use_lld) return error.LLD_LinkingIsTODO_ForWasm; // TODO + + // TODO: read the file and keep vaild parts instead of truncating + const file = try options.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true }); + errdefer file.close(); + + const wasm = try createEmpty(allocator, options); + errdefer wasm.base.destroy(); + + wasm.base.file = file; + + try file.writeAll(&(spec.magic ++ spec.version)); + + return wasm; +} + +pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Wasm { + const wasm = try gpa.create(Wasm); + wasm.* = .{ + .base = .{ + .tag = .wasm, + .options = options, + .file = null, + .allocator = gpa, + }, + }; + return wasm; +} + +pub fn deinit(self: *Wasm) void { + for (self.funcs.items) |decl| { + decl.fn_link.wasm.?.functype.deinit(self.base.allocator); + decl.fn_link.wasm.?.code.deinit(self.base.allocator); + decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator); + } + self.funcs.deinit(self.base.allocator); +} + +// Generate code for the Decl, storing it in memory to be later written to +// the file on flush(). +pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { + if (decl.typed_value.most_recent.typed_value.ty.zigTypeTag() != .Fn) + return error.TODOImplementNonFnDeclsForWasm; + + if (decl.fn_link.wasm) |*fn_data| { + fn_data.functype.items.len = 0; + fn_data.code.items.len = 0; + fn_data.idx_refs.items.len = 0; + } else { + decl.fn_link.wasm = .{}; + try self.funcs.append(self.base.allocator, decl); + } + const fn_data = &decl.fn_link.wasm.?; + + var managed_functype = fn_data.functype.toManaged(self.base.allocator); + var managed_code = fn_data.code.toManaged(self.base.allocator); + try codegen.genFunctype(&managed_functype, decl); + try codegen.genCode(&managed_code, decl); + fn_data.functype = managed_functype.toUnmanaged(); + fn_data.code = managed_code.toUnmanaged(); +} + +pub fn updateDeclExports( + self: *Wasm, + module: *Module, + decl: *const Module.Decl, + exports: []const *Module.Export, +) !void {} + +pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void { + // TODO: remove this assert when non-function Decls are implemented + assert(decl.typed_value.most_recent.typed_value.ty.zigTypeTag() == .Fn); + _ = self.funcs.swapRemove(self.getFuncidx(decl).?); + decl.fn_link.wasm.?.functype.deinit(self.base.allocator); + decl.fn_link.wasm.?.code.deinit(self.base.allocator); + decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator); + decl.fn_link.wasm = null; +} + +pub fn flush(self: *Wasm, comp: *Compilation) !void { + if (build_options.have_llvm and self.base.options.use_lld) { + return error.WasmLinkingWithLLDUnimplemented; + } else { + return self.flushModule(comp); + } +} + +pub fn flushModule(self: *Wasm, comp: *Compilation) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const file = self.base.file.?; + const header_size = 5 + 1; + + // No need to rewrite the magic/version header + try file.setEndPos(@sizeOf(@TypeOf(spec.magic ++ spec.version))); + try file.seekTo(@sizeOf(@TypeOf(spec.magic ++ spec.version))); + + // Type section + { + const header_offset = try reserveVecSectionHeader(file); + for (self.funcs.items) |decl| { + try file.writeAll(decl.fn_link.wasm.?.functype.items); + } + try writeVecSectionHeader( + file, + header_offset, + spec.types_id, + @intCast(u32, (try file.getPos()) - header_offset - header_size), + @intCast(u32, self.funcs.items.len), + ); + } + + // Function section + { + const header_offset = try reserveVecSectionHeader(file); + const writer = file.writer(); + for (self.funcs.items) |_, typeidx| try leb.writeULEB128(writer, @intCast(u32, typeidx)); + try writeVecSectionHeader( + file, + header_offset, + spec.funcs_id, + @intCast(u32, (try file.getPos()) - header_offset - header_size), + @intCast(u32, self.funcs.items.len), + ); + } + + // Export section + if (self.base.options.module) |module| { + const header_offset = try reserveVecSectionHeader(file); + const writer = file.writer(); + var count: u32 = 0; + for (module.decl_exports.entries.items) |entry| { + for (entry.value) |exprt| { + // Export name length + name + try leb.writeULEB128(writer, @intCast(u32, exprt.options.name.len)); + try writer.writeAll(exprt.options.name); + + switch (exprt.exported_decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) { + .Fn => { + // Type of the export + try writer.writeByte(0x00); + // Exported function index + try leb.writeULEB128(writer, self.getFuncidx(exprt.exported_decl).?); + }, + else => return error.TODOImplementNonFnDeclsForWasm, + } + + count += 1; + } + } + try writeVecSectionHeader( + file, + header_offset, + spec.exports_id, + @intCast(u32, (try file.getPos()) - header_offset - header_size), + count, + ); + } + + // Code section + { + const header_offset = try reserveVecSectionHeader(file); + const writer = file.writer(); + for (self.funcs.items) |decl| { + const fn_data = &decl.fn_link.wasm.?; + + // Write the already generated code to the file, inserting + // function indexes where required. + var current: u32 = 0; + for (fn_data.idx_refs.items) |idx_ref| { + try writer.writeAll(fn_data.code.items[current..idx_ref.offset]); + current = idx_ref.offset; + // Use a fixed width here to make calculating the code size + // in codegen.wasm.genCode() simpler. + var buf: [5]u8 = undefined; + leb.writeUnsignedFixed(5, &buf, self.getFuncidx(idx_ref.decl).?); + try writer.writeAll(&buf); + } + + try writer.writeAll(fn_data.code.items[current..]); + } + try writeVecSectionHeader( + file, + header_offset, + spec.code_id, + @intCast(u32, (try file.getPos()) - header_offset - header_size), + @intCast(u32, self.funcs.items.len), + ); + } +} + +/// Get the current index of a given Decl in the function list +/// TODO: we could maintain a hash map to potentially make this +fn getFuncidx(self: Wasm, decl: *Module.Decl) ?u32 { + return for (self.funcs.items) |func, idx| { + if (func == decl) break @intCast(u32, idx); + } else null; +} + +fn reserveVecSectionHeader(file: fs.File) !u64 { + // section id + fixed leb contents size + fixed leb vector length + const header_size = 1 + 5 + 5; + // TODO: this should be a single lseek(2) call, but fs.File does not + // currently provide a way to do this. + try file.seekBy(header_size); + return (try file.getPos()) - header_size; +} + +fn writeVecSectionHeader(file: fs.File, offset: u64, section: u8, size: u32, items: u32) !void { + var buf: [1 + 5 + 5]u8 = undefined; + buf[0] = section; + leb.writeUnsignedFixed(5, buf[1..6], size); + leb.writeUnsignedFixed(5, buf[6..], items); + try file.pwriteAll(&buf, offset); +} diff --git a/src/link/cbe.h b/src/link/cbe.h new file mode 100644 index 0000000000..854032227d --- /dev/null +++ b/src/link/cbe.h @@ -0,0 +1,15 @@ +#if __STDC_VERSION__ >= 201112L +#define zig_noreturn _Noreturn +#elif __GNUC__ +#define zig_noreturn __attribute__ ((noreturn)) +#elif _MSC_VER +#define zig_noreturn __declspec(noreturn) +#else +#define zig_noreturn +#endif + +#if __GNUC__ +#define zig_unreachable() __builtin_unreachable() +#else +#define zig_unreachable() +#endif diff --git a/src/link/msdos-stub.bin b/src/link/msdos-stub.bin Binary files differnew file mode 100644 index 0000000000..96ad91198f --- /dev/null +++ b/src/link/msdos-stub.bin |
