aboutsummaryrefslogtreecommitdiff
path: root/src/link
diff options
context:
space:
mode:
authorJakub Konka <kubkon@jakubkonka.com>2022-09-09 13:08:58 +0200
committerGitHub <noreply@github.com>2022-09-09 13:08:58 +0200
commit56b96cd61b0bdb7f5b11a5283fe6dd5b585ef10e (patch)
treea19765f949fd7ce1d1d6fdbcf684a812d30e634a /src/link
parenta833bdcd7e6fcfee6e9cc33a3f7de78b16a36941 (diff)
parent5006fb6846ccaa7edb1547588cf1aa08c8decf2b (diff)
downloadzig-56b96cd61b0bdb7f5b11a5283fe6dd5b585ef10e.tar.gz
zig-56b96cd61b0bdb7f5b11a5283fe6dd5b585ef10e.zip
Merge pull request #12772 from ziglang/coff-basic-imports
coff: implement enough of the incremental linker to pass behavior and incremental tests on Windows
Diffstat (limited to 'src/link')
-rw-r--r--src/link/Coff.zig895
-rw-r--r--src/link/Coff/Atom.zig17
-rw-r--r--src/link/MachO.zig51
-rw-r--r--src/link/MachO/Archive.zig1
-rw-r--r--src/link/MachO/DebugSymbols.zig1
-rw-r--r--src/link/Wasm.zig18
-rw-r--r--src/link/Wasm/Archive.zig1
-rw-r--r--src/link/Wasm/Object.zig3
-rw-r--r--src/link/strtab.zig4
9 files changed, 742 insertions, 249 deletions
diff --git a/src/link/Coff.zig b/src/link/Coff.zig
index e302571671..49263df225 100644
--- a/src/link/Coff.zig
+++ b/src/link/Coff.zig
@@ -30,7 +30,6 @@ const TypedValue = @import("../TypedValue.zig");
pub const base_tag: link.File.Tag = .coff;
const msdos_stub = @embedFile("msdos-stub.bin");
-const N_DATA_DIRS: u5 = 16;
/// If this is not null, an object file is created by LLVM and linked with LLD afterwards.
llvm_object: ?*LlvmObject = null,
@@ -44,24 +43,33 @@ page_size: u32,
objects: std.ArrayListUnmanaged(Object) = .{},
sections: std.MultiArrayList(Section) = .{},
-data_directories: [N_DATA_DIRS]coff.ImageDataDirectory,
+data_directories: [coff.IMAGE_NUMBEROF_DIRECTORY_ENTRIES]coff.ImageDataDirectory,
text_section_index: ?u16 = null,
got_section_index: ?u16 = null,
rdata_section_index: ?u16 = null,
data_section_index: ?u16 = null,
reloc_section_index: ?u16 = null,
+idata_section_index: ?u16 = null,
locals: std.ArrayListUnmanaged(coff.Symbol) = .{},
-globals: std.StringArrayHashMapUnmanaged(SymbolWithLoc) = .{},
+globals: std.ArrayListUnmanaged(SymbolWithLoc) = .{},
+resolver: std.StringHashMapUnmanaged(u32) = .{},
+unresolved: std.AutoArrayHashMapUnmanaged(u32, bool) = .{},
locals_free_list: std.ArrayListUnmanaged(u32) = .{},
+globals_free_list: std.ArrayListUnmanaged(u32) = .{},
strtab: StringTable(.strtab) = .{},
strtab_offset: ?u32 = null,
-got_entries: std.AutoArrayHashMapUnmanaged(SymbolWithLoc, u32) = .{},
+got_entries: std.ArrayListUnmanaged(Entry) = .{},
got_entries_free_list: std.ArrayListUnmanaged(u32) = .{},
+got_entries_table: std.AutoHashMapUnmanaged(SymbolWithLoc, u32) = .{},
+
+imports: std.ArrayListUnmanaged(Entry) = .{},
+imports_free_list: std.ArrayListUnmanaged(u32) = .{},
+imports_table: std.AutoHashMapUnmanaged(SymbolWithLoc, u32) = .{},
/// Virtual address of the entry point procedure relative to image base.
entry_addr: ?u32 = null,
@@ -109,17 +117,33 @@ relocs: RelocTable = .{},
/// this will be a table indexed by index into the list of Atoms.
base_relocs: BaseRelocationTable = .{},
+const Entry = struct {
+ target: SymbolWithLoc,
+ // Index into the synthetic symbol table (i.e., file == null).
+ sym_index: u32,
+};
+
pub const Reloc = struct {
@"type": enum {
got,
direct,
+ imports,
},
target: SymbolWithLoc,
offset: u32,
addend: u32,
pcrel: bool,
length: u2,
- prev_vaddr: u32,
+ dirty: bool = true,
+
+ /// Returns an Atom which is the target node of this relocation edge (if any).
+ fn getTargetAtom(self: Reloc, coff_file: *Coff) ?*Atom {
+ switch (self.@"type") {
+ .got => return coff_file.getGotAtomForSymbol(self.target),
+ .direct => return coff_file.getAtomForSymbol(self.target),
+ .imports => return coff_file.getImportAtomForSymbol(self.target),
+ }
+ }
};
const RelocTable = std.AutoHashMapUnmanaged(*Atom, std.ArrayListUnmanaged(Reloc));
@@ -180,6 +204,16 @@ pub const SymbolWithLoc = struct {
// null means it's a synthetic global or Zig source.
file: ?u32 = null,
+
+ pub fn eql(this: SymbolWithLoc, other: SymbolWithLoc) bool {
+ if (this.file == null and other.file == null) {
+ return this.sym_index == other.sym_index;
+ }
+ if (this.file != null and other.file != null) {
+ return this.sym_index == other.sym_index and this.file.? == other.file.?;
+ }
+ return false;
+ }
};
/// When allocating, the ideal_capacity is calculated by
@@ -234,7 +268,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff {
},
.ptr_width = ptr_width,
.page_size = page_size,
- .data_directories = comptime mem.zeroes([N_DATA_DIRS]coff.ImageDataDirectory),
+ .data_directories = comptime mem.zeroes([coff.IMAGE_NUMBEROF_DIRECTORY_ENTRIES]coff.ImageDataDirectory),
};
const use_llvm = build_options.have_llvm and options.use_llvm;
@@ -269,10 +303,24 @@ pub fn deinit(self: *Coff) void {
self.locals.deinit(gpa);
self.globals.deinit(gpa);
+
+ {
+ var it = self.resolver.keyIterator();
+ while (it.next()) |key_ptr| {
+ gpa.free(key_ptr.*);
+ }
+ self.resolver.deinit(gpa);
+ }
+
+ self.unresolved.deinit(gpa);
self.locals_free_list.deinit(gpa);
self.strtab.deinit(gpa);
self.got_entries.deinit(gpa);
self.got_entries_free_list.deinit(gpa);
+ self.got_entries_table.deinit(gpa);
+ self.imports.deinit(gpa);
+ self.imports_free_list.deinit(gpa);
+ self.imports_table.deinit(gpa);
self.decls.deinit(gpa);
self.atom_by_index_table.deinit(gpa);
@@ -305,145 +353,76 @@ fn populateMissingMetadata(self: *Coff) !void {
assert(self.llvm_object == null);
const gpa = self.base.allocator;
+ try self.strtab.buffer.ensureUnusedCapacity(gpa, @sizeOf(u32));
+ self.strtab.buffer.appendNTimesAssumeCapacity(0, @sizeOf(u32));
+
+ // Index 0 is always a null symbol.
+ try self.locals.append(gpa, .{
+ .name = [_]u8{0} ** 8,
+ .value = 0,
+ .section_number = .UNDEFINED,
+ .@"type" = .{ .base_type = .NULL, .complex_type = .NULL },
+ .storage_class = .NULL,
+ .number_of_aux_symbols = 0,
+ });
+
if (self.text_section_index == null) {
- self.text_section_index = @intCast(u16, self.sections.slice().len);
const file_size = @intCast(u32, self.base.options.program_code_size_hint);
- const off = self.findFreeSpace(file_size, self.page_size); // TODO we are over-aligning in file; we should track both in file and in memory pointers
- log.debug("found .text free space 0x{x} to 0x{x}", .{ off, off + file_size });
- var header = coff.SectionHeader{
- .name = undefined,
- .virtual_size = file_size,
- .virtual_address = off,
- .size_of_raw_data = file_size,
- .pointer_to_raw_data = off,
- .pointer_to_relocations = 0,
- .pointer_to_linenumbers = 0,
- .number_of_relocations = 0,
- .number_of_linenumbers = 0,
- .flags = .{
- .CNT_CODE = 1,
- .MEM_EXECUTE = 1,
- .MEM_READ = 1,
- },
- };
- try self.setSectionName(&header, ".text");
- try self.sections.append(gpa, .{ .header = header });
+ self.text_section_index = try self.allocateSection(".text", file_size, .{
+ .CNT_CODE = 1,
+ .MEM_EXECUTE = 1,
+ .MEM_READ = 1,
+ });
}
if (self.got_section_index == null) {
- self.got_section_index = @intCast(u16, self.sections.slice().len);
const file_size = @intCast(u32, self.base.options.symbol_count_hint) * self.ptr_width.abiSize();
- const off = self.findFreeSpace(file_size, self.page_size);
- log.debug("found .got free space 0x{x} to 0x{x}", .{ off, off + file_size });
- var header = coff.SectionHeader{
- .name = undefined,
- .virtual_size = file_size,
- .virtual_address = off,
- .size_of_raw_data = file_size,
- .pointer_to_raw_data = off,
- .pointer_to_relocations = 0,
- .pointer_to_linenumbers = 0,
- .number_of_relocations = 0,
- .number_of_linenumbers = 0,
- .flags = .{
- .CNT_INITIALIZED_DATA = 1,
- .MEM_READ = 1,
- },
- };
- try self.setSectionName(&header, ".got");
- try self.sections.append(gpa, .{ .header = header });
+ self.got_section_index = try self.allocateSection(".got", file_size, .{
+ .CNT_INITIALIZED_DATA = 1,
+ .MEM_READ = 1,
+ });
}
if (self.rdata_section_index == null) {
- self.rdata_section_index = @intCast(u16, self.sections.slice().len);
- const file_size: u32 = 1024;
- const off = self.findFreeSpace(file_size, self.page_size);
- log.debug("found .rdata free space 0x{x} to 0x{x}", .{ off, off + file_size });
- var header = coff.SectionHeader{
- .name = undefined,
- .virtual_size = file_size,
- .virtual_address = off,
- .size_of_raw_data = file_size,
- .pointer_to_raw_data = off,
- .pointer_to_relocations = 0,
- .pointer_to_linenumbers = 0,
- .number_of_relocations = 0,
- .number_of_linenumbers = 0,
- .flags = .{
- .CNT_INITIALIZED_DATA = 1,
- .MEM_READ = 1,
- },
- };
- try self.setSectionName(&header, ".rdata");
- try self.sections.append(gpa, .{ .header = header });
+ const file_size: u32 = self.page_size;
+ self.rdata_section_index = try self.allocateSection(".rdata", file_size, .{
+ .CNT_INITIALIZED_DATA = 1,
+ .MEM_READ = 1,
+ });
}
if (self.data_section_index == null) {
- self.data_section_index = @intCast(u16, self.sections.slice().len);
- const file_size: u32 = 1024;
- const off = self.findFreeSpace(file_size, self.page_size);
- log.debug("found .data free space 0x{x} to 0x{x}", .{ off, off + file_size });
- var header = coff.SectionHeader{
- .name = undefined,
- .virtual_size = file_size,
- .virtual_address = off,
- .size_of_raw_data = file_size,
- .pointer_to_raw_data = off,
- .pointer_to_relocations = 0,
- .pointer_to_linenumbers = 0,
- .number_of_relocations = 0,
- .number_of_linenumbers = 0,
- .flags = .{
- .CNT_INITIALIZED_DATA = 1,
- .MEM_READ = 1,
- .MEM_WRITE = 1,
- },
- };
- try self.setSectionName(&header, ".data");
- try self.sections.append(gpa, .{ .header = header });
+ const file_size: u32 = self.page_size;
+ self.data_section_index = try self.allocateSection(".data", file_size, .{
+ .CNT_INITIALIZED_DATA = 1,
+ .MEM_READ = 1,
+ .MEM_WRITE = 1,
+ });
+ }
+
+ if (self.idata_section_index == null) {
+ const file_size = @intCast(u32, self.base.options.symbol_count_hint) * self.ptr_width.abiSize();
+ self.idata_section_index = try self.allocateSection(".idata", file_size, .{
+ .CNT_INITIALIZED_DATA = 1,
+ .MEM_READ = 1,
+ });
}
if (self.reloc_section_index == null) {
- self.reloc_section_index = @intCast(u16, self.sections.slice().len);
const file_size = @intCast(u32, self.base.options.symbol_count_hint) * @sizeOf(coff.BaseRelocation);
- const off = self.findFreeSpace(file_size, self.page_size);
- log.debug("found .reloc free space 0x{x} to 0x{x}", .{ off, off + file_size });
- var header = coff.SectionHeader{
- .name = undefined,
- .virtual_size = file_size,
- .virtual_address = off,
- .size_of_raw_data = file_size,
- .pointer_to_raw_data = off,
- .pointer_to_relocations = 0,
- .pointer_to_linenumbers = 0,
- .number_of_relocations = 0,
- .number_of_linenumbers = 0,
- .flags = .{
- .CNT_INITIALIZED_DATA = 1,
- .MEM_PURGEABLE = 1,
- .MEM_READ = 1,
- },
- };
- try self.setSectionName(&header, ".reloc");
- try self.sections.append(gpa, .{ .header = header });
+ self.reloc_section_index = try self.allocateSection(".reloc", file_size, .{
+ .CNT_INITIALIZED_DATA = 1,
+ .MEM_DISCARDABLE = 1,
+ .MEM_READ = 1,
+ });
}
if (self.strtab_offset == null) {
- try self.strtab.buffer.append(gpa, 0);
- self.strtab_offset = self.findFreeSpace(@intCast(u32, self.strtab.len()), 1);
- log.debug("found strtab free space 0x{x} to 0x{x}", .{ self.strtab_offset.?, self.strtab_offset.? + self.strtab.len() });
+ const file_size = @intCast(u32, self.strtab.len());
+ self.strtab_offset = self.findFreeSpace(file_size, @alignOf(u32)); // 4bytes aligned seems like a good idea here
+ log.debug("found strtab free space 0x{x} to 0x{x}", .{ self.strtab_offset.?, self.strtab_offset.? + file_size });
}
- // Index 0 is always a null symbol.
- try self.locals.append(gpa, .{
- .name = [_]u8{0} ** 8,
- .value = 0,
- .section_number = @intToEnum(coff.SectionNumber, 0),
- .@"type" = .{ .base_type = .NULL, .complex_type = .NULL },
- .storage_class = .NULL,
- .number_of_aux_symbols = 0,
- });
-
{
// We need to find out what the max file offset is according to section headers.
// Otherwise, we may end up with an COFF binary with file size not matching the final section's
@@ -459,6 +438,72 @@ fn populateMissingMetadata(self: *Coff) !void {
}
}
+fn allocateSection(self: *Coff, name: []const u8, size: u32, flags: coff.SectionHeaderFlags) !u16 {
+ const index = @intCast(u16, self.sections.slice().len);
+ const off = self.findFreeSpace(size, default_file_alignment);
+ // Memory is always allocated in sequence
+ // TODO: investigate if we can allocate .text last; this way it would never need to grow in memory!
+ const vaddr = blk: {
+ if (index == 0) break :blk self.page_size;
+ const prev_header = self.sections.items(.header)[index - 1];
+ break :blk mem.alignForwardGeneric(u32, prev_header.virtual_address + prev_header.virtual_size, self.page_size);
+ };
+ // We commit more memory than needed upfront so that we don't have to reallocate too soon.
+ const memsz = mem.alignForwardGeneric(u32, size, self.page_size) * 100;
+ log.debug("found {s} free space 0x{x} to 0x{x} (0x{x} - 0x{x})", .{
+ name,
+ off,
+ off + size,
+ vaddr,
+ vaddr + size,
+ });
+ var header = coff.SectionHeader{
+ .name = undefined,
+ .virtual_size = memsz,
+ .virtual_address = vaddr,
+ .size_of_raw_data = size,
+ .pointer_to_raw_data = off,
+ .pointer_to_relocations = 0,
+ .pointer_to_linenumbers = 0,
+ .number_of_relocations = 0,
+ .number_of_linenumbers = 0,
+ .flags = flags,
+ };
+ try self.setSectionName(&header, name);
+ try self.sections.append(self.base.allocator, .{ .header = header });
+ return index;
+}
+
+fn growSectionVM(self: *Coff, sect_id: u32, needed_size: u32) !void {
+ const header = &self.sections.items(.header)[sect_id];
+ const increased_size = padToIdeal(needed_size);
+ const old_aligned_end = header.virtual_address + mem.alignForwardGeneric(u32, header.virtual_size, self.page_size);
+ const new_aligned_end = header.virtual_address + mem.alignForwardGeneric(u32, increased_size, self.page_size);
+ const diff = new_aligned_end - old_aligned_end;
+ log.debug("growing {s} in virtual memory by {x}", .{ self.getSectionName(header), diff });
+
+ // TODO: enforce order by increasing VM addresses in self.sections container.
+ // This is required by the loader anyhow as far as I can tell.
+ for (self.sections.items(.header)[sect_id + 1 ..]) |*next_header, next_sect_id| {
+ const maybe_last_atom = &self.sections.items(.last_atom)[sect_id + 1 + next_sect_id];
+ next_header.virtual_address += diff;
+
+ if (maybe_last_atom.*) |last_atom| {
+ var atom = last_atom;
+ while (true) {
+ const sym = atom.getSymbolPtr(self);
+ sym.value += diff;
+
+ if (atom.prev) |prev| {
+ atom = prev;
+ } else break;
+ }
+ }
+ }
+
+ header.virtual_size = increased_size;
+}
+
pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void {
if (self.llvm_object) |_| return;
const decl = self.base.options.module.?.declPtr(decl_index);
@@ -542,16 +587,33 @@ fn allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32) !u
const sect_capacity = self.allocatedSize(header.pointer_to_raw_data);
const needed_size: u32 = (vaddr + new_atom_size) - header.virtual_address;
if (needed_size > sect_capacity) {
- @panic("TODO move section");
+ const new_offset = self.findFreeSpace(needed_size, default_file_alignment);
+ const current_size = if (maybe_last_atom.*) |last_atom| blk: {
+ const sym = last_atom.getSymbol(self);
+ break :blk (sym.value + last_atom.size) - header.virtual_address;
+ } else 0;
+ log.debug("moving {s} from 0x{x} to 0x{x}", .{ self.getSectionName(header), header.pointer_to_raw_data, new_offset });
+ const amt = try self.base.file.?.copyRangeAll(
+ header.pointer_to_raw_data,
+ self.base.file.?,
+ new_offset,
+ current_size,
+ );
+ if (amt != current_size) return error.InputOutput;
+ header.pointer_to_raw_data = new_offset;
+ }
+
+ const sect_vm_capacity = self.allocatedVirtualSize(header.virtual_address);
+ if (needed_size > sect_vm_capacity) {
+ try self.growSectionVM(sect_id, needed_size);
+ self.markRelocsDirtyByAddress(header.virtual_address + needed_size);
}
+
+ header.virtual_size = @maximum(header.virtual_size, needed_size);
+ header.size_of_raw_data = needed_size;
maybe_last_atom.* = atom;
- // header.virtual_size = needed_size;
- // header.size_of_raw_data = mem.alignForwardGeneric(u32, needed_size, default_file_alignment);
}
- // if (header.getAlignment().? < alignment) {
- // header.setAlignment(alignment);
- // }
atom.size = new_atom_size;
atom.alignment = alignment;
@@ -596,7 +658,7 @@ fn allocateSymbol(self: *Coff) !u32 {
self.locals.items[index] = .{
.name = [_]u8{0} ** 8,
.value = 0,
- .section_number = @intToEnum(coff.SectionNumber, 0),
+ .section_number = .UNDEFINED,
.@"type" = .{ .base_type = .NULL, .complex_type = .NULL },
.storage_class = .NULL,
.number_of_aux_symbols = 0,
@@ -605,24 +667,71 @@ fn allocateSymbol(self: *Coff) !u32 {
return index;
}
+fn allocateGlobal(self: *Coff) !u32 {
+ const gpa = self.base.allocator;
+ try self.globals.ensureUnusedCapacity(gpa, 1);
+
+ const index = blk: {
+ if (self.globals_free_list.popOrNull()) |index| {
+ log.debug(" (reusing global index {d})", .{index});
+ break :blk index;
+ } else {
+ log.debug(" (allocating global index {d})", .{self.globals.items.len});
+ const index = @intCast(u32, self.globals.items.len);
+ _ = self.globals.addOneAssumeCapacity();
+ break :blk index;
+ }
+ };
+
+ self.globals.items[index] = .{
+ .sym_index = 0,
+ .file = null,
+ };
+
+ return index;
+}
+
pub fn allocateGotEntry(self: *Coff, target: SymbolWithLoc) !u32 {
const gpa = self.base.allocator;
try self.got_entries.ensureUnusedCapacity(gpa, 1);
+
const index: u32 = blk: {
if (self.got_entries_free_list.popOrNull()) |index| {
log.debug(" (reusing GOT entry index {d})", .{index});
- if (self.got_entries.getIndex(target)) |existing| {
- assert(existing == index);
- }
break :blk index;
} else {
- log.debug(" (allocating GOT entry at index {d})", .{self.got_entries.keys().len});
- const index = @intCast(u32, self.got_entries.keys().len);
- self.got_entries.putAssumeCapacityNoClobber(target, 0);
+ log.debug(" (allocating GOT entry at index {d})", .{self.got_entries.items.len});
+ const index = @intCast(u32, self.got_entries.items.len);
+ _ = self.got_entries.addOneAssumeCapacity();
break :blk index;
}
};
- self.got_entries.keys()[index] = target;
+
+ self.got_entries.items[index] = .{ .target = target, .sym_index = 0 };
+ try self.got_entries_table.putNoClobber(gpa, target, index);
+
+ return index;
+}
+
+pub fn allocateImportEntry(self: *Coff, target: SymbolWithLoc) !u32 {
+ const gpa = self.base.allocator;
+ try self.imports.ensureUnusedCapacity(gpa, 1);
+
+ const index: u32 = blk: {
+ if (self.imports_free_list.popOrNull()) |index| {
+ log.debug(" (reusing import entry index {d})", .{index});
+ break :blk index;
+ } else {
+ log.debug(" (allocating import entry at index {d})", .{self.imports.items.len});
+ const index = @intCast(u32, self.imports.items.len);
+ _ = self.imports.addOneAssumeCapacity();
+ break :blk index;
+ }
+ };
+
+ self.imports.items[index] = .{ .target = target, .sym_index = 0 };
+ try self.imports_table.putNoClobber(gpa, target, index);
+
return index;
}
@@ -637,7 +746,6 @@ fn createGotAtom(self: *Coff, target: SymbolWithLoc) !*Atom {
try self.managed_atoms.append(gpa, atom);
try self.atom_by_index_table.putNoClobber(gpa, atom.sym_index, atom);
- self.got_entries.getPtr(target).?.* = atom.sym_index;
const sym = atom.getSymbolPtr(self);
sym.section_number = @intToEnum(coff.SectionNumber, self.got_section_index.? + 1);
@@ -652,7 +760,6 @@ fn createGotAtom(self: *Coff, target: SymbolWithLoc) !*Atom {
.addend = 0,
.pcrel = false,
.length = 3,
- .prev_vaddr = sym.value,
});
const target_sym = self.getSymbol(target);
@@ -666,6 +773,27 @@ fn createGotAtom(self: *Coff, target: SymbolWithLoc) !*Atom {
return atom;
}
+fn createImportAtom(self: *Coff) !*Atom {
+ const gpa = self.base.allocator;
+ const atom = try gpa.create(Atom);
+ errdefer gpa.destroy(atom);
+ atom.* = Atom.empty;
+ atom.sym_index = try self.allocateSymbol();
+ atom.size = @sizeOf(u64);
+ atom.alignment = @alignOf(u64);
+
+ try self.managed_atoms.append(gpa, atom);
+ try self.atom_by_index_table.putNoClobber(gpa, atom.sym_index, atom);
+
+ const sym = atom.getSymbolPtr(self);
+ sym.section_number = @intToEnum(coff.SectionNumber, self.idata_section_index.? + 1);
+ sym.value = try self.allocateAtom(atom, atom.size, atom.alignment);
+
+ log.debug("allocated import atom at 0x{x}", .{sym.value});
+
+ return atom;
+}
+
fn growAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32) !u32 {
const sym = atom.getSymbol(self);
const align_ok = mem.alignBackwardGeneric(u32, sym.value, alignment) == sym.value;
@@ -686,12 +814,12 @@ fn writeAtom(self: *Coff, atom: *Atom, code: []const u8) !void {
const sym = atom.getSymbol(self);
const section = self.sections.get(@enumToInt(sym.section_number) - 1);
const file_offset = section.header.pointer_to_raw_data + sym.value - section.header.virtual_address;
- log.debug("writing atom for symbol {s} at file offset 0x{x}", .{ atom.getName(self), file_offset });
+ log.debug("writing atom for symbol {s} at file offset 0x{x} to 0x{x}", .{ atom.getName(self), file_offset, file_offset + code.len });
try self.base.file.?.pwriteAll(code, file_offset);
try self.resolveRelocs(atom);
}
-fn writeGotAtom(self: *Coff, atom: *Atom) !void {
+fn writePtrWidthAtom(self: *Coff, atom: *Atom) !void {
switch (self.ptr_width) {
.p32 => {
var buffer: [@sizeOf(u32)]u8 = [_]u8{0} ** @sizeOf(u32);
@@ -704,6 +832,29 @@ fn writeGotAtom(self: *Coff, atom: *Atom) !void {
}
}
+fn markRelocsDirtyByTarget(self: *Coff, target: SymbolWithLoc) void {
+ // TODO: reverse-lookup might come in handy here
+ var it = self.relocs.valueIterator();
+ while (it.next()) |relocs| {
+ for (relocs.items) |*reloc| {
+ if (!reloc.target.eql(target)) continue;
+ reloc.dirty = true;
+ }
+ }
+}
+
+fn markRelocsDirtyByAddress(self: *Coff, addr: u32) void {
+ var it = self.relocs.valueIterator();
+ while (it.next()) |relocs| {
+ for (relocs.items) |*reloc| {
+ const target_atom = reloc.getTargetAtom(self) orelse continue;
+ const target_sym = target_atom.getSymbol(self);
+ if (target_sym.value < addr) continue;
+ reloc.dirty = true;
+ }
+ }
+}
+
fn resolveRelocs(self: *Coff, atom: *Atom) !void {
const relocs = self.relocs.get(atom) orelse return;
const source_sym = atom.getSymbol(self);
@@ -713,29 +864,28 @@ fn resolveRelocs(self: *Coff, atom: *Atom) !void {
log.debug("relocating '{s}'", .{atom.getName(self)});
for (relocs.items) |*reloc| {
- const target_vaddr = switch (reloc.@"type") {
- .got => blk: {
- const got_atom = self.getGotAtomForSymbol(reloc.target) orelse continue;
- break :blk got_atom.getSymbol(self).value;
- },
- .direct => self.getSymbol(reloc.target).value,
- };
- const target_vaddr_with_addend = target_vaddr + reloc.addend;
+ if (!reloc.dirty) continue;
- if (target_vaddr_with_addend == reloc.prev_vaddr) continue;
+ const target_atom = reloc.getTargetAtom(self) orelse continue;
+ const target_vaddr = target_atom.getSymbol(self).value;
+ const target_vaddr_with_addend = target_vaddr + reloc.addend;
- log.debug(" ({x}: [() => 0x{x} ({s})) ({s})", .{
- reloc.offset,
+ log.debug(" ({x}: [() => 0x{x} ({s})) ({s}) (in file at 0x{x})", .{
+ source_sym.value + reloc.offset,
target_vaddr_with_addend,
self.getSymbolName(reloc.target),
@tagName(reloc.@"type"),
+ file_offset + reloc.offset,
});
+ reloc.dirty = false;
+
if (reloc.pcrel) {
const source_vaddr = source_sym.value + reloc.offset;
- const disp = target_vaddr_with_addend - source_vaddr - 4;
- try self.base.file.?.pwriteAll(mem.asBytes(&@intCast(u32, disp)), file_offset + reloc.offset);
- return;
+ const disp =
+ @intCast(i32, target_vaddr_with_addend) - @intCast(i32, source_vaddr) - 4;
+ try self.base.file.?.pwriteAll(mem.asBytes(&disp), file_offset + reloc.offset);
+ continue;
}
switch (self.ptr_width) {
@@ -755,14 +905,15 @@ fn resolveRelocs(self: *Coff, atom: *Atom) !void {
else => unreachable,
},
}
-
- reloc.prev_vaddr = target_vaddr_with_addend;
}
}
fn freeAtom(self: *Coff, atom: *Atom) void {
log.debug("freeAtom {*}", .{atom});
+ // Remove any relocs and base relocs associated with this Atom
+ self.freeRelocationsForAtom(atom);
+
const sym = atom.getSymbol(self);
const sect_id = @enumToInt(sym.section_number) - 1;
const free_list = &self.sections.items(.free_list)[sect_id];
@@ -825,11 +976,14 @@ pub fn updateFunc(self: *Coff, module: *Module, func: *Module.Fn, air: Air, live
const tracy = trace(@src());
defer tracy.end();
+ const decl_index = func.owner_decl;
+ const decl = module.declPtr(decl_index);
+ self.freeUnnamedConsts(decl_index);
+ self.freeRelocationsForAtom(&decl.link.coff);
+
var code_buffer = std.ArrayList(u8).init(self.base.allocator);
defer code_buffer.deinit();
- const decl_index = func.owner_decl;
- const decl = module.declPtr(decl_index);
const res = try codegen.generateFunction(
&self.base,
decl.srcLoc(),
@@ -856,10 +1010,67 @@ pub fn updateFunc(self: *Coff, module: *Module, func: *Module.Fn, air: Air, live
}
pub fn lowerUnnamedConst(self: *Coff, tv: TypedValue, decl_index: Module.Decl.Index) !u32 {
- _ = self;
- _ = tv;
- _ = decl_index;
- @panic("TODO lowerUnnamedConst");
+ const gpa = self.base.allocator;
+ var code_buffer = std.ArrayList(u8).init(gpa);
+ defer code_buffer.deinit();
+
+ const mod = self.base.options.module.?;
+ const decl = mod.declPtr(decl_index);
+
+ const gop = try self.unnamed_const_atoms.getOrPut(gpa, decl_index);
+ if (!gop.found_existing) {
+ gop.value_ptr.* = .{};
+ }
+ const unnamed_consts = gop.value_ptr;
+
+ const atom = try gpa.create(Atom);
+ errdefer gpa.destroy(atom);
+ atom.* = Atom.empty;
+
+ atom.sym_index = try self.allocateSymbol();
+ const sym = atom.getSymbolPtr(self);
+ const sym_name = blk: {
+ const decl_name = try decl.getFullyQualifiedName(mod);
+ defer gpa.free(decl_name);
+
+ const index = unnamed_consts.items.len;
+ break :blk try std.fmt.allocPrint(gpa, "__unnamed_{s}_{d}", .{ decl_name, index });
+ };
+ defer gpa.free(sym_name);
+ try self.setSymbolName(sym, sym_name);
+ sym.section_number = @intToEnum(coff.SectionNumber, self.rdata_section_index.? + 1);
+
+ try self.managed_atoms.append(gpa, atom);
+ try self.atom_by_index_table.putNoClobber(gpa, atom.sym_index, atom);
+
+ const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), tv, &code_buffer, .none, .{
+ .parent_atom_index = atom.sym_index,
+ });
+ const code = switch (res) {
+ .externally_managed => |x| x,
+ .appended => code_buffer.items,
+ .fail => |em| {
+ decl.analysis = .codegen_failure;
+ try mod.failed_decls.put(mod.gpa, decl_index, em);
+ log.err("{s}", .{em.msg});
+ return error.AnalysisFail;
+ },
+ };
+
+ const required_alignment = tv.ty.abiAlignment(self.base.options.target);
+ atom.alignment = required_alignment;
+ atom.size = @intCast(u32, code.len);
+ sym.value = try self.allocateAtom(atom, atom.size, atom.alignment);
+ errdefer self.freeAtom(atom);
+
+ try unnamed_consts.append(gpa, atom);
+
+ log.debug("allocated atom for {s} at 0x{x}", .{ sym_name, sym.value });
+ log.debug(" (required alignment 0x{x})", .{required_alignment});
+
+ try self.writeAtom(atom, code);
+
+ return atom.sym_index;
}
pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) !void {
@@ -884,6 +1095,8 @@ pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) !
}
}
+ self.freeRelocationsForAtom(&decl.link.coff);
+
var code_buffer = std.ArrayList(u8).init(self.base.allocator);
defer code_buffer.deinit();
@@ -892,7 +1105,7 @@ pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) !
.ty = decl.ty,
.val = decl_val,
}, &code_buffer, .none, .{
- .parent_atom_index = 0,
+ .parent_atom_index = decl.link.coff.sym_index,
});
const code = switch (res) {
.externally_managed => |x| x,
@@ -970,8 +1183,10 @@ fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8,
if (vaddr != sym.value) {
sym.value = vaddr;
log.debug(" (updating GOT entry)", .{});
- const got_atom = self.getGotAtomForSymbol(.{ .sym_index = atom.sym_index, .file = null }).?;
- try self.writeGotAtom(got_atom);
+ const got_target = SymbolWithLoc{ .sym_index = atom.sym_index, .file = null };
+ const got_atom = self.getGotAtomForSymbol(got_target).?;
+ self.markRelocsDirtyByTarget(got_target);
+ try self.writePtrWidthAtom(got_atom);
}
} else if (code_len < atom.size) {
self.shrinkAtom(atom, code_len);
@@ -990,14 +1205,35 @@ fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8,
sym.value = vaddr;
const got_target = SymbolWithLoc{ .sym_index = atom.sym_index, .file = null };
- _ = try self.allocateGotEntry(got_target);
+ const got_index = try self.allocateGotEntry(got_target);
const got_atom = try self.createGotAtom(got_target);
- try self.writeGotAtom(got_atom);
+ self.got_entries.items[got_index].sym_index = got_atom.sym_index;
+ try self.writePtrWidthAtom(got_atom);
}
+ self.markRelocsDirtyByTarget(atom.getSymbolWithLoc());
try self.writeAtom(atom, code);
}
+fn freeRelocationsForAtom(self: *Coff, atom: *Atom) void {
+ _ = self.relocs.remove(atom);
+ _ = self.base_relocs.remove(atom);
+}
+
+fn freeUnnamedConsts(self: *Coff, decl_index: Module.Decl.Index) void {
+ const gpa = self.base.allocator;
+ const unnamed_consts = self.unnamed_const_atoms.getPtr(decl_index) orelse return;
+ for (unnamed_consts.items) |atom| {
+ self.freeAtom(atom);
+ self.locals_free_list.append(gpa, atom.sym_index) catch {};
+ self.locals.items[atom.sym_index].section_number = .UNDEFINED;
+ _ = self.atom_by_index_table.remove(atom.sym_index);
+ log.debug(" adding local symbol index {d} to free list", .{atom.sym_index});
+ atom.sym_index = 0;
+ }
+ unnamed_consts.clearAndFree(gpa);
+}
+
pub fn freeDecl(self: *Coff, decl_index: Module.Decl.Index) void {
if (build_options.have_llvm) {
if (self.llvm_object) |llvm_object| return llvm_object.freeDecl(decl_index);
@@ -1011,6 +1247,7 @@ pub fn freeDecl(self: *Coff, decl_index: Module.Decl.Index) void {
const kv = self.decls.fetchRemove(decl_index);
if (kv.?.value) |_| {
self.freeAtom(&decl.link.coff);
+ self.freeUnnamedConsts(decl_index);
}
// Appending to free lists is allowed to fail because the free lists are heuristics based anyway.
@@ -1021,14 +1258,20 @@ pub fn freeDecl(self: *Coff, decl_index: Module.Decl.Index) void {
// Try freeing GOT atom if this decl had one
const got_target = SymbolWithLoc{ .sym_index = sym_index, .file = null };
- if (self.got_entries.getIndex(got_target)) |got_index| {
+ if (self.got_entries_table.get(got_target)) |got_index| {
self.got_entries_free_list.append(gpa, @intCast(u32, got_index)) catch {};
- self.got_entries.values()[got_index] = 0;
+ self.got_entries.items[got_index] = .{
+ .target = .{ .sym_index = 0, .file = null },
+ .sym_index = 0,
+ };
+ _ = self.got_entries_table.remove(got_target);
+
log.debug(" adding GOT index {d} to free list (target local@{d})", .{ got_index, sym_index });
}
- self.locals.items[sym_index].section_number = @intToEnum(coff.SectionNumber, 0);
+ self.locals.items[sym_index].section_number = .UNDEFINED;
_ = self.atom_by_index_table.remove(sym_index);
+ log.debug(" adding local symbol index {d} to free list", .{sym_index});
decl.link.coff.sym_index = 0;
}
}
@@ -1154,44 +1397,49 @@ pub fn deleteExport(self: *Coff, exp: Export) void {
const sym = self.getSymbolPtr(sym_loc);
const sym_name = self.getSymbolName(sym_loc);
log.debug("deleting export '{s}'", .{sym_name});
- assert(sym.storage_class == .EXTERNAL);
+ assert(sym.storage_class == .EXTERNAL and sym.section_number != .UNDEFINED);
sym.* = .{
.name = [_]u8{0} ** 8,
.value = 0,
- .section_number = @intToEnum(coff.SectionNumber, 0),
+ .section_number = .UNDEFINED,
.@"type" = .{ .base_type = .NULL, .complex_type = .NULL },
.storage_class = .NULL,
.number_of_aux_symbols = 0,
};
self.locals_free_list.append(gpa, sym_index) catch {};
- if (self.globals.get(sym_name)) |global| blk: {
- if (global.sym_index != sym_index) break :blk;
- if (global.file != null) break :blk;
- const kv = self.globals.fetchSwapRemove(sym_name);
- gpa.free(kv.?.key);
+ if (self.resolver.fetchRemove(sym_name)) |entry| {
+ defer gpa.free(entry.key);
+ self.globals_free_list.append(gpa, entry.value) catch {};
+ self.globals.items[entry.value] = .{
+ .sym_index = 0,
+ .file = null,
+ };
}
}
fn resolveGlobalSymbol(self: *Coff, current: SymbolWithLoc) !void {
const gpa = self.base.allocator;
const sym = self.getSymbol(current);
- _ = sym;
const sym_name = self.getSymbolName(current);
- const name = try gpa.dupe(u8, sym_name);
- const global_index = @intCast(u32, self.globals.values().len);
- _ = global_index;
- const gop = try self.globals.getOrPut(gpa, name);
- defer if (gop.found_existing) gpa.free(name);
-
- if (!gop.found_existing) {
- gop.value_ptr.* = current;
- // TODO undef + tentative
+ const global_index = self.resolver.get(sym_name) orelse {
+ const name = try gpa.dupe(u8, sym_name);
+ const global_index = try self.allocateGlobal();
+ self.globals.items[global_index] = current;
+ try self.resolver.putNoClobber(gpa, name, global_index);
+ if (sym.section_number == .UNDEFINED) {
+ try self.unresolved.putNoClobber(gpa, global_index, false);
+ }
return;
- }
+ };
log.debug("TODO finish resolveGlobalSymbols implementation", .{});
+
+ if (sym.section_number == .UNDEFINED) return;
+
+ _ = self.unresolved.swapRemove(global_index);
+ self.globals.items[global_index] = current;
}
pub fn flush(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void {
@@ -1227,6 +1475,17 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
sub_prog_node.activate();
defer sub_prog_node.end();
+ while (self.unresolved.popOrNull()) |entry| {
+ assert(entry.value); // We only expect imports generated by the incremental linker for now.
+ const global = self.globals.items[entry.key];
+ if (self.imports_table.contains(global)) continue;
+
+ const import_index = try self.allocateImportEntry(global);
+ const import_atom = try self.createImportAtom();
+ self.imports.items[import_index].sym_index = import_atom.sym_index;
+ try self.writePtrWidthAtom(import_atom);
+ }
+
if (build_options.enable_logging) {
self.logSymtab();
}
@@ -1237,6 +1496,7 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
try self.resolveRelocs(atom.*);
}
}
+ try self.writeImportTable();
try self.writeBaseRelocations();
if (self.getEntryPoint()) |entry_sym_loc| {
@@ -1262,10 +1522,47 @@ pub fn getDeclVAddr(
decl_index: Module.Decl.Index,
reloc_info: link.File.RelocInfo,
) !u64 {
- _ = self;
- _ = decl_index;
- _ = reloc_info;
- @panic("TODO getDeclVAddr");
+ const mod = self.base.options.module.?;
+ const decl = mod.declPtr(decl_index);
+
+ assert(self.llvm_object == null);
+ assert(decl.link.coff.sym_index != 0);
+
+ const atom = self.atom_by_index_table.get(reloc_info.parent_atom_index).?;
+ const target = SymbolWithLoc{ .sym_index = decl.link.coff.sym_index, .file = null };
+ try atom.addRelocation(self, .{
+ .@"type" = .direct,
+ .target = target,
+ .offset = @intCast(u32, reloc_info.offset),
+ .addend = reloc_info.addend,
+ .pcrel = false,
+ .length = 3,
+ });
+ try atom.addBaseRelocation(self, @intCast(u32, reloc_info.offset));
+
+ return 0;
+}
+
+pub fn getGlobalSymbol(self: *Coff, name: []const u8) !u32 {
+ if (self.resolver.get(name)) |global_index| {
+ return self.globals.items[global_index].sym_index;
+ }
+
+ const gpa = self.base.allocator;
+ const sym_index = try self.allocateSymbol();
+ const global_index = try self.allocateGlobal();
+ const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = null };
+ self.globals.items[global_index] = sym_loc;
+
+ const sym_name = try gpa.dupe(u8, name);
+ const sym = self.getSymbolPtr(sym_loc);
+ try self.setSymbolName(sym, sym_name);
+ sym.storage_class = .EXTERNAL;
+
+ try self.resolver.putNoClobber(gpa, sym_name, global_index);
+ try self.unresolved.putNoClobber(gpa, global_index, true);
+
+ return sym_index;
}
pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !void {
@@ -1342,7 +1639,25 @@ fn writeBaseRelocations(self: *Coff) !void {
const header = &self.sections.items(.header)[self.reloc_section_index.?];
const sect_capacity = self.allocatedSize(header.pointer_to_raw_data);
const needed_size = @intCast(u32, buffer.items.len);
- assert(needed_size < sect_capacity); // TODO expand .reloc section
+ if (needed_size > sect_capacity) {
+ const new_offset = self.findFreeSpace(needed_size, default_file_alignment);
+ log.debug("writing {s} at 0x{x} to 0x{x} (0x{x} - 0x{x})", .{
+ self.getSectionName(header),
+ header.pointer_to_raw_data,
+ header.pointer_to_raw_data + needed_size,
+ new_offset,
+ new_offset + needed_size,
+ });
+ header.pointer_to_raw_data = new_offset;
+
+ const sect_vm_capacity = self.allocatedVirtualSize(header.virtual_address);
+ if (needed_size > sect_vm_capacity) {
+ // TODO: we want to enforce .reloc after every alloc section.
+ try self.growSectionVM(self.reloc_section_index.?, needed_size);
+ }
+ }
+ header.virtual_size = @maximum(header.virtual_size, needed_size);
+ header.size_of_raw_data = needed_size;
try self.base.file.?.pwriteAll(buffer.items, header.pointer_to_raw_data);
@@ -1352,17 +1667,111 @@ fn writeBaseRelocations(self: *Coff) !void {
};
}
+fn writeImportTable(self: *Coff) !void {
+ if (self.idata_section_index == null) return;
+
+ const gpa = self.base.allocator;
+
+ const section = self.sections.get(self.idata_section_index.?);
+ const last_atom = section.last_atom orelse return;
+
+ const iat_rva = section.header.virtual_address;
+ const iat_size = last_atom.getSymbol(self).value + last_atom.size * 2 - iat_rva; // account for sentinel zero pointer
+
+ const dll_name = "KERNEL32.dll";
+
+ var import_dir_entry = coff.ImportDirectoryEntry{
+ .import_lookup_table_rva = @sizeOf(coff.ImportDirectoryEntry) * 2,
+ .time_date_stamp = 0,
+ .forwarder_chain = 0,
+ .name_rva = 0,
+ .import_address_table_rva = iat_rva,
+ };
+
+ // TODO: we currently assume there's only one (implicit) DLL - ntdll
+ var lookup_table = std.ArrayList(coff.ImportLookupEntry64.ByName).init(gpa);
+ defer lookup_table.deinit();
+
+ var names_table = std.ArrayList(u8).init(gpa);
+ defer names_table.deinit();
+
+ // TODO: check if import is still valid
+ for (self.imports.items) |entry| {
+ const target_name = self.getSymbolName(entry.target);
+ const start = names_table.items.len;
+ mem.writeIntLittle(u16, try names_table.addManyAsArray(2), 0); // TODO: currently, hint is set to 0 as we haven't yet parsed any DLL
+ try names_table.appendSlice(target_name);
+ try names_table.append(0);
+ const end = names_table.items.len;
+ if (!mem.isAlignedGeneric(usize, end - start, @sizeOf(u16))) {
+ try names_table.append(0);
+ }
+ try lookup_table.append(.{ .name_table_rva = @intCast(u31, start) });
+ }
+ try lookup_table.append(.{ .name_table_rva = 0 }); // the sentinel
+
+ const dir_entry_size = @sizeOf(coff.ImportDirectoryEntry) + lookup_table.items.len * @sizeOf(coff.ImportLookupEntry64.ByName) + names_table.items.len + dll_name.len + 1;
+ const needed_size = iat_size + dir_entry_size + @sizeOf(coff.ImportDirectoryEntry);
+ const sect_capacity = self.allocatedSize(section.header.pointer_to_raw_data);
+ assert(needed_size < sect_capacity); // TODO: implement expanding .idata section
+
+ // Fixup offsets
+ const base_rva = iat_rva + iat_size;
+ import_dir_entry.import_lookup_table_rva += base_rva;
+ import_dir_entry.name_rva = @intCast(u32, base_rva + dir_entry_size + @sizeOf(coff.ImportDirectoryEntry) - dll_name.len - 1);
+
+ for (lookup_table.items[0 .. lookup_table.items.len - 1]) |*lk| {
+ lk.name_table_rva += @intCast(u31, base_rva + @sizeOf(coff.ImportDirectoryEntry) * 2 + lookup_table.items.len * @sizeOf(coff.ImportLookupEntry64.ByName));
+ }
+
+ var buffer = std.ArrayList(u8).init(gpa);
+ defer buffer.deinit();
+ try buffer.ensureTotalCapacity(dir_entry_size + @sizeOf(coff.ImportDirectoryEntry));
+ buffer.appendSliceAssumeCapacity(mem.asBytes(&import_dir_entry));
+ buffer.appendNTimesAssumeCapacity(0, @sizeOf(coff.ImportDirectoryEntry)); // the sentinel; TODO: I think doing all of the above on bytes directly might be cleaner
+ buffer.appendSliceAssumeCapacity(mem.sliceAsBytes(lookup_table.items));
+ buffer.appendSliceAssumeCapacity(names_table.items);
+ buffer.appendSliceAssumeCapacity(dll_name);
+ buffer.appendAssumeCapacity(0);
+
+ try self.base.file.?.pwriteAll(buffer.items, section.header.pointer_to_raw_data + iat_size);
+ // Override the IAT atoms
+ // TODO: we should rewrite only dirtied atoms, but that's for way later
+ try self.base.file.?.pwriteAll(mem.sliceAsBytes(lookup_table.items), section.header.pointer_to_raw_data);
+
+ self.data_directories[@enumToInt(coff.DirectoryEntry.IMPORT)] = .{
+ .virtual_address = iat_rva + iat_size,
+ .size = @intCast(u32, @sizeOf(coff.ImportDirectoryEntry) * 2),
+ };
+
+ self.data_directories[@enumToInt(coff.DirectoryEntry.IAT)] = .{
+ .virtual_address = iat_rva,
+ .size = iat_size,
+ };
+}
+
fn writeStrtab(self: *Coff) !void {
+ if (self.strtab_offset == null) return;
+
const allocated_size = self.allocatedSize(self.strtab_offset.?);
const needed_size = @intCast(u32, self.strtab.len());
if (needed_size > allocated_size) {
self.strtab_offset = null;
- self.strtab_offset = @intCast(u32, self.findFreeSpace(needed_size, 1));
+ self.strtab_offset = @intCast(u32, self.findFreeSpace(needed_size, @alignOf(u32)));
}
log.debug("writing strtab from 0x{x} to 0x{x}", .{ self.strtab_offset.?, self.strtab_offset.? + needed_size });
- try self.base.file.?.pwriteAll(self.strtab.buffer.items, self.strtab_offset.?);
+
+ var buffer = std.ArrayList(u8).init(self.base.allocator);
+ defer buffer.deinit();
+ try buffer.ensureTotalCapacityPrecise(needed_size);
+ buffer.appendSliceAssumeCapacity(self.strtab.items());
+ // Here, we do a trick in that we do not commit the size of the strtab to strtab buffer, instead
+ // we write the length of the strtab to a temporary buffer that goes to file.
+ mem.writeIntLittle(u32, buffer.items[0..4], @intCast(u32, self.strtab.len()));
+
+ try self.base.file.?.pwriteAll(buffer.items, self.strtab_offset.?);
}
fn writeSectionHeaders(self: *Coff) !void {
@@ -1527,14 +1936,15 @@ pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) {
}
fn detectAllocCollision(self: *Coff, start: u32, size: u32) ?u32 {
- const headers_size = self.getSizeOfHeaders();
+ const headers_size = @maximum(self.getSizeOfHeaders(), self.page_size);
if (start < headers_size)
return headers_size;
- const end = start + size;
+ const end = start + padToIdeal(size);
if (self.strtab_offset) |off| {
- const increased_size = @intCast(u32, self.strtab.len());
+ const tight_size = @intCast(u32, self.strtab.len());
+ const increased_size = padToIdeal(tight_size);
const test_end = off + increased_size;
if (end > off and start < test_end) {
return test_end;
@@ -1542,7 +1952,8 @@ fn detectAllocCollision(self: *Coff, start: u32, size: u32) ?u32 {
}
for (self.sections.items(.header)) |header| {
- const increased_size = header.size_of_raw_data;
+ const tight_size = header.size_of_raw_data;
+ const increased_size = padToIdeal(tight_size);
const test_end = header.pointer_to_raw_data + increased_size;
if (end > header.pointer_to_raw_data and start < test_end) {
return test_end;
@@ -1552,7 +1963,7 @@ fn detectAllocCollision(self: *Coff, start: u32, size: u32) ?u32 {
return null;
}
-pub fn allocatedSize(self: *Coff, start: u32) u32 {
+fn allocatedSize(self: *Coff, start: u32) u32 {
if (start == 0)
return 0;
var min_pos: u32 = std.math.maxInt(u32);
@@ -1566,7 +1977,7 @@ pub fn allocatedSize(self: *Coff, start: u32) u32 {
return min_pos - start;
}
-pub fn findFreeSpace(self: *Coff, object_size: u32, min_alignment: u32) u32 {
+fn findFreeSpace(self: *Coff, object_size: u32, min_alignment: u32) u32 {
var start: u32 = 0;
while (self.detectAllocCollision(start, object_size)) |item_end| {
start = mem.alignForwardGeneric(u32, item_end, min_alignment);
@@ -1574,6 +1985,17 @@ pub fn findFreeSpace(self: *Coff, object_size: u32, min_alignment: u32) u32 {
return start;
}
+fn allocatedVirtualSize(self: *Coff, start: u32) u32 {
+ if (start == 0)
+ return 0;
+ var min_pos: u32 = std.math.maxInt(u32);
+ for (self.sections.items(.header)) |header| {
+ if (header.virtual_address <= start) continue;
+ if (header.virtual_address < min_pos) min_pos = header.virtual_address;
+ }
+ return min_pos - start;
+}
+
inline fn getSizeOfHeaders(self: Coff) u32 {
const msdos_hdr_size = msdos_stub.len + 4;
return @intCast(u32, msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize() +
@@ -1614,23 +2036,24 @@ inline fn getSizeOfImage(self: Coff) u32 {
/// Returns symbol location corresponding to the set entrypoint (if any).
pub fn getEntryPoint(self: Coff) ?SymbolWithLoc {
- const entry_name = self.base.options.entry orelse "_start"; // TODO this is incomplete
- return self.globals.get(entry_name);
+ const entry_name = self.base.options.entry orelse "wWinMainCRTStartup"; // TODO this is incomplete
+ const global_index = self.resolver.get(entry_name) orelse return null;
+ return self.globals.items[global_index];
}
-/// Returns pointer-to-symbol described by `sym_with_loc` descriptor.
+/// Returns pointer-to-symbol described by `sym_loc` descriptor.
pub fn getSymbolPtr(self: *Coff, sym_loc: SymbolWithLoc) *coff.Symbol {
assert(sym_loc.file == null); // TODO linking object files
return &self.locals.items[sym_loc.sym_index];
}
-/// Returns symbol described by `sym_with_loc` descriptor.
+/// Returns symbol described by `sym_loc` descriptor.
pub fn getSymbol(self: *const Coff, sym_loc: SymbolWithLoc) *const coff.Symbol {
assert(sym_loc.file == null); // TODO linking object files
return &self.locals.items[sym_loc.sym_index];
}
-/// Returns name of the symbol described by `sym_with_loc` descriptor.
+/// Returns name of the symbol described by `sym_loc` descriptor.
pub fn getSymbolName(self: *const Coff, sym_loc: SymbolWithLoc) []const u8 {
assert(sym_loc.file == null); // TODO linking object files
const sym = self.getSymbol(sym_loc);
@@ -1638,18 +2061,27 @@ pub fn getSymbolName(self: *const Coff, sym_loc: SymbolWithLoc) []const u8 {
return self.strtab.get(offset).?;
}
-/// Returns atom if there is an atom referenced by the symbol described by `sym_with_loc` descriptor.
+/// Returns atom if there is an atom referenced by the symbol described by `sym_loc` descriptor.
/// Returns null on failure.
pub fn getAtomForSymbol(self: *Coff, sym_loc: SymbolWithLoc) ?*Atom {
assert(sym_loc.file == null); // TODO linking with object files
return self.atom_by_index_table.get(sym_loc.sym_index);
}
-/// Returns GOT atom that references `sym_with_loc` if one exists.
+/// Returns GOT atom that references `sym_loc` if one exists.
/// Returns null otherwise.
pub fn getGotAtomForSymbol(self: *Coff, sym_loc: SymbolWithLoc) ?*Atom {
- const got_index = self.got_entries.get(sym_loc) orelse return null;
- return self.atom_by_index_table.get(got_index);
+ const got_index = self.got_entries_table.get(sym_loc) orelse return null;
+ const got_entry = self.got_entries.items[got_index];
+ return self.getAtomForSymbol(.{ .sym_index = got_entry.sym_index, .file = null });
+}
+
+/// Returns import atom that references `sym_loc` if one exists.
+/// Returns null otherwise.
+pub fn getImportAtomForSymbol(self: *Coff, sym_loc: SymbolWithLoc) ?*Atom {
+ const imports_index = self.imports_table.get(sym_loc) orelse return null;
+ const imports_entry = self.imports.items[imports_index];
+ return self.getAtomForSymbol(.{ .sym_index = imports_entry.sym_index, .file = null });
}
fn setSectionName(self: *Coff, header: *coff.SectionHeader, name: []const u8) !void {
@@ -1663,6 +2095,14 @@ fn setSectionName(self: *Coff, header: *coff.SectionHeader, name: []const u8) !v
mem.set(u8, header.name[name_offset.len..], 0);
}
+fn getSectionName(self: *const Coff, header: *const coff.SectionHeader) []const u8 {
+ if (header.getName()) |name| {
+ return name;
+ }
+ const offset = header.getNameOffset().?;
+ return self.strtab.get(offset).?;
+}
+
fn setSymbolName(self: *Coff, symbol: *coff.Symbol, name: []const u8) !void {
if (name.len <= 8) {
mem.copy(u8, &symbol.name, name);
@@ -1725,29 +2165,42 @@ fn logSymtab(self: *Coff) void {
}
log.debug("globals table:", .{});
- for (self.globals.keys()) |name, id| {
- const value = self.globals.values()[id];
- log.debug(" {s} => %{d} in object({?d})", .{ name, value.sym_index, value.file });
+ for (self.globals.items) |sym_loc| {
+ const sym_name = self.getSymbolName(sym_loc);
+ log.debug(" {s} => %{d} in object({?d})", .{ sym_name, sym_loc.sym_index, sym_loc.file });
}
log.debug("GOT entries:", .{});
- for (self.got_entries.keys()) |target, i| {
- const got_sym = self.getSymbol(.{ .sym_index = self.got_entries.values()[i], .file = null });
- const target_sym = self.getSymbol(target);
+ for (self.got_entries.items) |entry, i| {
+ const got_sym = self.getSymbol(.{ .sym_index = entry.sym_index, .file = null });
+ const target_sym = self.getSymbol(entry.target);
if (target_sym.section_number == .UNDEFINED) {
log.debug(" {d}@{x} => import('{s}')", .{
i,
got_sym.value,
- self.getSymbolName(target),
+ self.getSymbolName(entry.target),
});
} else {
log.debug(" {d}@{x} => local(%{d}) in object({?d}) {s}", .{
i,
got_sym.value,
- target.sym_index,
- target.file,
+ entry.target.sym_index,
+ entry.target.file,
logSymAttributes(target_sym, &buf),
});
}
}
}
+
+fn logSections(self: *Coff) void {
+ log.debug("sections:", .{});
+ for (self.sections.items(.header)) |*header| {
+ log.debug(" {s}: VM({x}, {x}) FILE({x}, {x})", .{
+ self.getSectionName(header),
+ header.virtual_address,
+ header.virtual_address + header.virtual_size,
+ header.pointer_to_raw_data,
+ header.pointer_to_raw_data + header.size_of_raw_data,
+ });
+ }
+}
diff --git a/src/link/Coff/Atom.zig b/src/link/Coff/Atom.zig
index a7608d9a34..ffd8fe45e6 100644
--- a/src/link/Coff/Atom.zig
+++ b/src/link/Coff/Atom.zig
@@ -4,8 +4,6 @@ const std = @import("std");
const coff = std.coff;
const log = std.log.scoped(.link);
-const Allocator = std.mem.Allocator;
-
const Coff = @import("../Coff.zig");
const Reloc = Coff.Reloc;
const SymbolWithLoc = Coff.SymbolWithLoc;
@@ -41,11 +39,6 @@ pub const empty = Atom{
.next = null,
};
-pub fn deinit(self: *Atom, gpa: Allocator) void {
- _ = self;
- _ = gpa;
-}
-
/// Returns symbol referencing this atom.
pub fn getSymbol(self: Atom, coff_file: *const Coff) *const coff.Symbol {
return coff_file.getSymbol(.{
@@ -118,3 +111,13 @@ pub fn addBaseRelocation(self: *Atom, coff_file: *Coff, offset: u32) !void {
}
try gop.value_ptr.append(gpa, offset);
}
+
+pub fn addBinding(self: *Atom, coff_file: *Coff, target: SymbolWithLoc) !void {
+ const gpa = coff_file.base.allocator;
+ log.debug(" (adding binding to target %{d} in %{d})", .{ target.sym_index, self.sym_index });
+ const gop = try coff_file.bindings.getOrPut(gpa, self);
+ if (!gop.found_existing) {
+ gop.value_ptr.* = .{};
+ }
+ try gop.value_ptr.append(gpa, target);
+}
diff --git a/src/link/MachO.zig b/src/link/MachO.zig
index af25441066..429bf64eb2 100644
--- a/src/link/MachO.zig
+++ b/src/link/MachO.zig
@@ -793,11 +793,13 @@ fn linkOneShot(self: *MachO, comp: *Compilation, prog_node: *std.Progress.Node)
}
} else {
const sub_path = self.base.options.emit.?.sub_path;
- self.base.file = try directory.handle.createFile(sub_path, .{
- .truncate = true,
- .read = true,
- .mode = link.determineMode(self.base.options),
- });
+ if (self.base.file == null) {
+ self.base.file = try directory.handle.createFile(sub_path, .{
+ .truncate = true,
+ .read = true,
+ .mode = link.determineMode(self.base.options),
+ });
+ }
// Index 0 is always a null symbol.
try self.locals.append(gpa, .{
.n_strx = 0,
@@ -1155,6 +1157,29 @@ fn linkOneShot(self: *MachO, comp: *Compilation, prog_node: *std.Progress.Node)
var ncmds: u32 = 0;
try self.writeLinkeditSegmentData(&ncmds, lc_writer);
+
+ // If the last section of __DATA segment is zerofill section, we need to ensure
+ // that the free space between the end of the last non-zerofill section of __DATA
+ // segment and the beginning of __LINKEDIT segment is zerofilled as the loader will
+ // copy-paste this space into memory for quicker zerofill operation.
+ if (self.data_segment_cmd_index) |data_seg_id| blk: {
+ var physical_zerofill_start: u64 = 0;
+ const section_indexes = self.getSectionIndexes(data_seg_id);
+ for (self.sections.items(.header)[section_indexes.start..section_indexes.end]) |header| {
+ if (header.isZerofill() and header.size > 0) break;
+ physical_zerofill_start = header.offset + header.size;
+ } else break :blk;
+ const linkedit = self.segments.items[self.linkedit_segment_cmd_index.?];
+ const physical_zerofill_size = math.cast(usize, linkedit.fileoff - physical_zerofill_start) orelse
+ return error.Overflow;
+ if (physical_zerofill_size > 0) {
+ var padding = try self.base.allocator.alloc(u8, physical_zerofill_size);
+ defer self.base.allocator.free(padding);
+ mem.set(u8, padding, 0);
+ try self.base.file.?.pwriteAll(padding, physical_zerofill_start);
+ }
+ }
+
try writeDylinkerLC(&ncmds, lc_writer);
try self.writeMainLC(&ncmds, lc_writer);
try self.writeDylibIdLC(&ncmds, lc_writer);
@@ -1435,7 +1460,6 @@ fn parseArchive(self: *MachO, path: []const u8, force_load: bool) !bool {
if (force_load) {
defer archive.deinit(gpa);
- defer file.close();
// Get all offsets from the ToC
var offsets = std.AutoArrayHashMap(u32, void).init(gpa);
defer offsets.deinit();
@@ -3086,15 +3110,6 @@ pub fn deinit(self: *MachO) void {
self.atom_by_index_table.deinit(gpa);
}
-pub fn closeFiles(self: MachO) void {
- for (self.archives.items) |archive| {
- archive.file.close();
- }
- if (self.d_sym) |ds| {
- ds.file.close();
- }
-}
-
fn freeAtom(self: *MachO, atom: *Atom, sect_id: u8, owns_atom: bool) void {
log.debug("freeAtom {*}", .{atom});
if (!owns_atom) {
@@ -5698,8 +5713,10 @@ fn writeHeader(self: *MachO, ncmds: u32, sizeofcmds: u32) !void {
else => unreachable,
}
- if (self.getSectionByName("__DATA", "__thread_vars")) |_| {
- header.flags |= macho.MH_HAS_TLV_DESCRIPTORS;
+ if (self.getSectionByName("__DATA", "__thread_vars")) |sect_id| {
+ if (self.sections.items(.header)[sect_id].size > 0) {
+ header.flags |= macho.MH_HAS_TLV_DESCRIPTORS;
+ }
}
header.ncmds = ncmds;
diff --git a/src/link/MachO/Archive.zig b/src/link/MachO/Archive.zig
index 054f75fff3..59a956534e 100644
--- a/src/link/MachO/Archive.zig
+++ b/src/link/MachO/Archive.zig
@@ -88,6 +88,7 @@ const ar_hdr = extern struct {
};
pub fn deinit(self: *Archive, allocator: Allocator) void {
+ self.file.close();
for (self.toc.keys()) |*key| {
allocator.free(key.*);
}
diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig
index a7dc6391c2..ffff0fe5f8 100644
--- a/src/link/MachO/DebugSymbols.zig
+++ b/src/link/MachO/DebugSymbols.zig
@@ -306,6 +306,7 @@ pub fn flushModule(self: *DebugSymbols, allocator: Allocator, options: link.Opti
}
pub fn deinit(self: *DebugSymbols, allocator: Allocator) void {
+ self.file.close();
self.segments.deinit(allocator);
self.sections.deinit(allocator);
self.dwarf.deinit();
diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig
index 0c5f0e810f..babed64755 100644
--- a/src/link/Wasm.zig
+++ b/src/link/Wasm.zig
@@ -695,12 +695,10 @@ pub fn deinit(self: *Wasm) void {
gpa.free(segment_info.name);
}
for (self.objects.items) |*object| {
- object.file.?.close();
object.deinit(gpa);
}
for (self.archives.items) |*archive| {
- archive.file.close();
archive.deinit(gpa);
}
@@ -3218,14 +3216,26 @@ fn writeVecSectionHeader(file: fs.File, offset: u64, section: wasm.Section, size
buf[0] = @enumToInt(section);
leb.writeUnsignedFixed(5, buf[1..6], size);
leb.writeUnsignedFixed(5, buf[6..], items);
- try file.pwriteAll(&buf, offset);
+
+ if (builtin.target.os.tag == .windows) {
+ // https://github.com/ziglang/zig/issues/12783
+ const curr_pos = try file.getPos();
+ try file.pwriteAll(&buf, offset);
+ try file.seekTo(curr_pos);
+ } else try file.pwriteAll(&buf, offset);
}
fn writeCustomSectionHeader(file: fs.File, offset: u64, size: u32) !void {
var buf: [1 + 5]u8 = undefined;
buf[0] = 0; // 0 = 'custom' section
leb.writeUnsignedFixed(5, buf[1..6], size);
- try file.pwriteAll(&buf, offset);
+
+ if (builtin.target.os.tag == .windows) {
+ // https://github.com/ziglang/zig/issues/12783
+ const curr_pos = try file.getPos();
+ try file.pwriteAll(&buf, offset);
+ try file.seekTo(curr_pos);
+ } else try file.pwriteAll(&buf, offset);
}
fn emitLinkSection(self: *Wasm, file: fs.File, arena: Allocator, symbol_table: *std.AutoArrayHashMap(SymbolLoc, u32)) !void {
diff --git a/src/link/Wasm/Archive.zig b/src/link/Wasm/Archive.zig
index c80d26d17d..b1cce15b1d 100644
--- a/src/link/Wasm/Archive.zig
+++ b/src/link/Wasm/Archive.zig
@@ -95,6 +95,7 @@ const ar_hdr = extern struct {
};
pub fn deinit(archive: *Archive, allocator: Allocator) void {
+ archive.file.close();
for (archive.toc.keys()) |*key| {
allocator.free(key.*);
}
diff --git a/src/link/Wasm/Object.zig b/src/link/Wasm/Object.zig
index 42c3b8a1a0..808608fac5 100644
--- a/src/link/Wasm/Object.zig
+++ b/src/link/Wasm/Object.zig
@@ -154,6 +154,9 @@ pub fn create(gpa: Allocator, file: std.fs.File, name: []const u8, maybe_max_siz
/// Frees all memory of `Object` at once. The given `Allocator` must be
/// the same allocator that was used when `init` was called.
pub fn deinit(self: *Object, gpa: Allocator) void {
+ if (self.file) |file| {
+ file.close();
+ }
for (self.func_types) |func_ty| {
gpa.free(func_ty.params);
gpa.free(func_ty.returns);
diff --git a/src/link/strtab.zig b/src/link/strtab.zig
index 8e314f189f..abb58defef 100644
--- a/src/link/strtab.zig
+++ b/src/link/strtab.zig
@@ -110,6 +110,10 @@ pub fn StringTable(comptime log_scope: @Type(.EnumLiteral)) type {
return self.get(off) orelse unreachable;
}
+ pub fn items(self: Self) []const u8 {
+ return self.buffer.items;
+ }
+
pub fn len(self: Self) usize {
return self.buffer.items.len;
}