mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-04-27 19:09:47 +03:00
Merge pull request #12677 from ziglang/coff-linker
coff: initial rewrite of the COFF/PE linker
This commit is contained in:
@@ -753,6 +753,9 @@ set(ZIG_STAGE2_SOURCES
|
||||
"${CMAKE_SOURCE_DIR}/src/link.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/C.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/Coff.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/Coff/Atom.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/Coff/Object.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/Coff/lld.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/Elf.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/MachO.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/MachO/Archive.zig"
|
||||
|
||||
@@ -372,6 +372,15 @@ pub const SectionHeader = extern struct {
|
||||
return std.math.powi(u16, 2, self.flags.ALIGN - 1) catch unreachable;
|
||||
}
|
||||
|
||||
pub fn setAlignment(self: *SectionHeader, new_alignment: u16) void {
|
||||
assert(new_alignment > 0 and new_alignment <= 8192);
|
||||
self.flags.ALIGN = std.math.log2(new_alignment);
|
||||
}
|
||||
|
||||
pub fn isCode(self: SectionHeader) bool {
|
||||
return self.flags.CNT_CODE == 0b1;
|
||||
}
|
||||
|
||||
pub fn isComdat(self: SectionHeader) bool {
|
||||
return self.flags.LNK_COMDAT == 0b1;
|
||||
}
|
||||
@@ -847,6 +856,21 @@ pub const MachineType = enum(u16) {
|
||||
/// MIPS little-endian WCE v2
|
||||
WCEMIPSV2 = 0x169,
|
||||
|
||||
pub fn fromTargetCpuArch(arch: std.Target.Cpu.Arch) MachineType {
|
||||
return switch (arch) {
|
||||
.arm => .ARM,
|
||||
.powerpc => .POWERPC,
|
||||
.riscv32 => .RISCV32,
|
||||
.thumb => .Thumb,
|
||||
.i386 => .I386,
|
||||
.aarch64 => .ARM64,
|
||||
.riscv64 => .RISCV64,
|
||||
.x86_64 => .X64,
|
||||
// there's cases we don't (yet) handle
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toTargetCpuArch(machine_type: MachineType) ?std.Target.Cpu.Arch {
|
||||
return switch (machine_type) {
|
||||
.ARM => .arm,
|
||||
|
||||
@@ -36,8 +36,6 @@ comptime {
|
||||
if (@typeInfo(@TypeOf(root.main)).Fn.calling_convention != .C) {
|
||||
@export(main2, .{ .name = "main" });
|
||||
}
|
||||
} else if (builtin.os.tag == .windows) {
|
||||
@export(wWinMainCRTStartup2, .{ .name = "wWinMainCRTStartup" });
|
||||
} else if (builtin.os.tag == .wasi and @hasDecl(root, "main")) {
|
||||
@export(wasiMain2, .{ .name = "_start" });
|
||||
} else {
|
||||
|
||||
@@ -1127,7 +1127,6 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
|
||||
link_eh_frame_hdr or
|
||||
options.link_emit_relocs or
|
||||
options.output_mode == .Lib or
|
||||
options.image_base_override != null or
|
||||
options.linker_script != null or options.version_script != null or
|
||||
options.emit_implib != null or
|
||||
build_id)
|
||||
|
||||
+7
-4
@@ -5259,9 +5259,9 @@ pub fn clearDecl(
|
||||
// TODO instead of a union, put this memory trailing Decl objects,
|
||||
// and allow it to be variably sized.
|
||||
decl.link = switch (mod.comp.bin_file.tag) {
|
||||
.coff => .{ .coff = link.File.Coff.TextBlock.empty },
|
||||
.coff => .{ .coff = link.File.Coff.Atom.empty },
|
||||
.elf => .{ .elf = link.File.Elf.TextBlock.empty },
|
||||
.macho => .{ .macho = link.File.MachO.TextBlock.empty },
|
||||
.macho => .{ .macho = link.File.MachO.Atom.empty },
|
||||
.plan9 => .{ .plan9 = link.File.Plan9.DeclBlock.empty },
|
||||
.c => .{ .c = {} },
|
||||
.wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty },
|
||||
@@ -5391,6 +5391,9 @@ fn deleteDeclExports(mod: *Module, decl_index: Decl.Index) void {
|
||||
if (mod.comp.bin_file.cast(link.File.Wasm)) |wasm| {
|
||||
wasm.deleteExport(exp.link.wasm);
|
||||
}
|
||||
if (mod.comp.bin_file.cast(link.File.Coff)) |coff| {
|
||||
coff.deleteExport(exp.link.coff);
|
||||
}
|
||||
if (mod.failed_exports.fetchSwapRemove(exp)) |failed_kv| {
|
||||
failed_kv.value.destroy(mod.gpa);
|
||||
}
|
||||
@@ -5680,9 +5683,9 @@ pub fn allocateNewDecl(
|
||||
.zir_decl_index = 0,
|
||||
.src_scope = src_scope,
|
||||
.link = switch (mod.comp.bin_file.tag) {
|
||||
.coff => .{ .coff = link.File.Coff.TextBlock.empty },
|
||||
.coff => .{ .coff = link.File.Coff.Atom.empty },
|
||||
.elf => .{ .elf = link.File.Elf.TextBlock.empty },
|
||||
.macho => .{ .macho = link.File.MachO.TextBlock.empty },
|
||||
.macho => .{ .macho = link.File.MachO.Atom.empty },
|
||||
.plan9 => .{ .plan9 = link.File.Plan9.DeclBlock.empty },
|
||||
.c => .{ .c = {} },
|
||||
.wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty },
|
||||
|
||||
+1
-1
@@ -5076,7 +5076,7 @@ pub fn analyzeExport(
|
||||
},
|
||||
.src = src,
|
||||
.link = switch (mod.comp.bin_file.tag) {
|
||||
.coff => .{ .coff = {} },
|
||||
.coff => .{ .coff = .{} },
|
||||
.elf => .{ .elf = .{} },
|
||||
.macho => .{ .macho = .{} },
|
||||
.plan9 => .{ .plan9 = null },
|
||||
|
||||
@@ -3466,19 +3466,16 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
|
||||
// on linking.
|
||||
const mod = self.bin_file.options.module.?;
|
||||
if (self.air.value(callee)) |func_value| {
|
||||
if (self.bin_file.tag == link.File.Elf.base_tag or self.bin_file.tag == link.File.Coff.base_tag) {
|
||||
if (self.bin_file.cast(link.File.Elf)) |elf_file| {
|
||||
if (func_value.castTag(.function)) |func_payload| {
|
||||
const func = func_payload.data;
|
||||
const ptr_bits = self.target.cpu.arch.ptrBitWidth();
|
||||
const ptr_bytes: u64 = @divExact(ptr_bits, 8);
|
||||
const fn_owner_decl = mod.declPtr(func.owner_decl);
|
||||
const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: {
|
||||
const got_addr = blk: {
|
||||
const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?];
|
||||
break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes);
|
||||
} else if (self.bin_file.cast(link.File.Coff)) |coff_file|
|
||||
coff_file.offset_table_virtual_address + fn_owner_decl.link.coff.offset_table_index * ptr_bytes
|
||||
else
|
||||
unreachable;
|
||||
};
|
||||
|
||||
try self.genSetReg(Type.initTag(.usize), .x30, .{ .memory = got_addr });
|
||||
|
||||
@@ -3546,6 +3543,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
|
||||
} else {
|
||||
return self.fail("TODO implement calling bitcasted functions", .{});
|
||||
}
|
||||
} else if (self.bin_file.cast(link.File.Coff)) |_| {
|
||||
return self.fail("TODO implement calling in COFF for {}", .{self.target.cpu.arch});
|
||||
} else unreachable;
|
||||
} else {
|
||||
assert(ty.zigTypeTag() == .Pointer);
|
||||
@@ -5109,9 +5108,8 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne
|
||||
// the linker has enough info to perform relocations.
|
||||
assert(decl.link.macho.sym_index != 0);
|
||||
return MCValue{ .got_load = decl.link.macho.sym_index };
|
||||
} else if (self.bin_file.cast(link.File.Coff)) |coff_file| {
|
||||
const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes;
|
||||
return MCValue{ .memory = got_addr };
|
||||
} else if (self.bin_file.cast(link.File.Coff)) |_| {
|
||||
return self.fail("TODO codegen COFF const Decl pointer", .{});
|
||||
} else if (self.bin_file.cast(link.File.Plan9)) |p9| {
|
||||
try p9.seeDecl(decl_index);
|
||||
const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes;
|
||||
|
||||
@@ -3698,7 +3698,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
|
||||
// Due to incremental compilation, how function calls are generated depends
|
||||
// on linking.
|
||||
switch (self.bin_file.tag) {
|
||||
.elf, .coff => {
|
||||
.elf => {
|
||||
if (self.air.value(callee)) |func_value| {
|
||||
if (func_value.castTag(.function)) |func_payload| {
|
||||
const func = func_payload.data;
|
||||
@@ -3709,11 +3709,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
|
||||
const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: {
|
||||
const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?];
|
||||
break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes);
|
||||
} else if (self.bin_file.cast(link.File.Coff)) |coff_file|
|
||||
coff_file.offset_table_virtual_address + fn_owner_decl.link.coff.offset_table_index * ptr_bytes
|
||||
else
|
||||
unreachable;
|
||||
|
||||
} else unreachable;
|
||||
try self.genSetReg(Type.initTag(.usize), .lr, .{ .memory = got_addr });
|
||||
} else if (func_value.castTag(.extern_fn)) |_| {
|
||||
return self.fail("TODO implement calling extern functions", .{});
|
||||
@@ -3751,6 +3747,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
|
||||
}
|
||||
},
|
||||
.macho => unreachable, // unsupported architecture for MachO
|
||||
.coff => return self.fail("TODO implement call in COFF for {}", .{self.target.cpu.arch}),
|
||||
.plan9 => return self.fail("TODO implement call on plan9 for {}", .{self.target.cpu.arch}),
|
||||
else => unreachable,
|
||||
}
|
||||
@@ -5548,9 +5545,8 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne
|
||||
return MCValue{ .memory = got_addr };
|
||||
} else if (self.bin_file.cast(link.File.MachO)) |_| {
|
||||
unreachable; // unsupported architecture for MachO
|
||||
} else if (self.bin_file.cast(link.File.Coff)) |coff_file| {
|
||||
const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes;
|
||||
return MCValue{ .memory = got_addr };
|
||||
} else if (self.bin_file.cast(link.File.Coff)) |_| {
|
||||
return self.fail("TODO codegen COFF const Decl pointer", .{});
|
||||
} else if (self.bin_file.cast(link.File.Plan9)) |p9| {
|
||||
try p9.seeDecl(decl_index);
|
||||
const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes;
|
||||
|
||||
@@ -1718,7 +1718,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
|
||||
|
||||
// Due to incremental compilation, how function calls are generated depends
|
||||
// on linking.
|
||||
if (self.bin_file.tag == link.File.Elf.base_tag or self.bin_file.tag == link.File.Coff.base_tag) {
|
||||
if (self.bin_file.cast(link.File.Elf)) |elf_file| {
|
||||
for (info.args) |mc_arg, arg_i| {
|
||||
const arg = args[arg_i];
|
||||
const arg_ty = self.air.typeOf(arg);
|
||||
@@ -1752,13 +1752,10 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
|
||||
const ptr_bytes: u64 = @divExact(ptr_bits, 8);
|
||||
const mod = self.bin_file.options.module.?;
|
||||
const fn_owner_decl = mod.declPtr(func.owner_decl);
|
||||
const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: {
|
||||
const got_addr = blk: {
|
||||
const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?];
|
||||
break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes);
|
||||
} else if (self.bin_file.cast(link.File.Coff)) |coff_file|
|
||||
coff_file.offset_table_virtual_address + fn_owner_decl.link.coff.offset_table_index * ptr_bytes
|
||||
else
|
||||
unreachable;
|
||||
};
|
||||
|
||||
try self.genSetReg(Type.initTag(.usize), .ra, .{ .memory = got_addr });
|
||||
_ = try self.addInst(.{
|
||||
@@ -1777,6 +1774,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
|
||||
} else {
|
||||
return self.fail("TODO implement calling runtime known function pointer", .{});
|
||||
}
|
||||
} else if (self.bin_file.cast(link.File.Coff)) |_| {
|
||||
return self.fail("TODO implement calling in COFF for {}", .{self.target.cpu.arch});
|
||||
} else if (self.bin_file.cast(link.File.MachO)) |_| {
|
||||
unreachable; // unsupported architecture for MachO
|
||||
} else if (self.bin_file.cast(link.File.Plan9)) |_| {
|
||||
@@ -2591,9 +2590,8 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne
|
||||
// TODO I'm hacking my way through here by repurposing .memory for storing
|
||||
// index to the GOT target symbol index.
|
||||
return MCValue{ .memory = decl.link.macho.sym_index };
|
||||
} else if (self.bin_file.cast(link.File.Coff)) |coff_file| {
|
||||
const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes;
|
||||
return MCValue{ .memory = got_addr };
|
||||
} else if (self.bin_file.cast(link.File.Coff)) |_| {
|
||||
return self.fail("TODO codegen COFF const Decl pointer", .{});
|
||||
} else if (self.bin_file.cast(link.File.Plan9)) |p9| {
|
||||
try p9.seeDecl(decl_index);
|
||||
const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes;
|
||||
|
||||
+50
-18
@@ -2657,22 +2657,26 @@ fn loadMemPtrIntoRegister(self: *Self, reg: Register, ptr_ty: Type, ptr: MCValue
|
||||
.direct_load,
|
||||
=> |sym_index| {
|
||||
const abi_size = @intCast(u32, ptr_ty.abiSize(self.target.*));
|
||||
const mod = self.bin_file.options.module.?;
|
||||
const fn_owner_decl = mod.declPtr(self.mod_fn.owner_decl);
|
||||
const atom_index = if (self.bin_file.tag == link.File.MachO.base_tag)
|
||||
fn_owner_decl.link.macho.sym_index
|
||||
else
|
||||
fn_owner_decl.link.coff.sym_index;
|
||||
const flags: u2 = switch (ptr) {
|
||||
.got_load => 0b00,
|
||||
.direct_load => 0b01,
|
||||
else => unreachable,
|
||||
};
|
||||
const mod = self.bin_file.options.module.?;
|
||||
const fn_owner_decl = mod.declPtr(self.mod_fn.owner_decl);
|
||||
_ = try self.addInst(.{
|
||||
.tag = .lea_pie,
|
||||
.tag = .lea_pic,
|
||||
.ops = Mir.Inst.Ops.encode(.{
|
||||
.reg1 = registerAlias(reg, abi_size),
|
||||
.flags = flags,
|
||||
}),
|
||||
.data = .{
|
||||
.relocation = .{
|
||||
.atom_index = fn_owner_decl.link.macho.sym_index,
|
||||
.atom_index = atom_index,
|
||||
.sym_index = sym_index,
|
||||
},
|
||||
},
|
||||
@@ -3961,20 +3965,17 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
|
||||
// Due to incremental compilation, how function calls are generated depends
|
||||
// on linking.
|
||||
const mod = self.bin_file.options.module.?;
|
||||
if (self.bin_file.tag == link.File.Elf.base_tag or self.bin_file.tag == link.File.Coff.base_tag) {
|
||||
if (self.bin_file.cast(link.File.Elf)) |elf_file| {
|
||||
if (self.air.value(callee)) |func_value| {
|
||||
if (func_value.castTag(.function)) |func_payload| {
|
||||
const func = func_payload.data;
|
||||
const ptr_bits = self.target.cpu.arch.ptrBitWidth();
|
||||
const ptr_bytes: u64 = @divExact(ptr_bits, 8);
|
||||
const fn_owner_decl = mod.declPtr(func.owner_decl);
|
||||
const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: {
|
||||
const got_addr = blk: {
|
||||
const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?];
|
||||
break :blk @intCast(u32, got.p_vaddr + fn_owner_decl.link.elf.offset_table_index * ptr_bytes);
|
||||
} else if (self.bin_file.cast(link.File.Coff)) |coff_file|
|
||||
@intCast(u32, coff_file.offset_table_virtual_address + fn_owner_decl.link.coff.offset_table_index * ptr_bytes)
|
||||
else
|
||||
unreachable;
|
||||
};
|
||||
_ = try self.addInst(.{
|
||||
.tag = .call,
|
||||
.ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }),
|
||||
@@ -3998,14 +3999,47 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
|
||||
.data = undefined,
|
||||
});
|
||||
}
|
||||
} else if (self.bin_file.cast(link.File.Coff)) |_| {
|
||||
if (self.air.value(callee)) |func_value| {
|
||||
if (func_value.castTag(.function)) |func_payload| {
|
||||
const func = func_payload.data;
|
||||
const fn_owner_decl = mod.declPtr(func.owner_decl);
|
||||
try self.genSetReg(Type.initTag(.usize), .rax, .{
|
||||
.got_load = fn_owner_decl.link.coff.sym_index,
|
||||
});
|
||||
_ = try self.addInst(.{
|
||||
.tag = .call,
|
||||
.ops = Mir.Inst.Ops.encode(.{
|
||||
.reg1 = .rax,
|
||||
.flags = 0b01,
|
||||
}),
|
||||
.data = undefined,
|
||||
});
|
||||
} else if (func_value.castTag(.extern_fn)) |_| {
|
||||
return self.fail("TODO implement calling extern functions", .{});
|
||||
} else {
|
||||
return self.fail("TODO implement calling bitcasted functions", .{});
|
||||
}
|
||||
} else {
|
||||
assert(ty.zigTypeTag() == .Pointer);
|
||||
const mcv = try self.resolveInst(callee);
|
||||
try self.genSetReg(Type.initTag(.usize), .rax, mcv);
|
||||
_ = try self.addInst(.{
|
||||
.tag = .call,
|
||||
.ops = Mir.Inst.Ops.encode(.{
|
||||
.reg1 = .rax,
|
||||
.flags = 0b01,
|
||||
}),
|
||||
.data = undefined,
|
||||
});
|
||||
}
|
||||
} else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
|
||||
if (self.air.value(callee)) |func_value| {
|
||||
if (func_value.castTag(.function)) |func_payload| {
|
||||
const func = func_payload.data;
|
||||
const fn_owner_decl = mod.declPtr(func.owner_decl);
|
||||
try self.genSetReg(Type.initTag(.usize), .rax, .{
|
||||
.got_load = fn_owner_decl.link.macho.sym_index,
|
||||
});
|
||||
const sym_index = fn_owner_decl.link.macho.sym_index;
|
||||
try self.genSetReg(Type.initTag(.usize), .rax, .{ .got_load = sym_index });
|
||||
// callq *%rax
|
||||
_ = try self.addInst(.{
|
||||
.tag = .call,
|
||||
@@ -6842,13 +6876,11 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne
|
||||
const got_addr = got.p_vaddr + decl.link.elf.offset_table_index * ptr_bytes;
|
||||
return MCValue{ .memory = got_addr };
|
||||
} else if (self.bin_file.cast(link.File.MachO)) |_| {
|
||||
// Because MachO is PIE-always-on, we defer memory address resolution until
|
||||
// the linker has enough info to perform relocations.
|
||||
assert(decl.link.macho.sym_index != 0);
|
||||
return MCValue{ .got_load = decl.link.macho.sym_index };
|
||||
} else if (self.bin_file.cast(link.File.Coff)) |coff_file| {
|
||||
const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes;
|
||||
return MCValue{ .memory = got_addr };
|
||||
} else if (self.bin_file.cast(link.File.Coff)) |_| {
|
||||
assert(decl.link.coff.sym_index != 0);
|
||||
return MCValue{ .got_load = decl.link.coff.sym_index };
|
||||
} else if (self.bin_file.cast(link.File.Plan9)) |p9| {
|
||||
try p9.seeDecl(decl_index);
|
||||
const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes;
|
||||
|
||||
+29
-11
@@ -137,7 +137,7 @@ pub fn lowerMir(emit: *Emit) InnerError!void {
|
||||
.fld => try emit.mirFld(inst),
|
||||
|
||||
.lea => try emit.mirLea(inst),
|
||||
.lea_pie => try emit.mirLeaPie(inst),
|
||||
.lea_pic => try emit.mirLeaPic(inst),
|
||||
|
||||
.shl => try emit.mirShift(.shl, inst),
|
||||
.sal => try emit.mirShift(.sal, inst),
|
||||
@@ -338,7 +338,7 @@ fn mirJmpCall(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void {
|
||||
.base = ops.reg1,
|
||||
}), emit.code);
|
||||
},
|
||||
0b11 => return emit.fail("TODO unused JMP/CALL variant 0b11", .{}),
|
||||
0b11 => return emit.fail("TODO unused variant jmp/call 0b11", .{}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -784,7 +784,7 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
|
||||
// FD
|
||||
return lowerToFdEnc(.mov, ops.reg1, imm, emit.code);
|
||||
},
|
||||
else => return emit.fail("TODO unused variant: movabs 0b{b}", .{ops.flags}),
|
||||
else => return emit.fail("TODO unused movabs variant", .{}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -978,12 +978,17 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
|
||||
}
|
||||
}
|
||||
|
||||
fn mirLeaPie(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
|
||||
fn mirLeaPic(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
|
||||
const tag = emit.mir.instructions.items(.tag)[inst];
|
||||
assert(tag == .lea_pie);
|
||||
assert(tag == .lea_pic);
|
||||
const ops = emit.mir.instructions.items(.ops)[inst].decode();
|
||||
const relocation = emit.mir.instructions.items(.data)[inst].relocation;
|
||||
|
||||
switch (ops.flags) {
|
||||
0b00, 0b01 => {},
|
||||
else => return emit.fail("TODO unused LEA PIC variants 0b10 and 0b11", .{}),
|
||||
}
|
||||
|
||||
// lea reg1, [rip + reloc]
|
||||
// RM
|
||||
try lowerToRmEnc(
|
||||
@@ -994,16 +999,17 @@ fn mirLeaPie(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
|
||||
);
|
||||
|
||||
const end_offset = emit.code.items.len;
|
||||
const gpa = emit.bin_file.allocator;
|
||||
|
||||
if (emit.bin_file.cast(link.File.MachO)) |macho_file| {
|
||||
const reloc_type = switch (ops.flags) {
|
||||
0b00 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_GOT),
|
||||
0b01 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_SIGNED),
|
||||
else => return emit.fail("TODO unused LEA PIE variants 0b10 and 0b11", .{}),
|
||||
else => unreachable,
|
||||
};
|
||||
const atom = macho_file.atom_by_index_table.get(relocation.atom_index).?;
|
||||
log.debug("adding reloc of type {} to local @{d}", .{ reloc_type, relocation.sym_index });
|
||||
try atom.relocs.append(emit.bin_file.allocator, .{
|
||||
try atom.relocs.append(gpa, .{
|
||||
.offset = @intCast(u32, end_offset - 4),
|
||||
.target = .{ .sym_index = relocation.sym_index, .file = null },
|
||||
.addend = 0,
|
||||
@@ -1012,11 +1018,23 @@ fn mirLeaPie(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
|
||||
.length = 2,
|
||||
.@"type" = reloc_type,
|
||||
});
|
||||
} else if (emit.bin_file.cast(link.File.Coff)) |coff_file| {
|
||||
const atom = coff_file.atom_by_index_table.get(relocation.atom_index).?;
|
||||
try atom.addRelocation(coff_file, .{
|
||||
.@"type" = switch (ops.flags) {
|
||||
0b00 => .got,
|
||||
0b01 => .direct,
|
||||
else => unreachable,
|
||||
},
|
||||
.target = .{ .sym_index = relocation.sym_index, .file = null },
|
||||
.offset = @intCast(u32, end_offset - 4),
|
||||
.addend = 0,
|
||||
.pcrel = true,
|
||||
.length = 2,
|
||||
.prev_vaddr = atom.getSymbol(coff_file).value,
|
||||
});
|
||||
} else {
|
||||
return emit.fail(
|
||||
"TODO implement lea reg, [rip + reloc] for linking backends different than MachO",
|
||||
.{},
|
||||
);
|
||||
return emit.fail("TODO implement lea reg, [rip + reloc] for linking backends different than MachO", .{});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -178,11 +178,11 @@ pub const Inst = struct {
|
||||
lea,
|
||||
|
||||
/// ops flags: form:
|
||||
/// 0b00 reg1, [rip + reloc] // via GOT emits X86_64_RELOC_GOT relocation
|
||||
/// 0b01 reg1, [rip + reloc] // direct load emits X86_64_RELOC_SIGNED relocation
|
||||
/// 0b00 reg1, [rip + reloc] // via GOT PIC
|
||||
/// 0b01 reg1, [rip + reloc] // direct load PIC
|
||||
/// Notes:
|
||||
/// * `Data` contains `relocation`
|
||||
lea_pie,
|
||||
lea_pic,
|
||||
|
||||
/// ops flags: form:
|
||||
/// 0b00 reg1, 1
|
||||
@@ -242,15 +242,14 @@ pub const Inst = struct {
|
||||
imul_complex,
|
||||
|
||||
/// ops flags: form:
|
||||
/// 0bX0 reg1, imm64
|
||||
/// 0bX1 rax, moffs64
|
||||
/// 0b00 reg1, imm64
|
||||
/// 0b01 rax, moffs64
|
||||
/// Notes:
|
||||
/// * If reg1 is 64-bit, the immediate is 64-bit and stored
|
||||
/// within extra data `Imm64`.
|
||||
/// * For 0bX1, reg1 (or reg2) need to be
|
||||
/// * For 0b01, reg1 (or reg2) need to be
|
||||
/// a version of rax. If reg1 == .none, then reg2 == .rax,
|
||||
/// or vice versa.
|
||||
/// TODO handle scaling
|
||||
movabs,
|
||||
|
||||
/// ops flags: form:
|
||||
|
||||
+3
-3
@@ -245,8 +245,8 @@ pub const File = struct {
|
||||
|
||||
pub const LinkBlock = union {
|
||||
elf: Elf.TextBlock,
|
||||
coff: Coff.TextBlock,
|
||||
macho: MachO.TextBlock,
|
||||
coff: Coff.Atom,
|
||||
macho: MachO.Atom,
|
||||
plan9: Plan9.DeclBlock,
|
||||
c: void,
|
||||
wasm: Wasm.DeclBlock,
|
||||
@@ -267,7 +267,7 @@ pub const File = struct {
|
||||
|
||||
pub const Export = union {
|
||||
elf: Elf.Export,
|
||||
coff: void,
|
||||
coff: Coff.Export,
|
||||
macho: MachO.Export,
|
||||
plan9: Plan9.Export,
|
||||
c: void,
|
||||
|
||||
+1295
-1234
@@ -1,132 +1,180 @@
|
||||
const Coff = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const build_options = @import("build_options");
|
||||
const builtin = @import("builtin");
|
||||
const log = std.log.scoped(.link);
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
const fs = std.fs;
|
||||
const allocPrint = std.fmt.allocPrint;
|
||||
const coff = std.coff;
|
||||
const fmt = std.fmt;
|
||||
const log = std.log.scoped(.link);
|
||||
const math = std.math;
|
||||
const mem = std.mem;
|
||||
|
||||
const lldMain = @import("../main.zig").lldMain;
|
||||
const trace = @import("../tracy.zig").trace;
|
||||
const Module = @import("../Module.zig");
|
||||
const Compilation = @import("../Compilation.zig");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const codegen = @import("../codegen.zig");
|
||||
const link = @import("../link.zig");
|
||||
const build_options = @import("build_options");
|
||||
const Cache = @import("../Cache.zig");
|
||||
const mingw = @import("../mingw.zig");
|
||||
const lld = @import("Coff/lld.zig");
|
||||
const trace = @import("../tracy.zig").trace;
|
||||
|
||||
const Air = @import("../Air.zig");
|
||||
pub const Atom = @import("Coff/Atom.zig");
|
||||
const Compilation = @import("../Compilation.zig");
|
||||
const Liveness = @import("../Liveness.zig");
|
||||
const LlvmObject = @import("../codegen/llvm.zig").Object;
|
||||
const Module = @import("../Module.zig");
|
||||
const Object = @import("Coff/Object.zig");
|
||||
const StringTable = @import("strtab.zig").StringTable;
|
||||
const TypedValue = @import("../TypedValue.zig");
|
||||
|
||||
const allocation_padding = 4 / 3;
|
||||
const minimum_text_block_size = 64 * allocation_padding;
|
||||
|
||||
const section_alignment = 4096;
|
||||
const file_alignment = 512;
|
||||
const default_image_base = 0x400_000;
|
||||
const section_table_size = 2 * 40;
|
||||
comptime {
|
||||
assert(mem.isAligned(default_image_base, section_alignment));
|
||||
}
|
||||
|
||||
pub const base_tag: link.File.Tag = .coff;
|
||||
|
||||
const msdos_stub = @embedFile("msdos-stub.bin");
|
||||
const N_DATA_DIRS: u5 = 16;
|
||||
|
||||
/// If this is not null, an object file is created by LLVM and linked with LLD afterwards.
|
||||
llvm_object: ?*LlvmObject = null,
|
||||
|
||||
base: link.File,
|
||||
ptr_width: PtrWidth,
|
||||
error_flags: link.File.ErrorFlags = .{},
|
||||
|
||||
text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = .{},
|
||||
last_text_block: ?*TextBlock = null,
|
||||
ptr_width: PtrWidth,
|
||||
page_size: u32,
|
||||
|
||||
/// Section table file pointer.
|
||||
section_table_offset: u32 = 0,
|
||||
/// Section data file pointer.
|
||||
section_data_offset: u32 = 0,
|
||||
/// Optional header file pointer.
|
||||
optional_header_offset: u32 = 0,
|
||||
objects: std.ArrayListUnmanaged(Object) = .{},
|
||||
|
||||
/// 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) = .{},
|
||||
sections: std.MultiArrayList(Section) = .{},
|
||||
data_directories: [N_DATA_DIRS]coff.ImageDataDirectory,
|
||||
|
||||
text_section_index: ?u16 = null,
|
||||
got_section_index: ?u16 = null,
|
||||
rdata_section_index: ?u16 = null,
|
||||
data_section_index: ?u16 = null,
|
||||
|
||||
locals: std.ArrayListUnmanaged(coff.Symbol) = .{},
|
||||
globals: std.StringArrayHashMapUnmanaged(SymbolWithLoc) = .{},
|
||||
|
||||
locals_free_list: std.ArrayListUnmanaged(u32) = .{},
|
||||
|
||||
strtab: StringTable(.strtab) = .{},
|
||||
strtab_offset: ?u32 = null,
|
||||
|
||||
got_entries: std.AutoArrayHashMapUnmanaged(SymbolWithLoc, u32) = .{},
|
||||
got_entries_free_list: std.ArrayListUnmanaged(u32) = .{},
|
||||
|
||||
/// Virtual address of the entry point procedure relative to image base.
|
||||
entry_addr: ?u32 = null,
|
||||
|
||||
/// Absolute virtual address of the text section when the executable is loaded in memory.
|
||||
text_section_virtual_address: u32 = 0,
|
||||
/// Current size of the `.text` section on disk, must be a multiple of `file_alignment`
|
||||
text_section_size: u32 = 0,
|
||||
/// Table of Decls that are currently alive.
|
||||
/// We store them here so that we can properly dispose of any allocated
|
||||
/// memory within the atom in the incremental linker.
|
||||
/// TODO consolidate this.
|
||||
decls: std.AutoHashMapUnmanaged(Module.Decl.Index, ?u16) = .{},
|
||||
|
||||
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,
|
||||
/// List of atoms that are either synthetic or map directly to the Zig source program.
|
||||
managed_atoms: std.ArrayListUnmanaged(*Atom) = .{},
|
||||
|
||||
pub const PtrWidth = enum { p32, p64 };
|
||||
/// Table of atoms indexed by the symbol index.
|
||||
atom_by_index_table: std.AutoHashMapUnmanaged(u32, *Atom) = .{},
|
||||
|
||||
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,
|
||||
/// Table of unnamed constants associated with a parent `Decl`.
|
||||
/// We store them here so that we can free the constants whenever the `Decl`
|
||||
/// needs updating or is freed.
|
||||
///
|
||||
/// For example,
|
||||
///
|
||||
/// ```zig
|
||||
/// const Foo = struct{
|
||||
/// a: u8,
|
||||
/// };
|
||||
///
|
||||
/// pub fn main() void {
|
||||
/// var foo = Foo{ .a = 1 };
|
||||
/// _ = foo;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// value assigned to label `foo` is an unnamed constant belonging/associated
|
||||
/// with `Decl` `main`, and lives as long as that `Decl`.
|
||||
unnamed_const_atoms: UnnamedConstTable = .{},
|
||||
|
||||
pub const empty = TextBlock{
|
||||
.text_offset = 0,
|
||||
.size = 0,
|
||||
.offset_table_index = undefined,
|
||||
.prev = null,
|
||||
.next = null,
|
||||
};
|
||||
/// A table of relocations indexed by the owning them `TextBlock`.
|
||||
/// Note that once we refactor `TextBlock`'s lifetime and ownership rules,
|
||||
/// this will be a table indexed by index into the list of Atoms.
|
||||
relocs: RelocTable = .{},
|
||||
|
||||
/// 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 Reloc = struct {
|
||||
@"type": enum {
|
||||
got,
|
||||
direct,
|
||||
},
|
||||
target: SymbolWithLoc,
|
||||
offset: u32,
|
||||
addend: u32,
|
||||
pcrel: bool,
|
||||
length: u2,
|
||||
prev_vaddr: u32,
|
||||
};
|
||||
|
||||
const RelocTable = std.AutoHashMapUnmanaged(*Atom, std.ArrayListUnmanaged(Reloc));
|
||||
const UnnamedConstTable = std.AutoHashMapUnmanaged(Module.Decl.Index, std.ArrayListUnmanaged(*Atom));
|
||||
|
||||
const default_file_alignment: u16 = 0x200;
|
||||
const default_image_base_dll: u64 = 0x10000000;
|
||||
const default_image_base_exe: u64 = 0x400000;
|
||||
const default_size_of_stack_reserve: u32 = 0x1000000;
|
||||
const default_size_of_stack_commit: u32 = 0x1000;
|
||||
const default_size_of_heap_reserve: u32 = 0x100000;
|
||||
const default_size_of_heap_commit: u32 = 0x1000;
|
||||
|
||||
const Section = struct {
|
||||
header: coff.SectionHeader,
|
||||
|
||||
last_atom: ?*Atom = null,
|
||||
|
||||
/// A list of atoms that have surplus capacity. This list can have false
|
||||
/// positives, as functions grow and shrink over time, only sometimes being added
|
||||
/// or removed from the freelist.
|
||||
///
|
||||
/// An atom has surplus capacity when its overcapacity value is greater than
|
||||
/// padToIdeal(minimum_atom_size). That is, when it has so
|
||||
/// much extra capacity, that we could fit a small new symbol in it, itself with
|
||||
/// ideal_capacity or more.
|
||||
///
|
||||
/// Ideal capacity is defined by size + (size / ideal_factor).
|
||||
///
|
||||
/// Overcapacity is measured by actual_capacity - ideal_capacity. Note that
|
||||
/// overcapacity can be negative. A simple way to have negative overcapacity is to
|
||||
/// allocate a fresh atom, which will have ideal capacity, and then grow it
|
||||
/// by 1 byte. It will then have -1 overcapacity.
|
||||
free_list: std.ArrayListUnmanaged(*Atom) = .{},
|
||||
};
|
||||
|
||||
pub const PtrWidth = enum { p32, p64 };
|
||||
pub const SrcFn = void;
|
||||
|
||||
pub const Export = struct {
|
||||
sym_index: ?u32 = null,
|
||||
};
|
||||
|
||||
pub const SymbolWithLoc = struct {
|
||||
// Index into the respective symbol table.
|
||||
sym_index: u32,
|
||||
|
||||
// null means it's a synthetic global or Zig source.
|
||||
file: ?u32 = null,
|
||||
};
|
||||
|
||||
/// When allocating, the ideal_capacity is calculated by
|
||||
/// actual_capacity + (actual_capacity / ideal_factor)
|
||||
const ideal_factor = 3;
|
||||
|
||||
/// In order for a slice of bytes to be considered eligible to keep metadata pointing at
|
||||
/// it as a possible place to put new symbols, it must have enough room for this many bytes
|
||||
/// (plus extra for reserved capacity).
|
||||
const minimum_text_block_size = 64;
|
||||
pub const min_text_capacity = padToIdeal(minimum_text_block_size);
|
||||
|
||||
pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Coff {
|
||||
assert(options.target.ofmt == .coff);
|
||||
|
||||
@@ -144,257 +192,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
|
||||
});
|
||||
self.base.file = file;
|
||||
|
||||
// TODO Write object specific relocations, COFF symbol table, then enable object file output.
|
||||
switch (options.output_mode) {
|
||||
.Exe => {},
|
||||
.Obj => return error.TODOImplementWritingObjFiles,
|
||||
.Lib => return error.TODOImplementWritingLibFiles,
|
||||
}
|
||||
|
||||
var coff_file_header_offset: u32 = 0;
|
||||
if (options.output_mode == .Exe) {
|
||||
// Write the MS-DOS stub and the PE signature
|
||||
try self.base.file.?.pwriteAll(msdos_stub ++ "PE\x00\x00", 0);
|
||||
coff_file_header_offset = msdos_stub.len + 4;
|
||||
}
|
||||
|
||||
// COFF file header
|
||||
const data_directory_count = 0;
|
||||
var hdr_data: [112 + data_directory_count * 8 + section_table_size]u8 = undefined;
|
||||
var index: usize = 0;
|
||||
|
||||
const machine = self.base.options.target.cpu.arch.toCoffMachine();
|
||||
if (machine == .Unknown) {
|
||||
return error.UnsupportedCOFFArchitecture;
|
||||
}
|
||||
mem.writeIntLittle(u16, hdr_data[0..2], @enumToInt(machine));
|
||||
index += 2;
|
||||
|
||||
// Number of sections (we only use .got, .text)
|
||||
mem.writeIntLittle(u16, hdr_data[index..][0..2], 2);
|
||||
index += 2;
|
||||
// TimeDateStamp (u32), PointerToSymbolTable (u32), NumberOfSymbols (u32)
|
||||
mem.set(u8, hdr_data[index..][0..12], 0);
|
||||
index += 12;
|
||||
|
||||
const optional_header_size = switch (options.output_mode) {
|
||||
.Exe => data_directory_count * 8 + switch (self.ptr_width) {
|
||||
.p32 => @as(u16, 96),
|
||||
.p64 => 112,
|
||||
},
|
||||
else => 0,
|
||||
};
|
||||
|
||||
const section_table_offset = coff_file_header_offset + 20 + optional_header_size;
|
||||
const default_offset_table_size = file_alignment;
|
||||
const default_size_of_code = 0;
|
||||
|
||||
self.section_data_offset = mem.alignForwardGeneric(u32, self.section_table_offset + section_table_size, file_alignment);
|
||||
const section_data_relative_virtual_address = mem.alignForwardGeneric(u32, self.section_table_offset + section_table_size, section_alignment);
|
||||
self.offset_table_virtual_address = default_image_base + section_data_relative_virtual_address;
|
||||
self.offset_table_size = default_offset_table_size;
|
||||
self.section_table_offset = section_table_offset;
|
||||
self.text_section_virtual_address = default_image_base + section_data_relative_virtual_address + section_alignment;
|
||||
self.text_section_size = default_size_of_code;
|
||||
|
||||
// Size of file when loaded in memory
|
||||
const size_of_image = mem.alignForwardGeneric(u32, self.text_section_virtual_address - default_image_base + default_size_of_code, section_alignment);
|
||||
|
||||
mem.writeIntLittle(u16, hdr_data[index..][0..2], optional_header_size);
|
||||
index += 2;
|
||||
|
||||
// Characteristics
|
||||
var characteristics: std.coff.CoffHeaderFlags = .{
|
||||
.DEBUG_STRIPPED = 1, // TODO remove debug info stripped flag when necessary
|
||||
.RELOCS_STRIPPED = 1,
|
||||
};
|
||||
if (options.output_mode == .Exe) {
|
||||
characteristics.EXECUTABLE_IMAGE = 1;
|
||||
}
|
||||
switch (self.ptr_width) {
|
||||
.p32 => characteristics.@"32BIT_MACHINE" = 1,
|
||||
.p64 => characteristics.LARGE_ADDRESS_AWARE = 1,
|
||||
}
|
||||
mem.writeIntLittle(u16, hdr_data[index..][0..2], @bitCast(u16, characteristics));
|
||||
index += 2;
|
||||
|
||||
assert(index == 20);
|
||||
try self.base.file.?.pwriteAll(hdr_data[0..index], coff_file_header_offset);
|
||||
|
||||
if (options.output_mode == .Exe) {
|
||||
self.optional_header_offset = coff_file_header_offset + 20;
|
||||
// Optional header
|
||||
index = 0;
|
||||
mem.writeIntLittle(u16, hdr_data[0..2], switch (self.ptr_width) {
|
||||
.p32 => @as(u16, 0x10b),
|
||||
.p64 => 0x20b,
|
||||
});
|
||||
index += 2;
|
||||
|
||||
// Linker version (u8 + u8)
|
||||
mem.set(u8, hdr_data[index..][0..2], 0);
|
||||
index += 2;
|
||||
|
||||
// SizeOfCode (UNUSED, u32), SizeOfInitializedData (u32), SizeOfUninitializedData (u32), AddressOfEntryPoint (u32), BaseOfCode (UNUSED, u32)
|
||||
mem.set(u8, hdr_data[index..][0..20], 0);
|
||||
index += 20;
|
||||
|
||||
if (self.ptr_width == .p32) {
|
||||
// Base of data relative to the image base (UNUSED)
|
||||
mem.set(u8, hdr_data[index..][0..4], 0);
|
||||
index += 4;
|
||||
|
||||
// Image base address
|
||||
mem.writeIntLittle(u32, hdr_data[index..][0..4], default_image_base);
|
||||
index += 4;
|
||||
} else {
|
||||
// Image base address
|
||||
mem.writeIntLittle(u64, hdr_data[index..][0..8], default_image_base);
|
||||
index += 8;
|
||||
}
|
||||
|
||||
// Section alignment
|
||||
mem.writeIntLittle(u32, hdr_data[index..][0..4], section_alignment);
|
||||
index += 4;
|
||||
// File alignment
|
||||
mem.writeIntLittle(u32, hdr_data[index..][0..4], file_alignment);
|
||||
index += 4;
|
||||
// Required OS version, 6.0 is vista
|
||||
mem.writeIntLittle(u16, hdr_data[index..][0..2], 6);
|
||||
index += 2;
|
||||
mem.writeIntLittle(u16, hdr_data[index..][0..2], 0);
|
||||
index += 2;
|
||||
// Image version
|
||||
mem.set(u8, hdr_data[index..][0..4], 0);
|
||||
index += 4;
|
||||
// Required subsystem version, same as OS version
|
||||
mem.writeIntLittle(u16, hdr_data[index..][0..2], 6);
|
||||
index += 2;
|
||||
mem.writeIntLittle(u16, hdr_data[index..][0..2], 0);
|
||||
index += 2;
|
||||
// Reserved zeroes (u32)
|
||||
mem.set(u8, hdr_data[index..][0..4], 0);
|
||||
index += 4;
|
||||
mem.writeIntLittle(u32, hdr_data[index..][0..4], size_of_image);
|
||||
index += 4;
|
||||
mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset);
|
||||
index += 4;
|
||||
// CheckSum (u32)
|
||||
mem.set(u8, hdr_data[index..][0..4], 0);
|
||||
index += 4;
|
||||
// Subsystem, TODO: Let users specify the subsystem, always CUI for now
|
||||
mem.writeIntLittle(u16, hdr_data[index..][0..2], 3);
|
||||
index += 2;
|
||||
// DLL characteristics
|
||||
mem.writeIntLittle(u16, hdr_data[index..][0..2], 0x0);
|
||||
index += 2;
|
||||
|
||||
switch (self.ptr_width) {
|
||||
.p32 => {
|
||||
// Size of stack reserve + commit
|
||||
mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000_000);
|
||||
index += 4;
|
||||
mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000);
|
||||
index += 4;
|
||||
// Size of heap reserve + commit
|
||||
mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x100_000);
|
||||
index += 4;
|
||||
mem.writeIntLittle(u32, hdr_data[index..][0..4], 0x1_000);
|
||||
index += 4;
|
||||
},
|
||||
.p64 => {
|
||||
// Size of stack reserve + commit
|
||||
mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000_000);
|
||||
index += 8;
|
||||
mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000);
|
||||
index += 8;
|
||||
// Size of heap reserve + commit
|
||||
mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x100_000);
|
||||
index += 8;
|
||||
mem.writeIntLittle(u64, hdr_data[index..][0..8], 0x1_000);
|
||||
index += 8;
|
||||
},
|
||||
}
|
||||
|
||||
// Reserved zeroes
|
||||
mem.set(u8, hdr_data[index..][0..4], 0);
|
||||
index += 4;
|
||||
|
||||
// Number of data directories
|
||||
mem.writeIntLittle(u32, hdr_data[index..][0..4], data_directory_count);
|
||||
index += 4;
|
||||
// Initialize data directories to zero
|
||||
mem.set(u8, hdr_data[index..][0 .. data_directory_count * 8], 0);
|
||||
index += data_directory_count * 8;
|
||||
|
||||
assert(index == optional_header_size);
|
||||
}
|
||||
|
||||
// Write section table.
|
||||
// First, the .got section
|
||||
hdr_data[index..][0..8].* = ".got\x00\x00\x00\x00".*;
|
||||
index += 8;
|
||||
if (options.output_mode == .Exe) {
|
||||
// Virtual size (u32)
|
||||
mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size);
|
||||
index += 4;
|
||||
// Virtual address (u32)
|
||||
mem.writeIntLittle(u32, hdr_data[index..][0..4], self.offset_table_virtual_address - default_image_base);
|
||||
index += 4;
|
||||
} else {
|
||||
mem.set(u8, hdr_data[index..][0..8], 0);
|
||||
index += 8;
|
||||
}
|
||||
// Size of raw data (u32)
|
||||
mem.writeIntLittle(u32, hdr_data[index..][0..4], default_offset_table_size);
|
||||
index += 4;
|
||||
// File pointer to the start of the section
|
||||
mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset);
|
||||
index += 4;
|
||||
// Pointer to relocations (u32), PointerToLinenumbers (u32), NumberOfRelocations (u16), NumberOfLinenumbers (u16)
|
||||
mem.set(u8, hdr_data[index..][0..12], 0);
|
||||
index += 12;
|
||||
// Section flags
|
||||
mem.writeIntLittle(u32, hdr_data[index..][0..4], @bitCast(u32, std.coff.SectionHeaderFlags{
|
||||
.CNT_INITIALIZED_DATA = 1,
|
||||
.MEM_READ = 1,
|
||||
}));
|
||||
index += 4;
|
||||
// Then, the .text section
|
||||
hdr_data[index..][0..8].* = ".text\x00\x00\x00".*;
|
||||
index += 8;
|
||||
if (options.output_mode == .Exe) {
|
||||
// Virtual size (u32)
|
||||
mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code);
|
||||
index += 4;
|
||||
// Virtual address (u32)
|
||||
mem.writeIntLittle(u32, hdr_data[index..][0..4], self.text_section_virtual_address - default_image_base);
|
||||
index += 4;
|
||||
} else {
|
||||
mem.set(u8, hdr_data[index..][0..8], 0);
|
||||
index += 8;
|
||||
}
|
||||
// Size of raw data (u32)
|
||||
mem.writeIntLittle(u32, hdr_data[index..][0..4], default_size_of_code);
|
||||
index += 4;
|
||||
// File pointer to the start of the section
|
||||
mem.writeIntLittle(u32, hdr_data[index..][0..4], self.section_data_offset + default_offset_table_size);
|
||||
index += 4;
|
||||
// Pointer to relocations (u32), PointerToLinenumbers (u32), NumberOfRelocations (u16), NumberOfLinenumbers (u16)
|
||||
mem.set(u8, hdr_data[index..][0..12], 0);
|
||||
index += 12;
|
||||
// Section flags
|
||||
mem.writeIntLittle(u32, hdr_data[index..][0..4], @bitCast(u32, std.coff.SectionHeaderFlags{
|
||||
.CNT_CODE = 1,
|
||||
.MEM_EXECUTE = 1,
|
||||
.MEM_READ = 1,
|
||||
.MEM_WRITE = 1,
|
||||
}));
|
||||
index += 4;
|
||||
|
||||
assert(index == optional_header_size + section_table_size);
|
||||
try self.base.file.?.pwriteAll(hdr_data[0..index], self.optional_header_offset);
|
||||
try self.base.file.?.setEndPos(self.section_data_offset + default_offset_table_size + default_size_of_code);
|
||||
try self.populateMissingMetadata();
|
||||
|
||||
return self;
|
||||
}
|
||||
@@ -405,6 +203,9 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff {
|
||||
33...64 => .p64,
|
||||
else => return error.UnsupportedCOFFArchitecture,
|
||||
};
|
||||
const page_size: u32 = switch (options.target.cpu.arch) {
|
||||
else => 0x1000,
|
||||
};
|
||||
const self = try gpa.create(Coff);
|
||||
errdefer gpa.destroy(self);
|
||||
self.* = .{
|
||||
@@ -415,6 +216,8 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff {
|
||||
.file = null,
|
||||
},
|
||||
.ptr_width = ptr_width,
|
||||
.page_size = page_size,
|
||||
.data_directories = comptime mem.zeroes([N_DATA_DIRS]coff.ImageDataDirectory),
|
||||
};
|
||||
|
||||
const use_llvm = build_options.have_llvm and options.use_llvm;
|
||||
@@ -425,245 +228,530 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Coff {
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void {
|
||||
if (self.llvm_object) |_| return;
|
||||
pub fn deinit(self: *Coff) void {
|
||||
const gpa = self.base.allocator;
|
||||
|
||||
try self.offset_table.ensureUnusedCapacity(self.base.allocator, 1);
|
||||
|
||||
const decl = self.base.options.module.?.declPtr(decl_index);
|
||||
if (self.offset_table_free_list.popOrNull()) |i| {
|
||||
decl.link.coff.offset_table_index = i;
|
||||
} else {
|
||||
decl.link.coff.offset_table_index = @intCast(u32, self.offset_table.items.len);
|
||||
_ = self.offset_table.addOneAssumeCapacity();
|
||||
|
||||
const entry_size = self.base.options.target.cpu.arch.ptrBitWidth() / 8;
|
||||
if (self.offset_table.items.len > self.offset_table_size / entry_size) {
|
||||
self.offset_table_size_dirty = true;
|
||||
}
|
||||
if (build_options.have_llvm) {
|
||||
if (self.llvm_object) |llvm_object| llvm_object.destroy(gpa);
|
||||
}
|
||||
|
||||
self.offset_table.items[decl.link.coff.offset_table_index] = 0;
|
||||
for (self.objects.items) |*object| {
|
||||
object.deinit(gpa);
|
||||
}
|
||||
self.objects.deinit(gpa);
|
||||
|
||||
for (self.sections.items(.free_list)) |*free_list| {
|
||||
free_list.deinit(gpa);
|
||||
}
|
||||
self.sections.deinit(gpa);
|
||||
|
||||
for (self.managed_atoms.items) |atom| {
|
||||
gpa.destroy(atom);
|
||||
}
|
||||
self.managed_atoms.deinit(gpa);
|
||||
|
||||
self.locals.deinit(gpa);
|
||||
self.globals.deinit(gpa);
|
||||
self.locals_free_list.deinit(gpa);
|
||||
self.strtab.deinit(gpa);
|
||||
self.got_entries.deinit(gpa);
|
||||
self.got_entries_free_list.deinit(gpa);
|
||||
self.decls.deinit(gpa);
|
||||
self.atom_by_index_table.deinit(gpa);
|
||||
|
||||
{
|
||||
var it = self.unnamed_const_atoms.valueIterator();
|
||||
while (it.next()) |atoms| {
|
||||
atoms.deinit(gpa);
|
||||
}
|
||||
self.unnamed_const_atoms.deinit(gpa);
|
||||
}
|
||||
|
||||
{
|
||||
var it = self.relocs.valueIterator();
|
||||
while (it.next()) |relocs| {
|
||||
relocs.deinit(gpa);
|
||||
}
|
||||
self.relocs.deinit(gpa);
|
||||
}
|
||||
}
|
||||
|
||||
fn allocateTextBlock(self: *Coff, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 {
|
||||
const new_block_min_capacity = new_block_size * allocation_padding;
|
||||
fn populateMissingMetadata(self: *Coff) !void {
|
||||
assert(self.llvm_object == null);
|
||||
const gpa = self.base.allocator;
|
||||
|
||||
// We use these to indicate our intention to update metadata, placing the new block,
|
||||
if (self.text_section_index == null) {
|
||||
self.text_section_index = @intCast(u16, self.sections.slice().len);
|
||||
const file_size = @intCast(u32, self.base.options.program_code_size_hint);
|
||||
const off = self.findFreeSpace(file_size, self.page_size); // TODO we are over-aligning in file; we should track both in file and in memory pointers
|
||||
log.debug("found .text free space 0x{x} to 0x{x}", .{ off, off + file_size });
|
||||
var header = coff.SectionHeader{
|
||||
.name = undefined,
|
||||
.virtual_size = file_size,
|
||||
.virtual_address = off,
|
||||
.size_of_raw_data = file_size,
|
||||
.pointer_to_raw_data = off,
|
||||
.pointer_to_relocations = 0,
|
||||
.pointer_to_linenumbers = 0,
|
||||
.number_of_relocations = 0,
|
||||
.number_of_linenumbers = 0,
|
||||
.flags = .{
|
||||
.CNT_CODE = 1,
|
||||
.MEM_EXECUTE = 1,
|
||||
.MEM_READ = 1,
|
||||
},
|
||||
};
|
||||
try self.setSectionName(&header, ".text");
|
||||
try self.sections.append(gpa, .{ .header = header });
|
||||
}
|
||||
|
||||
if (self.got_section_index == null) {
|
||||
self.got_section_index = @intCast(u16, self.sections.slice().len);
|
||||
const file_size = @intCast(u32, self.base.options.symbol_count_hint);
|
||||
const off = self.findFreeSpace(file_size, self.page_size);
|
||||
log.debug("found .got free space 0x{x} to 0x{x}", .{ off, off + file_size });
|
||||
var header = coff.SectionHeader{
|
||||
.name = undefined,
|
||||
.virtual_size = file_size,
|
||||
.virtual_address = off,
|
||||
.size_of_raw_data = file_size,
|
||||
.pointer_to_raw_data = off,
|
||||
.pointer_to_relocations = 0,
|
||||
.pointer_to_linenumbers = 0,
|
||||
.number_of_relocations = 0,
|
||||
.number_of_linenumbers = 0,
|
||||
.flags = .{
|
||||
.CNT_INITIALIZED_DATA = 1,
|
||||
.MEM_READ = 1,
|
||||
},
|
||||
};
|
||||
try self.setSectionName(&header, ".got");
|
||||
try self.sections.append(gpa, .{ .header = header });
|
||||
}
|
||||
|
||||
if (self.rdata_section_index == null) {
|
||||
self.rdata_section_index = @intCast(u16, self.sections.slice().len);
|
||||
const file_size: u32 = 1024;
|
||||
const off = self.findFreeSpace(file_size, self.page_size);
|
||||
log.debug("found .rdata free space 0x{x} to 0x{x}", .{ off, off + file_size });
|
||||
var header = coff.SectionHeader{
|
||||
.name = undefined,
|
||||
.virtual_size = file_size,
|
||||
.virtual_address = off,
|
||||
.size_of_raw_data = file_size,
|
||||
.pointer_to_raw_data = off,
|
||||
.pointer_to_relocations = 0,
|
||||
.pointer_to_linenumbers = 0,
|
||||
.number_of_relocations = 0,
|
||||
.number_of_linenumbers = 0,
|
||||
.flags = .{
|
||||
.CNT_INITIALIZED_DATA = 1,
|
||||
.MEM_READ = 1,
|
||||
},
|
||||
};
|
||||
try self.setSectionName(&header, ".rdata");
|
||||
try self.sections.append(gpa, .{ .header = header });
|
||||
}
|
||||
|
||||
if (self.data_section_index == null) {
|
||||
self.data_section_index = @intCast(u16, self.sections.slice().len);
|
||||
const file_size: u32 = 1024;
|
||||
const off = self.findFreeSpace(file_size, self.page_size);
|
||||
log.debug("found .data free space 0x{x} to 0x{x}", .{ off, off + file_size });
|
||||
var header = coff.SectionHeader{
|
||||
.name = undefined,
|
||||
.virtual_size = file_size,
|
||||
.virtual_address = off,
|
||||
.size_of_raw_data = file_size,
|
||||
.pointer_to_raw_data = off,
|
||||
.pointer_to_relocations = 0,
|
||||
.pointer_to_linenumbers = 0,
|
||||
.number_of_relocations = 0,
|
||||
.number_of_linenumbers = 0,
|
||||
.flags = .{
|
||||
.CNT_INITIALIZED_DATA = 1,
|
||||
.MEM_READ = 1,
|
||||
.MEM_WRITE = 1,
|
||||
},
|
||||
};
|
||||
try self.setSectionName(&header, ".data");
|
||||
try self.sections.append(gpa, .{ .header = header });
|
||||
}
|
||||
|
||||
if (self.strtab_offset == null) {
|
||||
try self.strtab.buffer.append(gpa, 0);
|
||||
self.strtab_offset = self.findFreeSpace(@intCast(u32, self.strtab.len()), 1);
|
||||
log.debug("found strtab free space 0x{x} to 0x{x}", .{ self.strtab_offset.?, self.strtab_offset.? + self.strtab.len() });
|
||||
}
|
||||
|
||||
// Index 0 is always a null symbol.
|
||||
try self.locals.append(gpa, .{
|
||||
.name = [_]u8{0} ** 8,
|
||||
.value = 0,
|
||||
.section_number = @intToEnum(coff.SectionNumber, 0),
|
||||
.@"type" = .{ .base_type = .NULL, .complex_type = .NULL },
|
||||
.storage_class = .NULL,
|
||||
.number_of_aux_symbols = 0,
|
||||
});
|
||||
|
||||
{
|
||||
// We need to find out what the max file offset is according to section headers.
|
||||
// Otherwise, we may end up with an COFF binary with file size not matching the final section's
|
||||
// offset + it's filesize.
|
||||
// TODO I don't like this here one bit
|
||||
var max_file_offset: u64 = 0;
|
||||
for (self.sections.items(.header)) |header| {
|
||||
if (header.pointer_to_raw_data + header.size_of_raw_data > max_file_offset) {
|
||||
max_file_offset = header.pointer_to_raw_data + header.size_of_raw_data;
|
||||
}
|
||||
}
|
||||
try self.base.file.?.pwriteAll(&[_]u8{0}, max_file_offset);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allocateDeclIndexes(self: *Coff, decl_index: Module.Decl.Index) !void {
|
||||
if (self.llvm_object) |_| return;
|
||||
const decl = self.base.options.module.?.declPtr(decl_index);
|
||||
if (decl.link.coff.sym_index != 0) return;
|
||||
decl.link.coff.sym_index = try self.allocateSymbol();
|
||||
const gpa = self.base.allocator;
|
||||
try self.atom_by_index_table.putNoClobber(gpa, decl.link.coff.sym_index, &decl.link.coff);
|
||||
try self.decls.putNoClobber(gpa, decl_index, null);
|
||||
}
|
||||
|
||||
fn allocateAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32) !u32 {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const sect_id = @enumToInt(atom.getSymbol(self).section_number) - 1;
|
||||
const header = &self.sections.items(.header)[sect_id];
|
||||
const free_list = &self.sections.items(.free_list)[sect_id];
|
||||
const maybe_last_atom = &self.sections.items(.last_atom)[sect_id];
|
||||
const new_atom_ideal_capacity = if (header.isCode()) padToIdeal(new_atom_size) else new_atom_size;
|
||||
|
||||
// We use these to indicate our intention to update metadata, placing the new atom,
|
||||
// and possibly removing a free list node.
|
||||
// It would be simpler to do it inside the for loop below, but that would cause a
|
||||
// problem if an error was returned later in the function. So this action
|
||||
// is actually carried out at the end of the function, when errors are no longer possible.
|
||||
var block_placement: ?*TextBlock = null;
|
||||
var atom_placement: ?*Atom = null;
|
||||
var free_list_removal: ?usize = null;
|
||||
|
||||
const vaddr = blk: {
|
||||
// First we look for an appropriately sized free list node.
|
||||
// The list is unordered. We'll just take the first thing that works.
|
||||
var vaddr = blk: {
|
||||
var i: usize = 0;
|
||||
while (i < self.text_block_free_list.items.len) {
|
||||
const free_block = self.text_block_free_list.items[i];
|
||||
|
||||
const next_block_text_offset = free_block.text_offset + free_block.capacity();
|
||||
const new_block_text_offset = mem.alignForwardGeneric(u64, free_block.getVAddr(self.*) + free_block.size, alignment) - self.text_section_virtual_address;
|
||||
if (new_block_text_offset < next_block_text_offset and next_block_text_offset - new_block_text_offset >= new_block_min_capacity) {
|
||||
block_placement = free_block;
|
||||
|
||||
const remaining_capacity = next_block_text_offset - new_block_text_offset - new_block_min_capacity;
|
||||
if (remaining_capacity < minimum_text_block_size) {
|
||||
free_list_removal = i;
|
||||
}
|
||||
|
||||
break :blk new_block_text_offset + self.text_section_virtual_address;
|
||||
} else {
|
||||
if (!free_block.freeListEligible()) {
|
||||
_ = self.text_block_free_list.swapRemove(i);
|
||||
while (i < free_list.items.len) {
|
||||
const big_atom = free_list.items[i];
|
||||
// We now have a pointer to a live atom that has too much capacity.
|
||||
// Is it enough that we could fit this new atom?
|
||||
const sym = big_atom.getSymbol(self);
|
||||
const capacity = big_atom.capacity(self);
|
||||
const ideal_capacity = if (header.isCode()) padToIdeal(capacity) else capacity;
|
||||
const ideal_capacity_end_vaddr = math.add(u32, sym.value, ideal_capacity) catch ideal_capacity;
|
||||
const capacity_end_vaddr = sym.value + capacity;
|
||||
const new_start_vaddr_unaligned = capacity_end_vaddr - new_atom_ideal_capacity;
|
||||
const new_start_vaddr = mem.alignBackwardGeneric(u32, new_start_vaddr_unaligned, alignment);
|
||||
if (new_start_vaddr < ideal_capacity_end_vaddr) {
|
||||
// Additional bookkeeping here to notice if this free list node
|
||||
// should be deleted because the atom that it points to has grown to take up
|
||||
// more of the extra capacity.
|
||||
if (!big_atom.freeListEligible(self)) {
|
||||
_ = free_list.swapRemove(i);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
} else if (self.last_text_block) |last| {
|
||||
const new_block_vaddr = mem.alignForwardGeneric(u64, last.getVAddr(self.*) + last.size, alignment);
|
||||
block_placement = last;
|
||||
break :blk new_block_vaddr;
|
||||
// At this point we know that we will place the new atom here. But the
|
||||
// remaining question is whether there is still yet enough capacity left
|
||||
// over for there to still be a free list node.
|
||||
const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr;
|
||||
const keep_free_list_node = remaining_capacity >= min_text_capacity;
|
||||
|
||||
// Set up the metadata to be updated, after errors are no longer possible.
|
||||
atom_placement = big_atom;
|
||||
if (!keep_free_list_node) {
|
||||
free_list_removal = i;
|
||||
}
|
||||
break :blk new_start_vaddr;
|
||||
} else if (maybe_last_atom.*) |last| {
|
||||
const last_symbol = last.getSymbol(self);
|
||||
const ideal_capacity = if (header.isCode()) padToIdeal(last.size) else last.size;
|
||||
const ideal_capacity_end_vaddr = last_symbol.value + ideal_capacity;
|
||||
const new_start_vaddr = mem.alignForwardGeneric(u32, ideal_capacity_end_vaddr, alignment);
|
||||
atom_placement = last;
|
||||
break :blk new_start_vaddr;
|
||||
} else {
|
||||
break :blk self.text_section_virtual_address;
|
||||
break :blk mem.alignForwardGeneric(u32, header.virtual_address, alignment);
|
||||
}
|
||||
};
|
||||
|
||||
const expand_text_section = block_placement == null or block_placement.?.next == null;
|
||||
if (expand_text_section) {
|
||||
const needed_size = @intCast(u32, mem.alignForwardGeneric(u64, vaddr + new_block_size - self.text_section_virtual_address, file_alignment));
|
||||
if (needed_size > self.text_section_size) {
|
||||
const current_text_section_virtual_size = mem.alignForwardGeneric(u32, self.text_section_size, section_alignment);
|
||||
const new_text_section_virtual_size = mem.alignForwardGeneric(u32, needed_size, section_alignment);
|
||||
if (current_text_section_virtual_size != new_text_section_virtual_size) {
|
||||
self.size_of_image_dirty = true;
|
||||
// Write new virtual size
|
||||
var buf: [4]u8 = undefined;
|
||||
mem.writeIntLittle(u32, &buf, new_text_section_virtual_size);
|
||||
try self.base.file.?.pwriteAll(&buf, self.section_table_offset + 40 + 8);
|
||||
}
|
||||
|
||||
self.text_section_size = needed_size;
|
||||
self.text_section_size_dirty = true;
|
||||
const expand_section = atom_placement == null or atom_placement.?.next == null;
|
||||
if (expand_section) {
|
||||
const sect_capacity = self.allocatedSize(header.pointer_to_raw_data);
|
||||
const needed_size: u32 = (vaddr + new_atom_size) - header.virtual_address;
|
||||
if (needed_size > sect_capacity) {
|
||||
@panic("TODO move section");
|
||||
}
|
||||
self.last_text_block = text_block;
|
||||
}
|
||||
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;
|
||||
maybe_last_atom.* = atom;
|
||||
// header.virtual_size = needed_size;
|
||||
// header.size_of_raw_data = mem.alignForwardGeneric(u32, needed_size, default_file_alignment);
|
||||
}
|
||||
|
||||
if (block_placement) |big_block| {
|
||||
text_block.prev = big_block;
|
||||
text_block.next = big_block.next;
|
||||
big_block.next = text_block;
|
||||
// if (header.getAlignment().? < alignment) {
|
||||
// header.setAlignment(alignment);
|
||||
// }
|
||||
atom.size = new_atom_size;
|
||||
atom.alignment = alignment;
|
||||
|
||||
if (atom.prev) |prev| {
|
||||
prev.next = atom.next;
|
||||
}
|
||||
if (atom.next) |next| {
|
||||
next.prev = atom.prev;
|
||||
}
|
||||
|
||||
if (atom_placement) |big_atom| {
|
||||
atom.prev = big_atom;
|
||||
atom.next = big_atom.next;
|
||||
big_atom.next = atom;
|
||||
} else {
|
||||
text_block.prev = null;
|
||||
text_block.next = null;
|
||||
atom.prev = null;
|
||||
atom.next = null;
|
||||
}
|
||||
if (free_list_removal) |i| {
|
||||
_ = self.text_block_free_list.swapRemove(i);
|
||||
_ = free_list.swapRemove(i);
|
||||
}
|
||||
|
||||
return vaddr;
|
||||
}
|
||||
|
||||
fn growTextBlock(self: *Coff, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 {
|
||||
const block_vaddr = text_block.getVAddr(self.*);
|
||||
const align_ok = mem.alignBackwardGeneric(u64, block_vaddr, alignment) == block_vaddr;
|
||||
const need_realloc = !align_ok or new_block_size > text_block.capacity();
|
||||
if (!need_realloc) return @as(u64, block_vaddr);
|
||||
return self.allocateTextBlock(text_block, new_block_size, alignment);
|
||||
fn allocateSymbol(self: *Coff) !u32 {
|
||||
const gpa = self.base.allocator;
|
||||
try self.locals.ensureUnusedCapacity(gpa, 1);
|
||||
|
||||
const index = blk: {
|
||||
if (self.locals_free_list.popOrNull()) |index| {
|
||||
log.debug(" (reusing symbol index {d})", .{index});
|
||||
break :blk index;
|
||||
} else {
|
||||
log.debug(" (allocating symbol index {d})", .{self.locals.items.len});
|
||||
const index = @intCast(u32, self.locals.items.len);
|
||||
_ = self.locals.addOneAssumeCapacity();
|
||||
break :blk index;
|
||||
}
|
||||
};
|
||||
|
||||
self.locals.items[index] = .{
|
||||
.name = [_]u8{0} ** 8,
|
||||
.value = 0,
|
||||
.section_number = @intToEnum(coff.SectionNumber, 0),
|
||||
.@"type" = .{ .base_type = .NULL, .complex_type = .NULL },
|
||||
.storage_class = .NULL,
|
||||
.number_of_aux_symbols = 0,
|
||||
};
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
fn shrinkTextBlock(self: *Coff, text_block: *TextBlock, new_block_size: u64) void {
|
||||
text_block.size = @intCast(u32, new_block_size);
|
||||
if (text_block.capacity() - text_block.size >= minimum_text_block_size) {
|
||||
self.text_block_free_list.append(self.base.allocator, text_block) catch {};
|
||||
pub fn allocateGotEntry(self: *Coff, target: SymbolWithLoc) !u32 {
|
||||
const gpa = self.base.allocator;
|
||||
try self.got_entries.ensureUnusedCapacity(gpa, 1);
|
||||
const index: u32 = blk: {
|
||||
if (self.got_entries_free_list.popOrNull()) |index| {
|
||||
log.debug(" (reusing GOT entry index {d})", .{index});
|
||||
if (self.got_entries.getIndex(target)) |existing| {
|
||||
assert(existing == index);
|
||||
}
|
||||
break :blk index;
|
||||
} else {
|
||||
log.debug(" (allocating GOT entry at index {d})", .{self.got_entries.keys().len});
|
||||
const index = @intCast(u32, self.got_entries.keys().len);
|
||||
self.got_entries.putAssumeCapacityNoClobber(target, 0);
|
||||
break :blk index;
|
||||
}
|
||||
};
|
||||
self.got_entries.keys()[index] = target;
|
||||
return index;
|
||||
}
|
||||
|
||||
fn createGotAtom(self: *Coff, target: SymbolWithLoc) !*Atom {
|
||||
const gpa = self.base.allocator;
|
||||
const atom = try gpa.create(Atom);
|
||||
errdefer gpa.destroy(atom);
|
||||
atom.* = Atom.empty;
|
||||
atom.sym_index = try self.allocateSymbol();
|
||||
atom.size = @sizeOf(u64);
|
||||
atom.alignment = @alignOf(u64);
|
||||
|
||||
try self.managed_atoms.append(gpa, atom);
|
||||
try self.atom_by_index_table.putNoClobber(gpa, atom.sym_index, atom);
|
||||
self.got_entries.getPtr(target).?.* = atom.sym_index;
|
||||
|
||||
const sym = atom.getSymbolPtr(self);
|
||||
sym.section_number = @intToEnum(coff.SectionNumber, self.got_section_index.? + 1);
|
||||
sym.value = try self.allocateAtom(atom, atom.size, atom.alignment);
|
||||
|
||||
log.debug("allocated GOT atom at 0x{x}", .{sym.value});
|
||||
|
||||
try atom.addRelocation(self, .{
|
||||
.@"type" = .direct,
|
||||
.target = target,
|
||||
.offset = 0,
|
||||
.addend = 0,
|
||||
.pcrel = false,
|
||||
.length = 3,
|
||||
.prev_vaddr = sym.value,
|
||||
});
|
||||
|
||||
return atom;
|
||||
}
|
||||
|
||||
fn growAtom(self: *Coff, atom: *Atom, new_atom_size: u32, alignment: u32) !u32 {
|
||||
const sym = atom.getSymbol(self);
|
||||
const align_ok = mem.alignBackwardGeneric(u32, sym.value, alignment) == sym.value;
|
||||
const need_realloc = !align_ok or new_atom_size > atom.capacity(self);
|
||||
if (!need_realloc) return sym.value;
|
||||
return self.allocateAtom(atom, new_atom_size, alignment);
|
||||
}
|
||||
|
||||
fn shrinkAtom(self: *Coff, atom: *Atom, new_block_size: u32) void {
|
||||
_ = self;
|
||||
_ = atom;
|
||||
_ = new_block_size;
|
||||
// TODO check the new capacity, and if it crosses the size threshold into a big enough
|
||||
// capacity, insert a free list node for it.
|
||||
}
|
||||
|
||||
fn writeAtom(self: *Coff, atom: *Atom, code: []const u8) !void {
|
||||
const sym = atom.getSymbol(self);
|
||||
const section = self.sections.get(@enumToInt(sym.section_number) - 1);
|
||||
const file_offset = section.header.pointer_to_raw_data + sym.value - section.header.virtual_address;
|
||||
log.debug("writing atom for symbol {s} at file offset 0x{x}", .{ atom.getName(self), file_offset });
|
||||
try self.base.file.?.pwriteAll(code, file_offset);
|
||||
try self.resolveRelocs(atom);
|
||||
}
|
||||
|
||||
fn writeGotAtom(self: *Coff, atom: *Atom) !void {
|
||||
switch (self.ptr_width) {
|
||||
.p32 => {
|
||||
var buffer: [@sizeOf(u32)]u8 = [_]u8{0} ** @sizeOf(u32);
|
||||
try self.writeAtom(atom, &buffer);
|
||||
},
|
||||
.p64 => {
|
||||
var buffer: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64);
|
||||
try self.writeAtom(atom, &buffer);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn freeTextBlock(self: *Coff, text_block: *TextBlock) void {
|
||||
fn resolveRelocs(self: *Coff, atom: *Atom) !void {
|
||||
const relocs = self.relocs.get(atom) orelse return;
|
||||
const source_sym = atom.getSymbol(self);
|
||||
const source_section = self.sections.get(@enumToInt(source_sym.section_number) - 1).header;
|
||||
const file_offset = source_section.pointer_to_raw_data + source_sym.value - source_section.virtual_address;
|
||||
|
||||
log.debug("relocating '{s}'", .{atom.getName(self)});
|
||||
|
||||
for (relocs.items) |*reloc| {
|
||||
const target_vaddr = switch (reloc.@"type") {
|
||||
.got => blk: {
|
||||
const got_atom = self.getGotAtomForSymbol(reloc.target) orelse continue;
|
||||
break :blk got_atom.getSymbol(self).value;
|
||||
},
|
||||
.direct => self.getSymbol(reloc.target).value,
|
||||
};
|
||||
const target_vaddr_with_addend = target_vaddr + reloc.addend;
|
||||
|
||||
if (target_vaddr_with_addend == reloc.prev_vaddr) continue;
|
||||
|
||||
log.debug(" ({x}: [() => 0x{x} ({s})) ({s})", .{
|
||||
reloc.offset,
|
||||
target_vaddr_with_addend,
|
||||
self.getSymbolName(reloc.target),
|
||||
@tagName(reloc.@"type"),
|
||||
});
|
||||
|
||||
if (reloc.pcrel) {
|
||||
const source_vaddr = source_sym.value + reloc.offset;
|
||||
const disp = target_vaddr_with_addend - source_vaddr - 4;
|
||||
try self.base.file.?.pwriteAll(mem.asBytes(&@intCast(u32, disp)), file_offset + reloc.offset);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (self.ptr_width) {
|
||||
.p32 => try self.base.file.?.pwriteAll(
|
||||
mem.asBytes(&@intCast(u32, target_vaddr_with_addend + default_image_base_exe)),
|
||||
file_offset + reloc.offset,
|
||||
),
|
||||
.p64 => switch (reloc.length) {
|
||||
2 => try self.base.file.?.pwriteAll(
|
||||
mem.asBytes(&@truncate(u32, target_vaddr_with_addend + default_image_base_exe)),
|
||||
file_offset + reloc.offset,
|
||||
),
|
||||
3 => try self.base.file.?.pwriteAll(
|
||||
mem.asBytes(&(target_vaddr_with_addend + default_image_base_exe)),
|
||||
file_offset + reloc.offset,
|
||||
),
|
||||
else => unreachable,
|
||||
},
|
||||
}
|
||||
|
||||
reloc.prev_vaddr = target_vaddr_with_addend;
|
||||
}
|
||||
}
|
||||
|
||||
fn freeAtom(self: *Coff, atom: *Atom) void {
|
||||
log.debug("freeAtom {*}", .{atom});
|
||||
|
||||
const sym = atom.getSymbol(self);
|
||||
const sect_id = @enumToInt(sym.section_number) - 1;
|
||||
const free_list = &self.sections.items(.free_list)[sect_id];
|
||||
var already_have_free_list_node = false;
|
||||
{
|
||||
var i: usize = 0;
|
||||
// TODO turn text_block_free_list into a hash map
|
||||
while (i < self.text_block_free_list.items.len) {
|
||||
if (self.text_block_free_list.items[i] == text_block) {
|
||||
_ = self.text_block_free_list.swapRemove(i);
|
||||
// TODO turn free_list into a hash map
|
||||
while (i < free_list.items.len) {
|
||||
if (free_list.items[i] == atom) {
|
||||
_ = free_list.swapRemove(i);
|
||||
continue;
|
||||
}
|
||||
if (self.text_block_free_list.items[i] == text_block.prev) {
|
||||
if (free_list.items[i] == atom.prev) {
|
||||
already_have_free_list_node = true;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
if (self.last_text_block == text_block) {
|
||||
self.last_text_block = text_block.prev;
|
||||
}
|
||||
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;
|
||||
mem.writeIntLittle(u32, buf[0..4], new_raw_size);
|
||||
try self.base.file.?.pwriteAll(buf[0..4], self.section_table_offset + 16);
|
||||
// Write the new .text section file offset in the .text section header
|
||||
mem.writeIntLittle(u32, buf[0..4], new_text_section_start);
|
||||
try self.base.file.?.pwriteAll(buf[0..4], self.section_table_offset + 40 + 20);
|
||||
|
||||
const current_virtual_size = mem.alignForwardGeneric(u32, self.offset_table_size, section_alignment);
|
||||
const new_virtual_size = mem.alignForwardGeneric(u32, new_raw_size, section_alignment);
|
||||
// If we had to move in the virtual address space, we need to fix the VAs in the offset table, as well as the virtual address of the `.text` section
|
||||
// and the virtual size of the `.got` section
|
||||
|
||||
if (new_virtual_size != current_virtual_size) {
|
||||
log.debug("growing offset table from virtual size {} to {}\n", .{ current_virtual_size, new_virtual_size });
|
||||
self.size_of_image_dirty = true;
|
||||
const va_offset = new_virtual_size - current_virtual_size;
|
||||
|
||||
// Write .got virtual size
|
||||
mem.writeIntLittle(u32, buf[0..4], new_virtual_size);
|
||||
try self.base.file.?.pwriteAll(buf[0..4], self.section_table_offset + 8);
|
||||
|
||||
// Write .text new virtual address
|
||||
self.text_section_virtual_address = self.text_section_virtual_address + va_offset;
|
||||
mem.writeIntLittle(u32, buf[0..4], self.text_section_virtual_address - default_image_base);
|
||||
try self.base.file.?.pwriteAll(buf[0..4], self.section_table_offset + 40 + 12);
|
||||
|
||||
// Fix the VAs in the offset table
|
||||
for (self.offset_table.items) |*va, idx| {
|
||||
if (va.* != 0) {
|
||||
va.* += va_offset;
|
||||
|
||||
switch (entry_size) {
|
||||
4 => {
|
||||
mem.writeInt(u32, buf[0..4], @intCast(u32, va.*), endian);
|
||||
try self.base.file.?.pwriteAll(buf[0..4], offset_table_start + idx * entry_size);
|
||||
},
|
||||
8 => {
|
||||
mem.writeInt(u64, &buf, va.*, endian);
|
||||
try self.base.file.?.pwriteAll(&buf, offset_table_start + idx * entry_size);
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
const maybe_last_atom = &self.sections.items(.last_atom)[sect_id];
|
||||
if (maybe_last_atom.*) |last_atom| {
|
||||
if (last_atom == atom) {
|
||||
if (atom.prev) |prev| {
|
||||
// TODO shrink the section size here
|
||||
maybe_last_atom.* = prev;
|
||||
} else {
|
||||
maybe_last_atom.* = null;
|
||||
}
|
||||
}
|
||||
self.offset_table_size = new_raw_size;
|
||||
self.offset_table_size_dirty = false;
|
||||
}
|
||||
// Write the new entry
|
||||
switch (entry_size) {
|
||||
4 => {
|
||||
var buf: [4]u8 = undefined;
|
||||
mem.writeInt(u32, &buf, @intCast(u32, self.offset_table.items[index]), endian);
|
||||
try self.base.file.?.pwriteAll(&buf, offset_table_start + index * entry_size);
|
||||
},
|
||||
8 => {
|
||||
var buf: [8]u8 = undefined;
|
||||
mem.writeInt(u64, &buf, self.offset_table.items[index], endian);
|
||||
try self.base.file.?.pwriteAll(&buf, offset_table_start + index * entry_size);
|
||||
},
|
||||
else => unreachable,
|
||||
|
||||
if (atom.prev) |prev| {
|
||||
prev.next = atom.next;
|
||||
|
||||
if (!already_have_free_list_node and prev.freeListEligible(self)) {
|
||||
// The free list is heuristics, it doesn't have to be perfect, so we can
|
||||
// ignore the OOM here.
|
||||
free_list.append(self.base.allocator, prev) catch {};
|
||||
}
|
||||
} else {
|
||||
atom.prev = null;
|
||||
}
|
||||
|
||||
if (atom.next) |next| {
|
||||
next.prev = atom.prev;
|
||||
} else {
|
||||
atom.next = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -702,15 +790,18 @@ pub fn updateFunc(self: *Coff, module: *Module, func: *Module.Fn, air: Air, live
|
||||
},
|
||||
};
|
||||
|
||||
return self.finishUpdateDecl(module, func.owner_decl, code);
|
||||
try self.updateDeclCode(decl_index, code, .FUNCTION);
|
||||
|
||||
// Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated.
|
||||
const decl_exports = module.decl_exports.get(decl_index) orelse &[0]*Module.Export{};
|
||||
return self.updateDeclExports(module, decl_index, decl_exports);
|
||||
}
|
||||
|
||||
pub fn lowerUnnamedConst(self: *Coff, tv: TypedValue, decl_index: Module.Decl.Index) !u32 {
|
||||
_ = self;
|
||||
_ = tv;
|
||||
_ = decl_index;
|
||||
log.debug("TODO lowerUnnamedConst for Coff", .{});
|
||||
return error.AnalysisFail;
|
||||
@panic("TODO lowerUnnamedConst");
|
||||
}
|
||||
|
||||
pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) !void {
|
||||
@@ -728,16 +819,20 @@ pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) !
|
||||
if (decl.val.tag() == .extern_fn) {
|
||||
return; // TODO Should we do more when front-end analyzed extern decl?
|
||||
}
|
||||
|
||||
// TODO COFF/PE debug information
|
||||
// TODO Implement exports
|
||||
if (decl.val.castTag(.variable)) |payload| {
|
||||
const variable = payload.data;
|
||||
if (variable.is_extern) {
|
||||
return; // TODO Should we do more when front-end analyzed extern decl?
|
||||
}
|
||||
}
|
||||
|
||||
var code_buffer = std.ArrayList(u8).init(self.base.allocator);
|
||||
defer code_buffer.deinit();
|
||||
|
||||
const decl_val = if (decl.val.castTag(.variable)) |payload| payload.data.init else decl.val;
|
||||
const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), .{
|
||||
.ty = decl.ty,
|
||||
.val = decl.val,
|
||||
.val = decl_val,
|
||||
}, &code_buffer, .none, .{
|
||||
.parent_atom_index = 0,
|
||||
});
|
||||
@@ -751,49 +846,100 @@ pub fn updateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index) !
|
||||
},
|
||||
};
|
||||
|
||||
return self.finishUpdateDecl(module, decl_index, code);
|
||||
}
|
||||
|
||||
fn finishUpdateDecl(self: *Coff, module: *Module, decl_index: Module.Decl.Index, code: []const u8) !void {
|
||||
const decl = module.declPtr(decl_index);
|
||||
const required_alignment = decl.ty.abiAlignment(self.base.options.target);
|
||||
const curr_size = decl.link.coff.size;
|
||||
if (curr_size != 0) {
|
||||
const capacity = decl.link.coff.capacity();
|
||||
const need_realloc = code.len > capacity or
|
||||
!mem.isAlignedGeneric(u32, decl.link.coff.text_offset, required_alignment);
|
||||
if (need_realloc) {
|
||||
const curr_vaddr = self.text_section_virtual_address + decl.link.coff.text_offset;
|
||||
const vaddr = try self.growTextBlock(&decl.link.coff, code.len, required_alignment);
|
||||
log.debug("growing {s} from 0x{x} to 0x{x}\n", .{ decl.name, curr_vaddr, vaddr });
|
||||
if (vaddr != curr_vaddr) {
|
||||
log.debug(" (writing new offset table entry)\n", .{});
|
||||
self.offset_table.items[decl.link.coff.offset_table_index] = vaddr;
|
||||
try self.writeOffsetTableEntry(decl.link.coff.offset_table_index);
|
||||
}
|
||||
} else if (code.len < curr_size) {
|
||||
self.shrinkTextBlock(&decl.link.coff, code.len);
|
||||
}
|
||||
} else {
|
||||
const vaddr = try self.allocateTextBlock(&decl.link.coff, code.len, required_alignment);
|
||||
log.debug("allocated text block for {s} at 0x{x} (size: {Bi})\n", .{
|
||||
mem.sliceTo(decl.name, 0),
|
||||
vaddr,
|
||||
std.fmt.fmtIntSizeDec(code.len),
|
||||
});
|
||||
errdefer self.freeTextBlock(&decl.link.coff);
|
||||
self.offset_table.items[decl.link.coff.offset_table_index] = vaddr;
|
||||
try self.writeOffsetTableEntry(decl.link.coff.offset_table_index);
|
||||
}
|
||||
|
||||
// Write the code into the file
|
||||
try self.base.file.?.pwriteAll(code, self.section_data_offset + self.offset_table_size + decl.link.coff.text_offset);
|
||||
try self.updateDeclCode(decl_index, code, .NULL);
|
||||
|
||||
// Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated.
|
||||
const decl_exports = module.decl_exports.get(decl_index) orelse &[0]*Module.Export{};
|
||||
return self.updateDeclExports(module, decl_index, decl_exports);
|
||||
}
|
||||
|
||||
fn getDeclOutputSection(self: *Coff, decl: *Module.Decl) u16 {
|
||||
const ty = decl.ty;
|
||||
const zig_ty = ty.zigTypeTag();
|
||||
const val = decl.val;
|
||||
const index: u16 = blk: {
|
||||
if (val.isUndefDeep()) {
|
||||
// TODO in release-fast and release-small, we should put undef in .bss
|
||||
break :blk self.data_section_index.?;
|
||||
}
|
||||
|
||||
switch (zig_ty) {
|
||||
.Fn => break :blk self.text_section_index.?,
|
||||
else => {
|
||||
if (val.castTag(.variable)) |_| {
|
||||
break :blk self.data_section_index.?;
|
||||
}
|
||||
break :blk self.rdata_section_index.?;
|
||||
},
|
||||
}
|
||||
};
|
||||
return index;
|
||||
}
|
||||
|
||||
fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []const u8, complex_type: coff.ComplexType) !void {
|
||||
const gpa = self.base.allocator;
|
||||
const mod = self.base.options.module.?;
|
||||
const decl = mod.declPtr(decl_index);
|
||||
|
||||
const decl_name = try decl.getFullyQualifiedName(mod);
|
||||
defer gpa.free(decl_name);
|
||||
|
||||
log.debug("updateDeclCode {s}{*}", .{ decl_name, decl });
|
||||
const required_alignment = decl.getAlignment(self.base.options.target);
|
||||
|
||||
const decl_ptr = self.decls.getPtr(decl_index).?;
|
||||
if (decl_ptr.* == null) {
|
||||
decl_ptr.* = self.getDeclOutputSection(decl);
|
||||
}
|
||||
const sect_index = decl_ptr.*.?;
|
||||
|
||||
const code_len = @intCast(u32, code.len);
|
||||
const atom = &decl.link.coff;
|
||||
assert(atom.sym_index != 0); // Caller forgot to allocateDeclIndexes()
|
||||
if (atom.size != 0) {
|
||||
const sym = atom.getSymbolPtr(self);
|
||||
try self.setSymbolName(sym, decl_name);
|
||||
sym.section_number = @intToEnum(coff.SectionNumber, sect_index + 1);
|
||||
sym.@"type" = .{ .complex_type = complex_type, .base_type = .NULL };
|
||||
|
||||
const capacity = atom.capacity(self);
|
||||
const need_realloc = code.len > capacity or !mem.isAlignedGeneric(u64, sym.value, required_alignment);
|
||||
if (need_realloc) {
|
||||
const vaddr = try self.growAtom(atom, code_len, required_alignment);
|
||||
log.debug("growing {s} from 0x{x} to 0x{x}", .{ decl_name, sym.value, vaddr });
|
||||
log.debug(" (required alignment 0x{x}", .{required_alignment});
|
||||
|
||||
if (vaddr != sym.value) {
|
||||
sym.value = vaddr;
|
||||
log.debug(" (updating GOT entry)", .{});
|
||||
const got_atom = self.getGotAtomForSymbol(.{ .sym_index = atom.sym_index, .file = null }).?;
|
||||
try self.writeGotAtom(got_atom);
|
||||
}
|
||||
} else if (code_len < atom.size) {
|
||||
self.shrinkAtom(atom, code_len);
|
||||
}
|
||||
atom.size = code_len;
|
||||
} else {
|
||||
const sym = atom.getSymbolPtr(self);
|
||||
try self.setSymbolName(sym, decl_name);
|
||||
sym.section_number = @intToEnum(coff.SectionNumber, sect_index + 1);
|
||||
sym.@"type" = .{ .complex_type = complex_type, .base_type = .NULL };
|
||||
|
||||
const vaddr = try self.allocateAtom(atom, code_len, required_alignment);
|
||||
errdefer self.freeAtom(atom);
|
||||
log.debug("allocated atom for {s} at 0x{x}", .{ decl_name, vaddr });
|
||||
atom.size = code_len;
|
||||
sym.value = vaddr;
|
||||
|
||||
const got_target = SymbolWithLoc{ .sym_index = atom.sym_index, .file = null };
|
||||
_ = try self.allocateGotEntry(got_target);
|
||||
const got_atom = try self.createGotAtom(got_target);
|
||||
try self.writeGotAtom(got_atom);
|
||||
}
|
||||
|
||||
try self.writeAtom(atom, code);
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -802,9 +948,31 @@ pub fn freeDecl(self: *Coff, decl_index: Module.Decl.Index) void {
|
||||
const mod = self.base.options.module.?;
|
||||
const decl = mod.declPtr(decl_index);
|
||||
|
||||
log.debug("freeDecl {*}", .{decl});
|
||||
|
||||
const kv = self.decls.fetchRemove(decl_index);
|
||||
if (kv.?.value) |_| {
|
||||
self.freeAtom(&decl.link.coff);
|
||||
}
|
||||
|
||||
// Appending to free lists is allowed to fail because the free lists are heuristics based anyway.
|
||||
self.freeTextBlock(&decl.link.coff);
|
||||
self.offset_table_free_list.append(self.base.allocator, decl.link.coff.offset_table_index) catch {};
|
||||
const gpa = self.base.allocator;
|
||||
const sym_index = decl.link.coff.sym_index;
|
||||
if (sym_index != 0) {
|
||||
self.locals_free_list.append(gpa, sym_index) catch {};
|
||||
|
||||
// Try freeing GOT atom if this decl had one
|
||||
const got_target = SymbolWithLoc{ .sym_index = sym_index, .file = null };
|
||||
if (self.got_entries.getIndex(got_target)) |got_index| {
|
||||
self.got_entries_free_list.append(gpa, @intCast(u32, got_index)) catch {};
|
||||
self.got_entries.values()[got_index] = 0;
|
||||
log.debug(" adding GOT index {d} to free list (target local@{d})", .{ got_index, sym_index });
|
||||
}
|
||||
|
||||
self.locals.items[sym_index].section_number = @intToEnum(coff.SectionNumber, 0);
|
||||
_ = self.atom_by_index_table.remove(sym_index);
|
||||
decl.link.coff.sym_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn updateDeclExports(
|
||||
@@ -817,64 +985,157 @@ pub fn updateDeclExports(
|
||||
@panic("Attempted to compile for object format that was disabled by build configuration");
|
||||
}
|
||||
|
||||
// Even in the case of LLVM, we need to notice certain exported symbols in order to
|
||||
// detect the default subsystem.
|
||||
for (exports) |exp| {
|
||||
const exported_decl = module.declPtr(exp.exported_decl);
|
||||
if (exported_decl.getFunction() == null) continue;
|
||||
const winapi_cc = switch (self.base.options.target.cpu.arch) {
|
||||
.i386 => std.builtin.CallingConvention.Stdcall,
|
||||
else => std.builtin.CallingConvention.C,
|
||||
};
|
||||
const decl_cc = exported_decl.ty.fnCallingConvention();
|
||||
if (decl_cc == .C and mem.eql(u8, exp.options.name, "main") and
|
||||
self.base.options.link_libc)
|
||||
{
|
||||
module.stage1_flags.have_c_main = true;
|
||||
} else if (decl_cc == winapi_cc and self.base.options.target.os.tag == .windows) {
|
||||
if (mem.eql(u8, exp.options.name, "WinMain")) {
|
||||
module.stage1_flags.have_winmain = true;
|
||||
} else if (mem.eql(u8, exp.options.name, "wWinMain")) {
|
||||
module.stage1_flags.have_wwinmain = true;
|
||||
} else if (mem.eql(u8, exp.options.name, "WinMainCRTStartup")) {
|
||||
module.stage1_flags.have_winmain_crt_startup = true;
|
||||
} else if (mem.eql(u8, exp.options.name, "wWinMainCRTStartup")) {
|
||||
module.stage1_flags.have_wwinmain_crt_startup = true;
|
||||
} else if (mem.eql(u8, exp.options.name, "DllMainCRTStartup")) {
|
||||
module.stage1_flags.have_dllmain_crt_startup = true;
|
||||
if (build_options.have_llvm) {
|
||||
// Even in the case of LLVM, we need to notice certain exported symbols in order to
|
||||
// detect the default subsystem.
|
||||
for (exports) |exp| {
|
||||
const exported_decl = module.declPtr(exp.exported_decl);
|
||||
if (exported_decl.getFunction() == null) continue;
|
||||
const winapi_cc = switch (self.base.options.target.cpu.arch) {
|
||||
.i386 => std.builtin.CallingConvention.Stdcall,
|
||||
else => std.builtin.CallingConvention.C,
|
||||
};
|
||||
const decl_cc = exported_decl.ty.fnCallingConvention();
|
||||
if (decl_cc == .C and mem.eql(u8, exp.options.name, "main") and
|
||||
self.base.options.link_libc)
|
||||
{
|
||||
module.stage1_flags.have_c_main = true;
|
||||
} else if (decl_cc == winapi_cc and self.base.options.target.os.tag == .windows) {
|
||||
if (mem.eql(u8, exp.options.name, "WinMain")) {
|
||||
module.stage1_flags.have_winmain = true;
|
||||
} else if (mem.eql(u8, exp.options.name, "wWinMain")) {
|
||||
module.stage1_flags.have_wwinmain = true;
|
||||
} else if (mem.eql(u8, exp.options.name, "WinMainCRTStartup")) {
|
||||
module.stage1_flags.have_winmain_crt_startup = true;
|
||||
} else if (mem.eql(u8, exp.options.name, "wWinMainCRTStartup")) {
|
||||
module.stage1_flags.have_wwinmain_crt_startup = true;
|
||||
} else if (mem.eql(u8, exp.options.name, "DllMainCRTStartup")) {
|
||||
module.stage1_flags.have_dllmain_crt_startup = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (build_options.have_llvm) {
|
||||
if (self.llvm_object) |llvm_object| return llvm_object.updateDeclExports(module, decl_index, exports);
|
||||
}
|
||||
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const gpa = self.base.allocator;
|
||||
|
||||
const decl = module.declPtr(decl_index);
|
||||
const atom = &decl.link.coff;
|
||||
if (atom.sym_index == 0) return;
|
||||
const decl_sym = atom.getSymbol(self);
|
||||
|
||||
for (exports) |exp| {
|
||||
log.debug("adding new export '{s}'", .{exp.options.name});
|
||||
|
||||
if (exp.options.section) |section_name| {
|
||||
if (!mem.eql(u8, section_name, ".text")) {
|
||||
try module.failed_exports.ensureUnusedCapacity(module.gpa, 1);
|
||||
module.failed_exports.putAssumeCapacityNoClobber(
|
||||
try module.failed_exports.putNoClobber(
|
||||
module.gpa,
|
||||
exp,
|
||||
try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: ExportOptions.section", .{}),
|
||||
try Module.ErrorMsg.create(
|
||||
gpa,
|
||||
decl.srcLoc(),
|
||||
"Unimplemented: ExportOptions.section",
|
||||
.{},
|
||||
),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (mem.eql(u8, exp.options.name, "_start")) {
|
||||
self.entry_addr = decl.link.coff.getVAddr(self.*) - default_image_base;
|
||||
} else {
|
||||
try module.failed_exports.ensureUnusedCapacity(module.gpa, 1);
|
||||
module.failed_exports.putAssumeCapacityNoClobber(
|
||||
|
||||
if (exp.options.linkage == .LinkOnce) {
|
||||
try module.failed_exports.putNoClobber(
|
||||
module.gpa,
|
||||
exp,
|
||||
try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: Exports other than '_start'", .{}),
|
||||
try Module.ErrorMsg.create(
|
||||
gpa,
|
||||
decl.srcLoc(),
|
||||
"Unimplemented: GlobalLinkage.LinkOnce",
|
||||
.{},
|
||||
),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const sym_index = exp.link.coff.sym_index orelse blk: {
|
||||
const sym_index = try self.allocateSymbol();
|
||||
exp.link.coff.sym_index = sym_index;
|
||||
break :blk sym_index;
|
||||
};
|
||||
const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = null };
|
||||
const sym = self.getSymbolPtr(sym_loc);
|
||||
try self.setSymbolName(sym, exp.options.name);
|
||||
sym.value = decl_sym.value;
|
||||
sym.section_number = @intToEnum(coff.SectionNumber, self.text_section_index.? + 1);
|
||||
sym.@"type" = .{ .complex_type = .FUNCTION, .base_type = .NULL };
|
||||
|
||||
switch (exp.options.linkage) {
|
||||
.Strong => {
|
||||
sym.storage_class = .EXTERNAL;
|
||||
},
|
||||
.Internal => @panic("TODO Internal"),
|
||||
.Weak => @panic("TODO WeakExternal"),
|
||||
else => unreachable,
|
||||
}
|
||||
|
||||
try self.resolveGlobalSymbol(sym_loc);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deleteExport(self: *Coff, exp: Export) void {
|
||||
if (self.llvm_object) |_| return;
|
||||
const sym_index = exp.sym_index orelse return;
|
||||
|
||||
const gpa = self.base.allocator;
|
||||
|
||||
const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = null };
|
||||
const sym = self.getSymbolPtr(sym_loc);
|
||||
const sym_name = self.getSymbolName(sym_loc);
|
||||
log.debug("deleting export '{s}'", .{sym_name});
|
||||
assert(sym.storage_class == .EXTERNAL);
|
||||
sym.* = .{
|
||||
.name = [_]u8{0} ** 8,
|
||||
.value = 0,
|
||||
.section_number = @intToEnum(coff.SectionNumber, 0),
|
||||
.@"type" = .{ .base_type = .NULL, .complex_type = .NULL },
|
||||
.storage_class = .NULL,
|
||||
.number_of_aux_symbols = 0,
|
||||
};
|
||||
self.locals_free_list.append(gpa, sym_index) catch {};
|
||||
|
||||
if (self.globals.get(sym_name)) |global| blk: {
|
||||
if (global.sym_index != sym_index) break :blk;
|
||||
if (global.file != null) break :blk;
|
||||
const kv = self.globals.fetchSwapRemove(sym_name);
|
||||
gpa.free(kv.?.key);
|
||||
}
|
||||
}
|
||||
|
||||
fn resolveGlobalSymbol(self: *Coff, current: SymbolWithLoc) !void {
|
||||
const gpa = self.base.allocator;
|
||||
const sym = self.getSymbol(current);
|
||||
_ = sym;
|
||||
const sym_name = self.getSymbolName(current);
|
||||
|
||||
const name = try gpa.dupe(u8, sym_name);
|
||||
const global_index = @intCast(u32, self.globals.values().len);
|
||||
_ = global_index;
|
||||
const gop = try self.globals.getOrPut(gpa, name);
|
||||
defer if (gop.found_existing) gpa.free(name);
|
||||
|
||||
if (!gop.found_existing) {
|
||||
gop.value_ptr.* = current;
|
||||
// TODO undef + tentative
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("TODO finish resolveGlobalSymbols implementation", .{});
|
||||
}
|
||||
|
||||
pub fn flush(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void {
|
||||
if (self.base.options.emit == null) {
|
||||
if (build_options.have_llvm) {
|
||||
@@ -884,14 +1145,13 @@ pub fn flush(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !vo
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (build_options.have_llvm and self.base.options.use_lld) {
|
||||
return self.linkWithLLD(comp, prog_node);
|
||||
} else {
|
||||
switch (self.base.options.effectiveOutputMode()) {
|
||||
.Exe, .Obj => {},
|
||||
.Lib => return error.TODOImplementWritingLibFiles,
|
||||
}
|
||||
return self.flushModule(comp, prog_node);
|
||||
const use_lld = build_options.have_llvm and self.base.options.use_lld;
|
||||
if (use_lld) {
|
||||
return lld.linkWithLLD(self, comp, prog_node);
|
||||
}
|
||||
switch (self.base.options.output_mode) {
|
||||
.Exe, .Obj => return self.flushModule(comp, prog_node),
|
||||
.Lib => return error.TODOImplementWritingLibFiles,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -909,648 +1169,449 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
|
||||
sub_prog_node.activate();
|
||||
defer sub_prog_node.end();
|
||||
|
||||
if (self.text_section_size_dirty) {
|
||||
// Write the new raw size in the .text header
|
||||
var buf: [4]u8 = undefined;
|
||||
mem.writeIntLittle(u32, &buf, self.text_section_size);
|
||||
try self.base.file.?.pwriteAll(&buf, self.section_table_offset + 40 + 16);
|
||||
try self.base.file.?.setEndPos(self.section_data_offset + self.offset_table_size + self.text_section_size);
|
||||
self.text_section_size_dirty = false;
|
||||
if (build_options.enable_logging) {
|
||||
self.logSymtab();
|
||||
}
|
||||
|
||||
if (self.base.options.output_mode == .Exe and self.size_of_image_dirty) {
|
||||
const new_size_of_image = mem.alignForwardGeneric(u32, self.text_section_virtual_address - default_image_base + self.text_section_size, section_alignment);
|
||||
var buf: [4]u8 = undefined;
|
||||
mem.writeIntLittle(u32, &buf, new_size_of_image);
|
||||
try self.base.file.?.pwriteAll(&buf, self.optional_header_offset + 56);
|
||||
self.size_of_image_dirty = false;
|
||||
{
|
||||
var it = self.relocs.keyIterator();
|
||||
while (it.next()) |atom| {
|
||||
try self.resolveRelocs(atom.*);
|
||||
}
|
||||
}
|
||||
|
||||
if (self.getEntryPoint()) |entry_sym_loc| {
|
||||
self.entry_addr = self.getSymbol(entry_sym_loc).value;
|
||||
}
|
||||
|
||||
try self.writeStrtab();
|
||||
try self.writeDataDirectoriesHeaders();
|
||||
try self.writeSectionHeaders();
|
||||
|
||||
if (self.entry_addr == null and self.base.options.output_mode == .Exe) {
|
||||
log.debug("flushing. no_entry_point_found = true\n", .{});
|
||||
self.error_flags.no_entry_point_found = true;
|
||||
} else {
|
||||
log.debug("flushing. no_entry_point_found = false\n", .{});
|
||||
self.error_flags.no_entry_point_found = false;
|
||||
|
||||
if (self.base.options.output_mode == .Exe) {
|
||||
// Write AddressOfEntryPoint
|
||||
var buf: [4]u8 = undefined;
|
||||
mem.writeIntLittle(u32, &buf, self.entry_addr.?);
|
||||
try self.base.file.?.pwriteAll(&buf, self.optional_header_offset + 16);
|
||||
}
|
||||
try self.writeHeader();
|
||||
}
|
||||
}
|
||||
|
||||
fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
var arena_allocator = std.heap.ArenaAllocator.init(self.base.allocator);
|
||||
defer arena_allocator.deinit();
|
||||
const arena = arena_allocator.allocator();
|
||||
|
||||
const directory = self.base.options.emit.?.directory; // Just an alias to make it shorter to type.
|
||||
const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path});
|
||||
|
||||
// If there is no Zig code to compile, then we should skip flushing the output file because it
|
||||
// will not be part of the linker line anyway.
|
||||
const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: {
|
||||
const use_stage1 = build_options.have_stage1 and self.base.options.use_stage1;
|
||||
if (use_stage1) {
|
||||
const obj_basename = try std.zig.binNameAlloc(arena, .{
|
||||
.root_name = self.base.options.root_name,
|
||||
.target = self.base.options.target,
|
||||
.output_mode = .Obj,
|
||||
});
|
||||
switch (self.base.options.cache_mode) {
|
||||
.incremental => break :blk try module.zig_cache_artifact_directory.join(
|
||||
arena,
|
||||
&[_][]const u8{obj_basename},
|
||||
),
|
||||
.whole => break :blk try fs.path.join(arena, &.{
|
||||
fs.path.dirname(full_out_path).?, obj_basename,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
try self.flushModule(comp, prog_node);
|
||||
|
||||
if (fs.path.dirname(full_out_path)) |dirname| {
|
||||
break :blk try fs.path.join(arena, &.{ dirname, self.base.intermediary_basename.? });
|
||||
} else {
|
||||
break :blk self.base.intermediary_basename.?;
|
||||
}
|
||||
} else null;
|
||||
|
||||
var sub_prog_node = prog_node.start("LLD Link", 0);
|
||||
sub_prog_node.activate();
|
||||
sub_prog_node.context.refresh();
|
||||
defer sub_prog_node.end();
|
||||
|
||||
const is_lib = self.base.options.output_mode == .Lib;
|
||||
const is_dyn_lib = self.base.options.link_mode == .Dynamic and is_lib;
|
||||
const is_exe_or_dyn_lib = is_dyn_lib or self.base.options.output_mode == .Exe;
|
||||
const link_in_crt = self.base.options.link_libc and is_exe_or_dyn_lib;
|
||||
const target = self.base.options.target;
|
||||
|
||||
// See link/Elf.zig for comments on how this mechanism works.
|
||||
const id_symlink_basename = "lld.id";
|
||||
|
||||
var man: Cache.Manifest = undefined;
|
||||
defer if (!self.base.options.disable_lld_caching) man.deinit();
|
||||
|
||||
var digest: [Cache.hex_digest_len]u8 = undefined;
|
||||
|
||||
if (!self.base.options.disable_lld_caching) {
|
||||
man = comp.cache_parent.obtain();
|
||||
self.base.releaseLock();
|
||||
|
||||
comptime assert(Compilation.link_hash_implementation_version == 7);
|
||||
|
||||
for (self.base.options.objects) |obj| {
|
||||
_ = try man.addFile(obj.path, null);
|
||||
man.hash.add(obj.must_link);
|
||||
}
|
||||
for (comp.c_object_table.keys()) |key| {
|
||||
_ = try man.addFile(key.status.success.object_path, null);
|
||||
}
|
||||
try man.addOptionalFile(module_obj_path);
|
||||
man.hash.addOptionalBytes(self.base.options.entry);
|
||||
man.hash.addOptional(self.base.options.stack_size_override);
|
||||
man.hash.addOptional(self.base.options.image_base_override);
|
||||
man.hash.addListOfBytes(self.base.options.lib_dirs);
|
||||
man.hash.add(self.base.options.skip_linker_dependencies);
|
||||
if (self.base.options.link_libc) {
|
||||
man.hash.add(self.base.options.libc_installation != null);
|
||||
if (self.base.options.libc_installation) |libc_installation| {
|
||||
man.hash.addBytes(libc_installation.crt_dir.?);
|
||||
if (target.abi == .msvc) {
|
||||
man.hash.addBytes(libc_installation.msvc_lib_dir.?);
|
||||
man.hash.addBytes(libc_installation.kernel32_lib_dir.?);
|
||||
}
|
||||
}
|
||||
}
|
||||
link.hashAddSystemLibs(&man.hash, self.base.options.system_libs);
|
||||
man.hash.addListOfBytes(self.base.options.force_undefined_symbols.keys());
|
||||
man.hash.addOptional(self.base.options.subsystem);
|
||||
man.hash.add(self.base.options.is_test);
|
||||
man.hash.add(self.base.options.tsaware);
|
||||
man.hash.add(self.base.options.nxcompat);
|
||||
man.hash.add(self.base.options.dynamicbase);
|
||||
// strip does not need to go into the linker hash because it is part of the hash namespace
|
||||
man.hash.addOptional(self.base.options.major_subsystem_version);
|
||||
man.hash.addOptional(self.base.options.minor_subsystem_version);
|
||||
|
||||
// We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.
|
||||
_ = try man.hit();
|
||||
digest = man.final();
|
||||
var prev_digest_buf: [digest.len]u8 = undefined;
|
||||
const prev_digest: []u8 = Cache.readSmallFile(
|
||||
directory.handle,
|
||||
id_symlink_basename,
|
||||
&prev_digest_buf,
|
||||
) catch |err| blk: {
|
||||
log.debug("COFF LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) });
|
||||
// Handle this as a cache miss.
|
||||
break :blk prev_digest_buf[0..0];
|
||||
};
|
||||
if (mem.eql(u8, prev_digest, &digest)) {
|
||||
log.debug("COFF LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)});
|
||||
// Hot diggity dog! The output binary is already there.
|
||||
self.base.lock = man.toOwnedLock();
|
||||
return;
|
||||
}
|
||||
log.debug("COFF LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) });
|
||||
|
||||
// We are about to change the output file to be different, so we invalidate the build hash now.
|
||||
directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) {
|
||||
error.FileNotFound => {},
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
if (self.base.options.output_mode == .Obj) {
|
||||
// LLD's COFF driver does not support the equivalent of `-r` so we do a simple file copy
|
||||
// here. TODO: think carefully about how we can avoid this redundant operation when doing
|
||||
// build-obj. See also the corresponding TODO in linkAsArchive.
|
||||
const the_object_path = blk: {
|
||||
if (self.base.options.objects.len != 0)
|
||||
break :blk self.base.options.objects[0].path;
|
||||
|
||||
if (comp.c_object_table.count() != 0)
|
||||
break :blk comp.c_object_table.keys()[0].status.success.object_path;
|
||||
|
||||
if (module_obj_path) |p|
|
||||
break :blk p;
|
||||
|
||||
// TODO I think this is unreachable. Audit this situation when solving the above TODO
|
||||
// regarding eliding redundant object -> object transformations.
|
||||
return error.NoObjectsToLink;
|
||||
};
|
||||
// This can happen when using --enable-cache and using the stage1 backend. In this case
|
||||
// we can skip the file copy.
|
||||
if (!mem.eql(u8, the_object_path, full_out_path)) {
|
||||
try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{});
|
||||
}
|
||||
} else {
|
||||
// Create an LLD command line and invoke it.
|
||||
var argv = std.ArrayList([]const u8).init(self.base.allocator);
|
||||
defer argv.deinit();
|
||||
// We will invoke ourselves as a child process to gain access to LLD.
|
||||
// This is necessary because LLD does not behave properly as a library -
|
||||
// it calls exit() and does not reset all global data between invocations.
|
||||
try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, "lld-link" });
|
||||
|
||||
try argv.append("-ERRORLIMIT:0");
|
||||
try argv.append("-NOLOGO");
|
||||
if (!self.base.options.strip) {
|
||||
try argv.append("-DEBUG");
|
||||
}
|
||||
if (self.base.options.lto) {
|
||||
switch (self.base.options.optimize_mode) {
|
||||
.Debug => {},
|
||||
.ReleaseSmall => try argv.append("-OPT:lldlto=2"),
|
||||
.ReleaseFast, .ReleaseSafe => try argv.append("-OPT:lldlto=3"),
|
||||
}
|
||||
}
|
||||
if (self.base.options.output_mode == .Exe) {
|
||||
const stack_size = self.base.options.stack_size_override orelse 16777216;
|
||||
try argv.append(try allocPrint(arena, "-STACK:{d}", .{stack_size}));
|
||||
}
|
||||
if (self.base.options.image_base_override) |image_base| {
|
||||
try argv.append(try std.fmt.allocPrint(arena, "-BASE:{d}", .{image_base}));
|
||||
}
|
||||
|
||||
if (target.cpu.arch == .i386) {
|
||||
try argv.append("-MACHINE:X86");
|
||||
} else if (target.cpu.arch == .x86_64) {
|
||||
try argv.append("-MACHINE:X64");
|
||||
} else if (target.cpu.arch.isARM()) {
|
||||
if (target.cpu.arch.ptrBitWidth() == 32) {
|
||||
try argv.append("-MACHINE:ARM");
|
||||
} else {
|
||||
try argv.append("-MACHINE:ARM64");
|
||||
}
|
||||
}
|
||||
|
||||
for (self.base.options.force_undefined_symbols.keys()) |symbol| {
|
||||
try argv.append(try allocPrint(arena, "-INCLUDE:{s}", .{symbol}));
|
||||
}
|
||||
|
||||
if (is_dyn_lib) {
|
||||
try argv.append("-DLL");
|
||||
}
|
||||
|
||||
if (self.base.options.entry) |entry| {
|
||||
try argv.append(try allocPrint(arena, "-ENTRY:{s}", .{entry}));
|
||||
}
|
||||
|
||||
if (self.base.options.tsaware) {
|
||||
try argv.append("-tsaware");
|
||||
}
|
||||
if (self.base.options.nxcompat) {
|
||||
try argv.append("-nxcompat");
|
||||
}
|
||||
if (self.base.options.dynamicbase) {
|
||||
try argv.append("-dynamicbase");
|
||||
}
|
||||
|
||||
try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path}));
|
||||
|
||||
if (self.base.options.implib_emit) |emit| {
|
||||
const implib_out_path = try emit.directory.join(arena, &[_][]const u8{emit.sub_path});
|
||||
try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path}));
|
||||
}
|
||||
|
||||
if (self.base.options.link_libc) {
|
||||
if (self.base.options.libc_installation) |libc_installation| {
|
||||
try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.crt_dir.?}));
|
||||
|
||||
if (target.abi == .msvc) {
|
||||
try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.msvc_lib_dir.?}));
|
||||
try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.kernel32_lib_dir.?}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (self.base.options.lib_dirs) |lib_dir| {
|
||||
try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{lib_dir}));
|
||||
}
|
||||
|
||||
try argv.ensureUnusedCapacity(self.base.options.objects.len);
|
||||
for (self.base.options.objects) |obj| {
|
||||
if (obj.must_link) {
|
||||
argv.appendAssumeCapacity(try allocPrint(arena, "-WHOLEARCHIVE:{s}", .{obj.path}));
|
||||
} else {
|
||||
argv.appendAssumeCapacity(obj.path);
|
||||
}
|
||||
}
|
||||
|
||||
for (comp.c_object_table.keys()) |key| {
|
||||
try argv.append(key.status.success.object_path);
|
||||
}
|
||||
|
||||
if (module_obj_path) |p| {
|
||||
try argv.append(p);
|
||||
}
|
||||
|
||||
const resolved_subsystem: ?std.Target.SubSystem = blk: {
|
||||
if (self.base.options.subsystem) |explicit| break :blk explicit;
|
||||
switch (target.os.tag) {
|
||||
.windows => {
|
||||
if (self.base.options.module) |module| {
|
||||
if (module.stage1_flags.have_dllmain_crt_startup or is_dyn_lib)
|
||||
break :blk null;
|
||||
if (module.stage1_flags.have_c_main or self.base.options.is_test or
|
||||
module.stage1_flags.have_winmain_crt_startup or
|
||||
module.stage1_flags.have_wwinmain_crt_startup)
|
||||
{
|
||||
break :blk .Console;
|
||||
}
|
||||
if (module.stage1_flags.have_winmain or module.stage1_flags.have_wwinmain)
|
||||
break :blk .Windows;
|
||||
}
|
||||
},
|
||||
.uefi => break :blk .EfiApplication,
|
||||
else => {},
|
||||
}
|
||||
break :blk null;
|
||||
};
|
||||
|
||||
const Mode = enum { uefi, win32 };
|
||||
const mode: Mode = mode: {
|
||||
if (resolved_subsystem) |subsystem| {
|
||||
const subsystem_suffix = ss: {
|
||||
if (self.base.options.major_subsystem_version) |major| {
|
||||
if (self.base.options.minor_subsystem_version) |minor| {
|
||||
break :ss try allocPrint(arena, ",{d}.{d}", .{ major, minor });
|
||||
} else {
|
||||
break :ss try allocPrint(arena, ",{d}", .{major});
|
||||
}
|
||||
}
|
||||
break :ss "";
|
||||
};
|
||||
|
||||
switch (subsystem) {
|
||||
.Console => {
|
||||
try argv.append(try allocPrint(arena, "-SUBSYSTEM:console{s}", .{
|
||||
subsystem_suffix,
|
||||
}));
|
||||
break :mode .win32;
|
||||
},
|
||||
.EfiApplication => {
|
||||
try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_application{s}", .{
|
||||
subsystem_suffix,
|
||||
}));
|
||||
break :mode .uefi;
|
||||
},
|
||||
.EfiBootServiceDriver => {
|
||||
try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_boot_service_driver{s}", .{
|
||||
subsystem_suffix,
|
||||
}));
|
||||
break :mode .uefi;
|
||||
},
|
||||
.EfiRom => {
|
||||
try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_rom{s}", .{
|
||||
subsystem_suffix,
|
||||
}));
|
||||
break :mode .uefi;
|
||||
},
|
||||
.EfiRuntimeDriver => {
|
||||
try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_runtime_driver{s}", .{
|
||||
subsystem_suffix,
|
||||
}));
|
||||
break :mode .uefi;
|
||||
},
|
||||
.Native => {
|
||||
try argv.append(try allocPrint(arena, "-SUBSYSTEM:native{s}", .{
|
||||
subsystem_suffix,
|
||||
}));
|
||||
break :mode .win32;
|
||||
},
|
||||
.Posix => {
|
||||
try argv.append(try allocPrint(arena, "-SUBSYSTEM:posix{s}", .{
|
||||
subsystem_suffix,
|
||||
}));
|
||||
break :mode .win32;
|
||||
},
|
||||
.Windows => {
|
||||
try argv.append(try allocPrint(arena, "-SUBSYSTEM:windows{s}", .{
|
||||
subsystem_suffix,
|
||||
}));
|
||||
break :mode .win32;
|
||||
},
|
||||
}
|
||||
} else if (target.os.tag == .uefi) {
|
||||
break :mode .uefi;
|
||||
} else {
|
||||
break :mode .win32;
|
||||
}
|
||||
};
|
||||
|
||||
switch (mode) {
|
||||
.uefi => try argv.appendSlice(&[_][]const u8{
|
||||
"-BASE:0",
|
||||
"-ENTRY:EfiMain",
|
||||
"-OPT:REF",
|
||||
"-SAFESEH:NO",
|
||||
"-MERGE:.rdata=.data",
|
||||
"-ALIGN:32",
|
||||
"-NODEFAULTLIB",
|
||||
"-SECTION:.xdata,D",
|
||||
}),
|
||||
.win32 => {
|
||||
if (link_in_crt) {
|
||||
if (target.abi.isGnu()) {
|
||||
try argv.append("-lldmingw");
|
||||
|
||||
if (target.cpu.arch == .i386) {
|
||||
try argv.append("-ALTERNATENAME:__image_base__=___ImageBase");
|
||||
} else {
|
||||
try argv.append("-ALTERNATENAME:__image_base__=__ImageBase");
|
||||
}
|
||||
|
||||
if (is_dyn_lib) {
|
||||
try argv.append(try comp.get_libc_crt_file(arena, "dllcrt2.obj"));
|
||||
if (target.cpu.arch == .i386) {
|
||||
try argv.append("-ALTERNATENAME:__DllMainCRTStartup@12=_DllMainCRTStartup@12");
|
||||
} else {
|
||||
try argv.append("-ALTERNATENAME:_DllMainCRTStartup=DllMainCRTStartup");
|
||||
}
|
||||
} else {
|
||||
try argv.append(try comp.get_libc_crt_file(arena, "crt2.obj"));
|
||||
}
|
||||
|
||||
try argv.append(try comp.get_libc_crt_file(arena, "mingw32.lib"));
|
||||
try argv.append(try comp.get_libc_crt_file(arena, "mingwex.lib"));
|
||||
try argv.append(try comp.get_libc_crt_file(arena, "msvcrt-os.lib"));
|
||||
|
||||
for (mingw.always_link_libs) |name| {
|
||||
if (!self.base.options.system_libs.contains(name)) {
|
||||
const lib_basename = try allocPrint(arena, "{s}.lib", .{name});
|
||||
try argv.append(try comp.get_libc_crt_file(arena, lib_basename));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const lib_str = switch (self.base.options.link_mode) {
|
||||
.Dynamic => "",
|
||||
.Static => "lib",
|
||||
};
|
||||
const d_str = switch (self.base.options.optimize_mode) {
|
||||
.Debug => "d",
|
||||
else => "",
|
||||
};
|
||||
switch (self.base.options.link_mode) {
|
||||
.Static => try argv.append(try allocPrint(arena, "libcmt{s}.lib", .{d_str})),
|
||||
.Dynamic => try argv.append(try allocPrint(arena, "msvcrt{s}.lib", .{d_str})),
|
||||
}
|
||||
|
||||
try argv.append(try allocPrint(arena, "{s}vcruntime{s}.lib", .{ lib_str, d_str }));
|
||||
try argv.append(try allocPrint(arena, "{s}ucrt{s}.lib", .{ lib_str, d_str }));
|
||||
|
||||
//Visual C++ 2015 Conformance Changes
|
||||
//https://msdn.microsoft.com/en-us/library/bb531344.aspx
|
||||
try argv.append("legacy_stdio_definitions.lib");
|
||||
|
||||
// msvcrt depends on kernel32 and ntdll
|
||||
try argv.append("kernel32.lib");
|
||||
try argv.append("ntdll.lib");
|
||||
}
|
||||
} else {
|
||||
try argv.append("-NODEFAULTLIB");
|
||||
if (!is_lib) {
|
||||
if (self.base.options.module) |module| {
|
||||
if (module.stage1_flags.have_winmain_crt_startup) {
|
||||
try argv.append("-ENTRY:WinMainCRTStartup");
|
||||
} else {
|
||||
try argv.append("-ENTRY:wWinMainCRTStartup");
|
||||
}
|
||||
} else {
|
||||
try argv.append("-ENTRY:wWinMainCRTStartup");
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// libc++ dep
|
||||
if (self.base.options.link_libcpp) {
|
||||
try argv.append(comp.libcxxabi_static_lib.?.full_object_path);
|
||||
try argv.append(comp.libcxx_static_lib.?.full_object_path);
|
||||
}
|
||||
|
||||
// libunwind dep
|
||||
if (self.base.options.link_libunwind) {
|
||||
try argv.append(comp.libunwind_static_lib.?.full_object_path);
|
||||
}
|
||||
|
||||
if (is_exe_or_dyn_lib and !self.base.options.skip_linker_dependencies) {
|
||||
if (!self.base.options.link_libc) {
|
||||
if (comp.libc_static_lib) |lib| {
|
||||
try argv.append(lib.full_object_path);
|
||||
}
|
||||
}
|
||||
// MinGW doesn't provide libssp symbols
|
||||
if (target.abi.isGnu()) {
|
||||
if (comp.libssp_static_lib) |lib| {
|
||||
try argv.append(lib.full_object_path);
|
||||
}
|
||||
}
|
||||
// MSVC compiler_rt is missing some stuff, so we build it unconditionally but
|
||||
// and rely on weak linkage to allow MSVC compiler_rt functions to override ours.
|
||||
if (comp.compiler_rt_lib) |lib| {
|
||||
try argv.append(lib.full_object_path);
|
||||
}
|
||||
}
|
||||
|
||||
try argv.ensureUnusedCapacity(self.base.options.system_libs.count());
|
||||
for (self.base.options.system_libs.keys()) |key| {
|
||||
const lib_basename = try allocPrint(arena, "{s}.lib", .{key});
|
||||
if (comp.crt_files.get(lib_basename)) |crt_file| {
|
||||
argv.appendAssumeCapacity(crt_file.full_object_path);
|
||||
continue;
|
||||
}
|
||||
if (try self.findLib(arena, lib_basename)) |full_path| {
|
||||
argv.appendAssumeCapacity(full_path);
|
||||
continue;
|
||||
}
|
||||
if (target.abi.isGnu()) {
|
||||
const fallback_name = try allocPrint(arena, "lib{s}.dll.a", .{key});
|
||||
if (try self.findLib(arena, fallback_name)) |full_path| {
|
||||
argv.appendAssumeCapacity(full_path);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
log.err("DLL import library for -l{s} not found", .{key});
|
||||
return error.DllImportLibraryNotFound;
|
||||
}
|
||||
|
||||
if (self.base.options.verbose_link) {
|
||||
// Skip over our own name so that the LLD linker name is the first argv item.
|
||||
Compilation.dump_argv(argv.items[1..]);
|
||||
}
|
||||
|
||||
if (std.process.can_spawn) {
|
||||
// If possible, we run LLD as a child process because it does not always
|
||||
// behave properly as a library, unfortunately.
|
||||
// https://github.com/ziglang/zig/issues/3825
|
||||
var child = std.ChildProcess.init(argv.items, arena);
|
||||
if (comp.clang_passthrough_mode) {
|
||||
child.stdin_behavior = .Inherit;
|
||||
child.stdout_behavior = .Inherit;
|
||||
child.stderr_behavior = .Inherit;
|
||||
|
||||
const term = child.spawnAndWait() catch |err| {
|
||||
log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
|
||||
return error.UnableToSpawnSelf;
|
||||
};
|
||||
switch (term) {
|
||||
.Exited => |code| {
|
||||
if (code != 0) {
|
||||
std.process.exit(code);
|
||||
}
|
||||
},
|
||||
else => std.process.abort(),
|
||||
}
|
||||
} else {
|
||||
child.stdin_behavior = .Ignore;
|
||||
child.stdout_behavior = .Ignore;
|
||||
child.stderr_behavior = .Pipe;
|
||||
|
||||
try child.spawn();
|
||||
|
||||
const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024);
|
||||
|
||||
const term = child.wait() catch |err| {
|
||||
log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
|
||||
return error.UnableToSpawnSelf;
|
||||
};
|
||||
|
||||
switch (term) {
|
||||
.Exited => |code| {
|
||||
if (code != 0) {
|
||||
// TODO parse this output and surface with the Compilation API rather than
|
||||
// directly outputting to stderr here.
|
||||
std.debug.print("{s}", .{stderr});
|
||||
return error.LLDReportedFailure;
|
||||
}
|
||||
},
|
||||
else => {
|
||||
log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr });
|
||||
return error.LLDCrashed;
|
||||
},
|
||||
}
|
||||
|
||||
if (stderr.len != 0) {
|
||||
log.warn("unexpected LLD stderr:\n{s}", .{stderr});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const exit_code = try lldMain(arena, argv.items, false);
|
||||
if (exit_code != 0) {
|
||||
if (comp.clang_passthrough_mode) {
|
||||
std.process.exit(exit_code);
|
||||
} else {
|
||||
return error.LLDReportedFailure;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!self.base.options.disable_lld_caching) {
|
||||
// Update the file with the digest. If it fails we can continue; it only
|
||||
// means that the next invocation will have an unnecessary cache miss.
|
||||
Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| {
|
||||
log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)});
|
||||
};
|
||||
// Again failure here only means an unnecessary cache miss.
|
||||
man.writeManifest() catch |err| {
|
||||
log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)});
|
||||
};
|
||||
// We hang on to this lock so that the output file path can be used without
|
||||
// other processes clobbering it.
|
||||
self.base.lock = man.toOwnedLock();
|
||||
}
|
||||
}
|
||||
|
||||
fn findLib(self: *Coff, arena: Allocator, name: []const u8) !?[]const u8 {
|
||||
for (self.base.options.lib_dirs) |lib_dir| {
|
||||
const full_path = try fs.path.join(arena, &.{ lib_dir, name });
|
||||
fs.cwd().access(full_path, .{}) catch |err| switch (err) {
|
||||
error.FileNotFound => continue,
|
||||
else => |e| return e,
|
||||
};
|
||||
return full_path;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn getDeclVAddr(
|
||||
self: *Coff,
|
||||
decl_index: Module.Decl.Index,
|
||||
reloc_info: link.File.RelocInfo,
|
||||
) !u64 {
|
||||
_ = self;
|
||||
_ = decl_index;
|
||||
_ = reloc_info;
|
||||
const mod = self.base.options.module.?;
|
||||
const decl = mod.declPtr(decl_index);
|
||||
assert(self.llvm_object == null);
|
||||
return self.text_section_virtual_address + decl.link.coff.text_offset;
|
||||
@panic("TODO getDeclVAddr");
|
||||
}
|
||||
|
||||
pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !void {
|
||||
_ = self;
|
||||
_ = module;
|
||||
_ = decl;
|
||||
// TODO Implement this
|
||||
log.debug("TODO implement updateDeclLineNumber", .{});
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Coff) void {
|
||||
if (build_options.have_llvm) {
|
||||
if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator);
|
||||
fn writeStrtab(self: *Coff) !void {
|
||||
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.text_block_free_list.deinit(self.base.allocator);
|
||||
self.offset_table.deinit(self.base.allocator);
|
||||
self.offset_table_free_list.deinit(self.base.allocator);
|
||||
log.debug("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.?);
|
||||
}
|
||||
|
||||
fn writeSectionHeaders(self: *Coff) !void {
|
||||
const offset = self.getSectionHeadersOffset();
|
||||
try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items(.header)), offset);
|
||||
}
|
||||
|
||||
fn writeDataDirectoriesHeaders(self: *Coff) !void {
|
||||
const offset = self.getDataDirectoryHeadersOffset();
|
||||
try self.base.file.?.pwriteAll(mem.sliceAsBytes(&self.data_directories), offset);
|
||||
}
|
||||
|
||||
fn writeHeader(self: *Coff) !void {
|
||||
const gpa = self.base.allocator;
|
||||
var buffer = std.ArrayList(u8).init(gpa);
|
||||
defer buffer.deinit();
|
||||
const writer = buffer.writer();
|
||||
|
||||
try buffer.ensureTotalCapacity(self.getSizeOfHeaders());
|
||||
writer.writeAll(msdos_stub) catch unreachable;
|
||||
mem.writeIntLittle(u32, buffer.items[0x3c..][0..4], msdos_stub.len);
|
||||
|
||||
writer.writeAll("PE\x00\x00") catch unreachable;
|
||||
var flags = coff.CoffHeaderFlags{
|
||||
.EXECUTABLE_IMAGE = 1,
|
||||
.DEBUG_STRIPPED = 1, // TODO
|
||||
};
|
||||
switch (self.ptr_width) {
|
||||
.p32 => flags.@"32BIT_MACHINE" = 1,
|
||||
.p64 => flags.LARGE_ADDRESS_AWARE = 1,
|
||||
}
|
||||
if (self.base.options.output_mode == .Lib and self.base.options.link_mode == .Dynamic) {
|
||||
flags.DLL = 1;
|
||||
}
|
||||
|
||||
const timestamp = std.time.timestamp();
|
||||
const size_of_optional_header = @intCast(u16, self.getOptionalHeaderSize() + self.getDataDirectoryHeadersSize());
|
||||
var coff_header = coff.CoffHeader{
|
||||
.machine = coff.MachineType.fromTargetCpuArch(self.base.options.target.cpu.arch),
|
||||
.number_of_sections = @intCast(u16, self.sections.slice().len), // TODO what if we prune a section
|
||||
.time_date_stamp = @truncate(u32, @bitCast(u64, timestamp)),
|
||||
.pointer_to_symbol_table = self.strtab_offset orelse 0,
|
||||
.number_of_symbols = 0,
|
||||
.size_of_optional_header = size_of_optional_header,
|
||||
.flags = flags,
|
||||
};
|
||||
|
||||
writer.writeAll(mem.asBytes(&coff_header)) catch unreachable;
|
||||
|
||||
const dll_flags: coff.DllFlags = .{
|
||||
.HIGH_ENTROPY_VA = 0, //@boolToInt(self.base.options.pie),
|
||||
.DYNAMIC_BASE = 0,
|
||||
.TERMINAL_SERVER_AWARE = 1, // We are not a legacy app
|
||||
.NX_COMPAT = 1, // We are compatible with Data Execution Prevention
|
||||
};
|
||||
const subsystem: coff.Subsystem = .WINDOWS_CUI;
|
||||
const size_of_image: u32 = self.getSizeOfImage();
|
||||
const size_of_headers: u32 = mem.alignForwardGeneric(u32, self.getSizeOfHeaders(), default_file_alignment);
|
||||
const image_base = self.base.options.image_base_override orelse switch (self.base.options.output_mode) {
|
||||
.Exe => default_image_base_exe,
|
||||
.Lib => default_image_base_dll,
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
const base_of_code = self.sections.get(self.text_section_index.?).header.virtual_address;
|
||||
const base_of_data = self.sections.get(self.data_section_index.?).header.virtual_address;
|
||||
|
||||
var size_of_code: u32 = 0;
|
||||
var size_of_initialized_data: u32 = 0;
|
||||
var size_of_uninitialized_data: u32 = 0;
|
||||
for (self.sections.items(.header)) |header| {
|
||||
if (header.flags.CNT_CODE == 1) {
|
||||
size_of_code += header.size_of_raw_data;
|
||||
}
|
||||
if (header.flags.CNT_INITIALIZED_DATA == 1) {
|
||||
size_of_initialized_data += header.size_of_raw_data;
|
||||
}
|
||||
if (header.flags.CNT_UNINITIALIZED_DATA == 1) {
|
||||
size_of_uninitialized_data += header.size_of_raw_data;
|
||||
}
|
||||
}
|
||||
|
||||
switch (self.ptr_width) {
|
||||
.p32 => {
|
||||
var opt_header = coff.OptionalHeaderPE32{
|
||||
.magic = coff.IMAGE_NT_OPTIONAL_HDR32_MAGIC,
|
||||
.major_linker_version = 0,
|
||||
.minor_linker_version = 0,
|
||||
.size_of_code = size_of_code,
|
||||
.size_of_initialized_data = size_of_initialized_data,
|
||||
.size_of_uninitialized_data = size_of_uninitialized_data,
|
||||
.address_of_entry_point = self.entry_addr orelse 0,
|
||||
.base_of_code = base_of_code,
|
||||
.base_of_data = base_of_data,
|
||||
.image_base = @intCast(u32, image_base),
|
||||
.section_alignment = self.page_size,
|
||||
.file_alignment = default_file_alignment,
|
||||
.major_operating_system_version = 6,
|
||||
.minor_operating_system_version = 0,
|
||||
.major_image_version = 0,
|
||||
.minor_image_version = 0,
|
||||
.major_subsystem_version = 6,
|
||||
.minor_subsystem_version = 0,
|
||||
.win32_version_value = 0,
|
||||
.size_of_image = size_of_image,
|
||||
.size_of_headers = size_of_headers,
|
||||
.checksum = 0,
|
||||
.subsystem = subsystem,
|
||||
.dll_flags = dll_flags,
|
||||
.size_of_stack_reserve = default_size_of_stack_reserve,
|
||||
.size_of_stack_commit = default_size_of_stack_commit,
|
||||
.size_of_heap_reserve = default_size_of_heap_reserve,
|
||||
.size_of_heap_commit = default_size_of_heap_commit,
|
||||
.loader_flags = 0,
|
||||
.number_of_rva_and_sizes = @intCast(u32, self.data_directories.len),
|
||||
};
|
||||
writer.writeAll(mem.asBytes(&opt_header)) catch unreachable;
|
||||
},
|
||||
.p64 => {
|
||||
var opt_header = coff.OptionalHeaderPE64{
|
||||
.magic = coff.IMAGE_NT_OPTIONAL_HDR64_MAGIC,
|
||||
.major_linker_version = 0,
|
||||
.minor_linker_version = 0,
|
||||
.size_of_code = size_of_code,
|
||||
.size_of_initialized_data = size_of_initialized_data,
|
||||
.size_of_uninitialized_data = size_of_uninitialized_data,
|
||||
.address_of_entry_point = self.entry_addr orelse 0,
|
||||
.base_of_code = base_of_code,
|
||||
.image_base = image_base,
|
||||
.section_alignment = self.page_size,
|
||||
.file_alignment = default_file_alignment,
|
||||
.major_operating_system_version = 6,
|
||||
.minor_operating_system_version = 0,
|
||||
.major_image_version = 0,
|
||||
.minor_image_version = 0,
|
||||
.major_subsystem_version = 6,
|
||||
.minor_subsystem_version = 0,
|
||||
.win32_version_value = 0,
|
||||
.size_of_image = size_of_image,
|
||||
.size_of_headers = size_of_headers,
|
||||
.checksum = 0,
|
||||
.subsystem = subsystem,
|
||||
.dll_flags = dll_flags,
|
||||
.size_of_stack_reserve = default_size_of_stack_reserve,
|
||||
.size_of_stack_commit = default_size_of_stack_commit,
|
||||
.size_of_heap_reserve = default_size_of_heap_reserve,
|
||||
.size_of_heap_commit = default_size_of_heap_commit,
|
||||
.loader_flags = 0,
|
||||
.number_of_rva_and_sizes = @intCast(u32, self.data_directories.len),
|
||||
};
|
||||
writer.writeAll(mem.asBytes(&opt_header)) catch unreachable;
|
||||
},
|
||||
}
|
||||
|
||||
try self.base.file.?.pwriteAll(buffer.items, 0);
|
||||
}
|
||||
|
||||
pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) {
|
||||
// TODO https://github.com/ziglang/zig/issues/1284
|
||||
return math.add(@TypeOf(actual_size), actual_size, actual_size / ideal_factor) catch
|
||||
math.maxInt(@TypeOf(actual_size));
|
||||
}
|
||||
|
||||
fn detectAllocCollision(self: *Coff, start: u32, size: u32) ?u32 {
|
||||
const headers_size = self.getSizeOfHeaders();
|
||||
if (start < headers_size)
|
||||
return headers_size;
|
||||
|
||||
const end = start + size;
|
||||
|
||||
if (self.strtab_offset) |off| {
|
||||
const increased_size = @intCast(u32, self.strtab.len());
|
||||
const test_end = off + increased_size;
|
||||
if (end > off and start < test_end) {
|
||||
return test_end;
|
||||
}
|
||||
}
|
||||
|
||||
for (self.sections.items(.header)) |header| {
|
||||
const increased_size = header.size_of_raw_data;
|
||||
const test_end = header.pointer_to_raw_data + increased_size;
|
||||
if (end > header.pointer_to_raw_data and start < test_end) {
|
||||
return test_end;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn allocatedSize(self: *Coff, start: u32) u32 {
|
||||
if (start == 0)
|
||||
return 0;
|
||||
var min_pos: u32 = std.math.maxInt(u32);
|
||||
if (self.strtab_offset) |off| {
|
||||
if (off > start and off < min_pos) min_pos = off;
|
||||
}
|
||||
for (self.sections.items(.header)) |header| {
|
||||
if (header.pointer_to_raw_data <= start) continue;
|
||||
if (header.pointer_to_raw_data < min_pos) min_pos = header.pointer_to_raw_data;
|
||||
}
|
||||
return min_pos - start;
|
||||
}
|
||||
|
||||
pub fn findFreeSpace(self: *Coff, object_size: u32, min_alignment: u32) u32 {
|
||||
var start: u32 = 0;
|
||||
while (self.detectAllocCollision(start, object_size)) |item_end| {
|
||||
start = mem.alignForwardGeneric(u32, item_end, min_alignment);
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
inline fn getSizeOfHeaders(self: Coff) u32 {
|
||||
const msdos_hdr_size = msdos_stub.len + 4;
|
||||
return @intCast(u32, msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize() +
|
||||
self.getDataDirectoryHeadersSize() + self.getSectionHeadersSize());
|
||||
}
|
||||
|
||||
inline fn getOptionalHeaderSize(self: Coff) u32 {
|
||||
return switch (self.ptr_width) {
|
||||
.p32 => @intCast(u32, @sizeOf(coff.OptionalHeaderPE32)),
|
||||
.p64 => @intCast(u32, @sizeOf(coff.OptionalHeaderPE64)),
|
||||
};
|
||||
}
|
||||
|
||||
inline fn getDataDirectoryHeadersSize(self: Coff) u32 {
|
||||
return @intCast(u32, self.data_directories.len * @sizeOf(coff.ImageDataDirectory));
|
||||
}
|
||||
|
||||
inline fn getSectionHeadersSize(self: Coff) u32 {
|
||||
return @intCast(u32, self.sections.slice().len * @sizeOf(coff.SectionHeader));
|
||||
}
|
||||
|
||||
inline fn getDataDirectoryHeadersOffset(self: Coff) u32 {
|
||||
const msdos_hdr_size = msdos_stub.len + 4;
|
||||
return @intCast(u32, msdos_hdr_size + @sizeOf(coff.CoffHeader) + self.getOptionalHeaderSize());
|
||||
}
|
||||
|
||||
inline fn getSectionHeadersOffset(self: Coff) u32 {
|
||||
return self.getDataDirectoryHeadersOffset() + self.getDataDirectoryHeadersSize();
|
||||
}
|
||||
|
||||
inline fn getSizeOfImage(self: Coff) u32 {
|
||||
var image_size: u32 = mem.alignForwardGeneric(u32, self.getSizeOfHeaders(), self.page_size);
|
||||
for (self.sections.items(.header)) |header| {
|
||||
image_size += mem.alignForwardGeneric(u32, header.virtual_size, self.page_size);
|
||||
}
|
||||
return image_size;
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
/// Returns pointer-to-symbol described by `sym_with_loc` descriptor.
|
||||
pub fn getSymbolPtr(self: *Coff, sym_loc: SymbolWithLoc) *coff.Symbol {
|
||||
assert(sym_loc.file == null); // TODO linking object files
|
||||
return &self.locals.items[sym_loc.sym_index];
|
||||
}
|
||||
|
||||
/// Returns symbol described by `sym_with_loc` descriptor.
|
||||
pub fn getSymbol(self: *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.
|
||||
pub fn getSymbolName(self: *const Coff, sym_loc: SymbolWithLoc) []const u8 {
|
||||
assert(sym_loc.file == null); // TODO linking object files
|
||||
const sym = self.getSymbol(sym_loc);
|
||||
const offset = sym.getNameOffset() orelse return sym.getName().?;
|
||||
return self.strtab.get(offset).?;
|
||||
}
|
||||
|
||||
/// Returns atom if there is an atom referenced by the symbol described by `sym_with_loc` descriptor.
|
||||
/// Returns null on failure.
|
||||
pub fn getAtomForSymbol(self: *Coff, sym_loc: SymbolWithLoc) ?*Atom {
|
||||
assert(sym_loc.file == null); // TODO linking with object files
|
||||
return self.atom_by_index_table.get(sym_loc.sym_index);
|
||||
}
|
||||
|
||||
/// Returns GOT atom that references `sym_with_loc` if one exists.
|
||||
/// Returns null otherwise.
|
||||
pub fn getGotAtomForSymbol(self: *Coff, sym_loc: SymbolWithLoc) ?*Atom {
|
||||
const got_index = self.got_entries.get(sym_loc) orelse return null;
|
||||
return self.atom_by_index_table.get(got_index);
|
||||
}
|
||||
|
||||
fn setSectionName(self: *Coff, header: *coff.SectionHeader, name: []const u8) !void {
|
||||
if (name.len <= 8) {
|
||||
mem.copy(u8, &header.name, name);
|
||||
mem.set(u8, header.name[name.len..], 0);
|
||||
return;
|
||||
}
|
||||
const offset = try self.strtab.insert(self.base.allocator, name);
|
||||
const name_offset = fmt.bufPrint(&header.name, "/{d}", .{offset}) catch unreachable;
|
||||
mem.set(u8, header.name[name_offset.len..], 0);
|
||||
}
|
||||
|
||||
fn setSymbolName(self: *Coff, symbol: *coff.Symbol, name: []const u8) !void {
|
||||
if (name.len <= 8) {
|
||||
mem.copy(u8, &symbol.name, name);
|
||||
mem.set(u8, symbol.name[name.len..], 0);
|
||||
return;
|
||||
}
|
||||
const offset = try self.strtab.insert(self.base.allocator, name);
|
||||
mem.set(u8, symbol.name[0..4], 0);
|
||||
mem.writeIntLittle(u32, symbol.name[4..8], offset);
|
||||
}
|
||||
|
||||
fn logSymAttributes(sym: *const coff.Symbol, buf: *[4]u8) []const u8 {
|
||||
mem.set(u8, buf[0..4], '_');
|
||||
switch (sym.section_number) {
|
||||
.UNDEFINED => {
|
||||
buf[3] = 'u';
|
||||
switch (sym.storage_class) {
|
||||
.EXTERNAL => buf[1] = 'e',
|
||||
.WEAK_EXTERNAL => buf[1] = 'w',
|
||||
.NULL => {},
|
||||
else => unreachable,
|
||||
}
|
||||
},
|
||||
.ABSOLUTE => unreachable, // handle ABSOLUTE
|
||||
.DEBUG => unreachable,
|
||||
else => {
|
||||
buf[0] = 's';
|
||||
switch (sym.storage_class) {
|
||||
.EXTERNAL => buf[1] = 'e',
|
||||
.WEAK_EXTERNAL => buf[1] = 'w',
|
||||
.NULL => {},
|
||||
else => unreachable,
|
||||
}
|
||||
},
|
||||
}
|
||||
return buf[0..];
|
||||
}
|
||||
|
||||
fn logSymtab(self: *Coff) void {
|
||||
var buf: [4]u8 = undefined;
|
||||
|
||||
log.debug("symtab:", .{});
|
||||
log.debug(" object(null)", .{});
|
||||
for (self.locals.items) |*sym, sym_id| {
|
||||
const where = if (sym.section_number == .UNDEFINED) "ord" else "sect";
|
||||
const def_index: u16 = switch (sym.section_number) {
|
||||
.UNDEFINED => 0, // TODO
|
||||
.ABSOLUTE => unreachable, // TODO
|
||||
.DEBUG => unreachable, // TODO
|
||||
else => @enumToInt(sym.section_number),
|
||||
};
|
||||
log.debug(" %{d}: {?s} @{x} in {s}({d}), {s}", .{
|
||||
sym_id,
|
||||
self.getSymbolName(.{ .sym_index = @intCast(u32, sym_id), .file = null }),
|
||||
sym.value,
|
||||
where,
|
||||
def_index,
|
||||
logSymAttributes(sym, &buf),
|
||||
});
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
log.debug("GOT entries:", .{});
|
||||
for (self.got_entries.keys()) |target, i| {
|
||||
const got_sym = self.getSymbol(.{ .sym_index = self.got_entries.values()[i], .file = null });
|
||||
const target_sym = self.getSymbol(target);
|
||||
if (target_sym.section_number == .UNDEFINED) {
|
||||
log.debug(" {d}@{x} => import('{s}')", .{
|
||||
i,
|
||||
got_sym.value,
|
||||
self.getSymbolName(target),
|
||||
});
|
||||
} else {
|
||||
log.debug(" {d}@{x} => local(%{d}) in object({?d}) {s}", .{
|
||||
i,
|
||||
got_sym.value,
|
||||
target.sym_index,
|
||||
target.file,
|
||||
logSymAttributes(target_sym, &buf),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
const Atom = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const coff = std.coff;
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const Coff = @import("../Coff.zig");
|
||||
const Reloc = Coff.Reloc;
|
||||
const SymbolWithLoc = Coff.SymbolWithLoc;
|
||||
|
||||
/// Each decl always gets a local symbol with the fully qualified name.
|
||||
/// The vaddr and size are found here directly.
|
||||
/// The file offset is found by computing the vaddr offset from the section vaddr
|
||||
/// the symbol references, and adding that to the file offset of the section.
|
||||
/// If this field is 0, it means the codegen size = 0 and there is no symbol or
|
||||
/// offset table entry.
|
||||
sym_index: u32,
|
||||
|
||||
/// null means symbol defined by Zig source.
|
||||
file: ?u32,
|
||||
|
||||
/// Used size of the atom
|
||||
size: u32,
|
||||
|
||||
/// Alignment of the atom
|
||||
alignment: u32,
|
||||
|
||||
/// Points to the previous and next neighbors, based on the `text_offset`.
|
||||
/// This can be used to find, for example, the capacity of this `Atom`.
|
||||
prev: ?*Atom,
|
||||
next: ?*Atom,
|
||||
|
||||
pub const empty = Atom{
|
||||
.sym_index = 0,
|
||||
.file = null,
|
||||
.size = 0,
|
||||
.alignment = 0,
|
||||
.prev = null,
|
||||
.next = null,
|
||||
};
|
||||
|
||||
pub fn deinit(self: *Atom, gpa: Allocator) void {
|
||||
_ = self;
|
||||
_ = gpa;
|
||||
}
|
||||
|
||||
/// Returns symbol referencing this atom.
|
||||
pub fn getSymbol(self: Atom, coff_file: *const Coff) *const coff.Symbol {
|
||||
return coff_file.getSymbol(.{
|
||||
.sym_index = self.sym_index,
|
||||
.file = self.file,
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns pointer-to-symbol referencing this atom.
|
||||
pub fn getSymbolPtr(self: Atom, coff_file: *Coff) *coff.Symbol {
|
||||
return coff_file.getSymbolPtr(.{
|
||||
.sym_index = self.sym_index,
|
||||
.file = self.file,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn getSymbolWithLoc(self: Atom) SymbolWithLoc {
|
||||
return .{ .sym_index = self.sym_index, .file = self.file };
|
||||
}
|
||||
|
||||
/// Returns the name of this atom.
|
||||
pub fn getName(self: Atom, coff_file: *const Coff) []const u8 {
|
||||
return coff_file.getSymbolName(.{
|
||||
.sym_index = self.sym_index,
|
||||
.file = self.file,
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns how much room there is to grow in virtual address space.
|
||||
pub fn capacity(self: Atom, coff_file: *const Coff) u32 {
|
||||
const self_sym = self.getSymbol(coff_file);
|
||||
if (self.next) |next| {
|
||||
const next_sym = next.getSymbol(coff_file);
|
||||
return next_sym.value - self_sym.value;
|
||||
} else {
|
||||
// We are the last atom.
|
||||
// The capacity is limited only by virtual address space.
|
||||
return std.math.maxInt(u32) - self_sym.value;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn freeListEligible(self: Atom, coff_file: *const Coff) bool {
|
||||
// No need to keep a free list node for the last atom.
|
||||
const next = self.next orelse return false;
|
||||
const self_sym = self.getSymbol(coff_file);
|
||||
const next_sym = next.getSymbol(coff_file);
|
||||
const cap = next_sym.value - self_sym.value;
|
||||
const ideal_cap = Coff.padToIdeal(self.size);
|
||||
if (cap <= ideal_cap) return false;
|
||||
const surplus = cap - ideal_cap;
|
||||
return surplus >= Coff.min_text_capacity;
|
||||
}
|
||||
|
||||
pub fn addRelocation(self: *Atom, coff_file: *Coff, reloc: Reloc) !void {
|
||||
const gpa = coff_file.base.allocator;
|
||||
// TODO causes a segfault on Windows
|
||||
// log.debug("adding reloc of type {s} to target %{d}", .{ @tagName(reloc.@"type"), reloc.target.sym_index });
|
||||
const gop = try coff_file.relocs.getOrPut(gpa, self);
|
||||
if (!gop.found_existing) {
|
||||
gop.value_ptr.* = .{};
|
||||
}
|
||||
try gop.value_ptr.append(gpa, reloc);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
const Object = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const mem = std.mem;
|
||||
|
||||
const Allocator = mem.Allocator;
|
||||
|
||||
name: []const u8,
|
||||
|
||||
pub fn deinit(self: *Object, gpa: Allocator) void {
|
||||
gpa.free(self.name);
|
||||
}
|
||||
@@ -0,0 +1,602 @@
|
||||
const std = @import("std");
|
||||
const build_options = @import("build_options");
|
||||
const allocPrint = std.fmt.allocPrint;
|
||||
const assert = std.debug.assert;
|
||||
const fs = std.fs;
|
||||
const log = std.log.scoped(.link);
|
||||
const mem = std.mem;
|
||||
|
||||
const mingw = @import("../../mingw.zig");
|
||||
const link = @import("../../link.zig");
|
||||
const lldMain = @import("../../main.zig").lldMain;
|
||||
const trace = @import("../../tracy.zig").trace;
|
||||
|
||||
const Allocator = mem.Allocator;
|
||||
|
||||
const Cache = @import("../../Cache.zig");
|
||||
const Coff = @import("../Coff.zig");
|
||||
const Compilation = @import("../../Compilation.zig");
|
||||
|
||||
pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
var arena_allocator = std.heap.ArenaAllocator.init(self.base.allocator);
|
||||
defer arena_allocator.deinit();
|
||||
const arena = arena_allocator.allocator();
|
||||
|
||||
const directory = self.base.options.emit.?.directory; // Just an alias to make it shorter to type.
|
||||
const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path});
|
||||
|
||||
// If there is no Zig code to compile, then we should skip flushing the output file because it
|
||||
// will not be part of the linker line anyway.
|
||||
const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: {
|
||||
const use_stage1 = build_options.have_stage1 and self.base.options.use_stage1;
|
||||
if (use_stage1) {
|
||||
const obj_basename = try std.zig.binNameAlloc(arena, .{
|
||||
.root_name = self.base.options.root_name,
|
||||
.target = self.base.options.target,
|
||||
.output_mode = .Obj,
|
||||
});
|
||||
switch (self.base.options.cache_mode) {
|
||||
.incremental => break :blk try module.zig_cache_artifact_directory.join(
|
||||
arena,
|
||||
&[_][]const u8{obj_basename},
|
||||
),
|
||||
.whole => break :blk try fs.path.join(arena, &.{
|
||||
fs.path.dirname(full_out_path).?, obj_basename,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
try self.flushModule(comp, prog_node);
|
||||
|
||||
if (fs.path.dirname(full_out_path)) |dirname| {
|
||||
break :blk try fs.path.join(arena, &.{ dirname, self.base.intermediary_basename.? });
|
||||
} else {
|
||||
break :blk self.base.intermediary_basename.?;
|
||||
}
|
||||
} else null;
|
||||
|
||||
var sub_prog_node = prog_node.start("LLD Link", 0);
|
||||
sub_prog_node.activate();
|
||||
sub_prog_node.context.refresh();
|
||||
defer sub_prog_node.end();
|
||||
|
||||
const is_lib = self.base.options.output_mode == .Lib;
|
||||
const is_dyn_lib = self.base.options.link_mode == .Dynamic and is_lib;
|
||||
const is_exe_or_dyn_lib = is_dyn_lib or self.base.options.output_mode == .Exe;
|
||||
const link_in_crt = self.base.options.link_libc and is_exe_or_dyn_lib;
|
||||
const target = self.base.options.target;
|
||||
|
||||
// See link/Elf.zig for comments on how this mechanism works.
|
||||
const id_symlink_basename = "lld.id";
|
||||
|
||||
var man: Cache.Manifest = undefined;
|
||||
defer if (!self.base.options.disable_lld_caching) man.deinit();
|
||||
|
||||
var digest: [Cache.hex_digest_len]u8 = undefined;
|
||||
|
||||
if (!self.base.options.disable_lld_caching) {
|
||||
man = comp.cache_parent.obtain();
|
||||
self.base.releaseLock();
|
||||
|
||||
comptime assert(Compilation.link_hash_implementation_version == 7);
|
||||
|
||||
for (self.base.options.objects) |obj| {
|
||||
_ = try man.addFile(obj.path, null);
|
||||
man.hash.add(obj.must_link);
|
||||
}
|
||||
for (comp.c_object_table.keys()) |key| {
|
||||
_ = try man.addFile(key.status.success.object_path, null);
|
||||
}
|
||||
try man.addOptionalFile(module_obj_path);
|
||||
man.hash.addOptionalBytes(self.base.options.entry);
|
||||
man.hash.addOptional(self.base.options.stack_size_override);
|
||||
man.hash.addOptional(self.base.options.image_base_override);
|
||||
man.hash.addListOfBytes(self.base.options.lib_dirs);
|
||||
man.hash.add(self.base.options.skip_linker_dependencies);
|
||||
if (self.base.options.link_libc) {
|
||||
man.hash.add(self.base.options.libc_installation != null);
|
||||
if (self.base.options.libc_installation) |libc_installation| {
|
||||
man.hash.addBytes(libc_installation.crt_dir.?);
|
||||
if (target.abi == .msvc) {
|
||||
man.hash.addBytes(libc_installation.msvc_lib_dir.?);
|
||||
man.hash.addBytes(libc_installation.kernel32_lib_dir.?);
|
||||
}
|
||||
}
|
||||
}
|
||||
link.hashAddSystemLibs(&man.hash, self.base.options.system_libs);
|
||||
man.hash.addListOfBytes(self.base.options.force_undefined_symbols.keys());
|
||||
man.hash.addOptional(self.base.options.subsystem);
|
||||
man.hash.add(self.base.options.is_test);
|
||||
man.hash.add(self.base.options.tsaware);
|
||||
man.hash.add(self.base.options.nxcompat);
|
||||
man.hash.add(self.base.options.dynamicbase);
|
||||
// strip does not need to go into the linker hash because it is part of the hash namespace
|
||||
man.hash.addOptional(self.base.options.major_subsystem_version);
|
||||
man.hash.addOptional(self.base.options.minor_subsystem_version);
|
||||
|
||||
// We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.
|
||||
_ = try man.hit();
|
||||
digest = man.final();
|
||||
var prev_digest_buf: [digest.len]u8 = undefined;
|
||||
const prev_digest: []u8 = Cache.readSmallFile(
|
||||
directory.handle,
|
||||
id_symlink_basename,
|
||||
&prev_digest_buf,
|
||||
) catch |err| blk: {
|
||||
log.debug("COFF LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) });
|
||||
// Handle this as a cache miss.
|
||||
break :blk prev_digest_buf[0..0];
|
||||
};
|
||||
if (mem.eql(u8, prev_digest, &digest)) {
|
||||
log.debug("COFF LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)});
|
||||
// Hot diggity dog! The output binary is already there.
|
||||
self.base.lock = man.toOwnedLock();
|
||||
return;
|
||||
}
|
||||
log.debug("COFF LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) });
|
||||
|
||||
// We are about to change the output file to be different, so we invalidate the build hash now.
|
||||
directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) {
|
||||
error.FileNotFound => {},
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
if (self.base.options.output_mode == .Obj) {
|
||||
// LLD's COFF driver does not support the equivalent of `-r` so we do a simple file copy
|
||||
// here. TODO: think carefully about how we can avoid this redundant operation when doing
|
||||
// build-obj. See also the corresponding TODO in linkAsArchive.
|
||||
const the_object_path = blk: {
|
||||
if (self.base.options.objects.len != 0)
|
||||
break :blk self.base.options.objects[0].path;
|
||||
|
||||
if (comp.c_object_table.count() != 0)
|
||||
break :blk comp.c_object_table.keys()[0].status.success.object_path;
|
||||
|
||||
if (module_obj_path) |p|
|
||||
break :blk p;
|
||||
|
||||
// TODO I think this is unreachable. Audit this situation when solving the above TODO
|
||||
// regarding eliding redundant object -> object transformations.
|
||||
return error.NoObjectsToLink;
|
||||
};
|
||||
// This can happen when using --enable-cache and using the stage1 backend. In this case
|
||||
// we can skip the file copy.
|
||||
if (!mem.eql(u8, the_object_path, full_out_path)) {
|
||||
try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{});
|
||||
}
|
||||
} else {
|
||||
// Create an LLD command line and invoke it.
|
||||
var argv = std.ArrayList([]const u8).init(self.base.allocator);
|
||||
defer argv.deinit();
|
||||
// We will invoke ourselves as a child process to gain access to LLD.
|
||||
// This is necessary because LLD does not behave properly as a library -
|
||||
// it calls exit() and does not reset all global data between invocations.
|
||||
try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, "lld-link" });
|
||||
|
||||
try argv.append("-ERRORLIMIT:0");
|
||||
try argv.append("-NOLOGO");
|
||||
if (!self.base.options.strip) {
|
||||
try argv.append("-DEBUG");
|
||||
}
|
||||
if (self.base.options.lto) {
|
||||
switch (self.base.options.optimize_mode) {
|
||||
.Debug => {},
|
||||
.ReleaseSmall => try argv.append("-OPT:lldlto=2"),
|
||||
.ReleaseFast, .ReleaseSafe => try argv.append("-OPT:lldlto=3"),
|
||||
}
|
||||
}
|
||||
if (self.base.options.output_mode == .Exe) {
|
||||
const stack_size = self.base.options.stack_size_override orelse 16777216;
|
||||
try argv.append(try allocPrint(arena, "-STACK:{d}", .{stack_size}));
|
||||
}
|
||||
if (self.base.options.image_base_override) |image_base| {
|
||||
try argv.append(try std.fmt.allocPrint(arena, "-BASE:{d}", .{image_base}));
|
||||
}
|
||||
|
||||
if (target.cpu.arch == .i386) {
|
||||
try argv.append("-MACHINE:X86");
|
||||
} else if (target.cpu.arch == .x86_64) {
|
||||
try argv.append("-MACHINE:X64");
|
||||
} else if (target.cpu.arch.isARM()) {
|
||||
if (target.cpu.arch.ptrBitWidth() == 32) {
|
||||
try argv.append("-MACHINE:ARM");
|
||||
} else {
|
||||
try argv.append("-MACHINE:ARM64");
|
||||
}
|
||||
}
|
||||
|
||||
for (self.base.options.force_undefined_symbols.keys()) |symbol| {
|
||||
try argv.append(try allocPrint(arena, "-INCLUDE:{s}", .{symbol}));
|
||||
}
|
||||
|
||||
if (is_dyn_lib) {
|
||||
try argv.append("-DLL");
|
||||
}
|
||||
|
||||
if (self.base.options.entry) |entry| {
|
||||
try argv.append(try allocPrint(arena, "-ENTRY:{s}", .{entry}));
|
||||
}
|
||||
|
||||
if (self.base.options.tsaware) {
|
||||
try argv.append("-tsaware");
|
||||
}
|
||||
if (self.base.options.nxcompat) {
|
||||
try argv.append("-nxcompat");
|
||||
}
|
||||
if (self.base.options.dynamicbase) {
|
||||
try argv.append("-dynamicbase");
|
||||
}
|
||||
|
||||
try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path}));
|
||||
|
||||
if (self.base.options.implib_emit) |emit| {
|
||||
const implib_out_path = try emit.directory.join(arena, &[_][]const u8{emit.sub_path});
|
||||
try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path}));
|
||||
}
|
||||
|
||||
if (self.base.options.link_libc) {
|
||||
if (self.base.options.libc_installation) |libc_installation| {
|
||||
try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.crt_dir.?}));
|
||||
|
||||
if (target.abi == .msvc) {
|
||||
try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.msvc_lib_dir.?}));
|
||||
try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.kernel32_lib_dir.?}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (self.base.options.lib_dirs) |lib_dir| {
|
||||
try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{lib_dir}));
|
||||
}
|
||||
|
||||
try argv.ensureUnusedCapacity(self.base.options.objects.len);
|
||||
for (self.base.options.objects) |obj| {
|
||||
if (obj.must_link) {
|
||||
argv.appendAssumeCapacity(try allocPrint(arena, "-WHOLEARCHIVE:{s}", .{obj.path}));
|
||||
} else {
|
||||
argv.appendAssumeCapacity(obj.path);
|
||||
}
|
||||
}
|
||||
|
||||
for (comp.c_object_table.keys()) |key| {
|
||||
try argv.append(key.status.success.object_path);
|
||||
}
|
||||
|
||||
if (module_obj_path) |p| {
|
||||
try argv.append(p);
|
||||
}
|
||||
|
||||
const resolved_subsystem: ?std.Target.SubSystem = blk: {
|
||||
if (self.base.options.subsystem) |explicit| break :blk explicit;
|
||||
switch (target.os.tag) {
|
||||
.windows => {
|
||||
if (self.base.options.module) |module| {
|
||||
if (module.stage1_flags.have_dllmain_crt_startup or is_dyn_lib)
|
||||
break :blk null;
|
||||
if (module.stage1_flags.have_c_main or self.base.options.is_test or
|
||||
module.stage1_flags.have_winmain_crt_startup or
|
||||
module.stage1_flags.have_wwinmain_crt_startup)
|
||||
{
|
||||
break :blk .Console;
|
||||
}
|
||||
if (module.stage1_flags.have_winmain or module.stage1_flags.have_wwinmain)
|
||||
break :blk .Windows;
|
||||
}
|
||||
},
|
||||
.uefi => break :blk .EfiApplication,
|
||||
else => {},
|
||||
}
|
||||
break :blk null;
|
||||
};
|
||||
|
||||
const Mode = enum { uefi, win32 };
|
||||
const mode: Mode = mode: {
|
||||
if (resolved_subsystem) |subsystem| {
|
||||
const subsystem_suffix = ss: {
|
||||
if (self.base.options.major_subsystem_version) |major| {
|
||||
if (self.base.options.minor_subsystem_version) |minor| {
|
||||
break :ss try allocPrint(arena, ",{d}.{d}", .{ major, minor });
|
||||
} else {
|
||||
break :ss try allocPrint(arena, ",{d}", .{major});
|
||||
}
|
||||
}
|
||||
break :ss "";
|
||||
};
|
||||
|
||||
switch (subsystem) {
|
||||
.Console => {
|
||||
try argv.append(try allocPrint(arena, "-SUBSYSTEM:console{s}", .{
|
||||
subsystem_suffix,
|
||||
}));
|
||||
break :mode .win32;
|
||||
},
|
||||
.EfiApplication => {
|
||||
try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_application{s}", .{
|
||||
subsystem_suffix,
|
||||
}));
|
||||
break :mode .uefi;
|
||||
},
|
||||
.EfiBootServiceDriver => {
|
||||
try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_boot_service_driver{s}", .{
|
||||
subsystem_suffix,
|
||||
}));
|
||||
break :mode .uefi;
|
||||
},
|
||||
.EfiRom => {
|
||||
try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_rom{s}", .{
|
||||
subsystem_suffix,
|
||||
}));
|
||||
break :mode .uefi;
|
||||
},
|
||||
.EfiRuntimeDriver => {
|
||||
try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_runtime_driver{s}", .{
|
||||
subsystem_suffix,
|
||||
}));
|
||||
break :mode .uefi;
|
||||
},
|
||||
.Native => {
|
||||
try argv.append(try allocPrint(arena, "-SUBSYSTEM:native{s}", .{
|
||||
subsystem_suffix,
|
||||
}));
|
||||
break :mode .win32;
|
||||
},
|
||||
.Posix => {
|
||||
try argv.append(try allocPrint(arena, "-SUBSYSTEM:posix{s}", .{
|
||||
subsystem_suffix,
|
||||
}));
|
||||
break :mode .win32;
|
||||
},
|
||||
.Windows => {
|
||||
try argv.append(try allocPrint(arena, "-SUBSYSTEM:windows{s}", .{
|
||||
subsystem_suffix,
|
||||
}));
|
||||
break :mode .win32;
|
||||
},
|
||||
}
|
||||
} else if (target.os.tag == .uefi) {
|
||||
break :mode .uefi;
|
||||
} else {
|
||||
break :mode .win32;
|
||||
}
|
||||
};
|
||||
|
||||
switch (mode) {
|
||||
.uefi => try argv.appendSlice(&[_][]const u8{
|
||||
"-BASE:0",
|
||||
"-ENTRY:EfiMain",
|
||||
"-OPT:REF",
|
||||
"-SAFESEH:NO",
|
||||
"-MERGE:.rdata=.data",
|
||||
"-ALIGN:32",
|
||||
"-NODEFAULTLIB",
|
||||
"-SECTION:.xdata,D",
|
||||
}),
|
||||
.win32 => {
|
||||
if (link_in_crt) {
|
||||
if (target.abi.isGnu()) {
|
||||
try argv.append("-lldmingw");
|
||||
|
||||
if (target.cpu.arch == .i386) {
|
||||
try argv.append("-ALTERNATENAME:__image_base__=___ImageBase");
|
||||
} else {
|
||||
try argv.append("-ALTERNATENAME:__image_base__=__ImageBase");
|
||||
}
|
||||
|
||||
if (is_dyn_lib) {
|
||||
try argv.append(try comp.get_libc_crt_file(arena, "dllcrt2.obj"));
|
||||
if (target.cpu.arch == .i386) {
|
||||
try argv.append("-ALTERNATENAME:__DllMainCRTStartup@12=_DllMainCRTStartup@12");
|
||||
} else {
|
||||
try argv.append("-ALTERNATENAME:_DllMainCRTStartup=DllMainCRTStartup");
|
||||
}
|
||||
} else {
|
||||
try argv.append(try comp.get_libc_crt_file(arena, "crt2.obj"));
|
||||
}
|
||||
|
||||
try argv.append(try comp.get_libc_crt_file(arena, "mingw32.lib"));
|
||||
try argv.append(try comp.get_libc_crt_file(arena, "mingwex.lib"));
|
||||
try argv.append(try comp.get_libc_crt_file(arena, "msvcrt-os.lib"));
|
||||
|
||||
for (mingw.always_link_libs) |name| {
|
||||
if (!self.base.options.system_libs.contains(name)) {
|
||||
const lib_basename = try allocPrint(arena, "{s}.lib", .{name});
|
||||
try argv.append(try comp.get_libc_crt_file(arena, lib_basename));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const lib_str = switch (self.base.options.link_mode) {
|
||||
.Dynamic => "",
|
||||
.Static => "lib",
|
||||
};
|
||||
const d_str = switch (self.base.options.optimize_mode) {
|
||||
.Debug => "d",
|
||||
else => "",
|
||||
};
|
||||
switch (self.base.options.link_mode) {
|
||||
.Static => try argv.append(try allocPrint(arena, "libcmt{s}.lib", .{d_str})),
|
||||
.Dynamic => try argv.append(try allocPrint(arena, "msvcrt{s}.lib", .{d_str})),
|
||||
}
|
||||
|
||||
try argv.append(try allocPrint(arena, "{s}vcruntime{s}.lib", .{ lib_str, d_str }));
|
||||
try argv.append(try allocPrint(arena, "{s}ucrt{s}.lib", .{ lib_str, d_str }));
|
||||
|
||||
//Visual C++ 2015 Conformance Changes
|
||||
//https://msdn.microsoft.com/en-us/library/bb531344.aspx
|
||||
try argv.append("legacy_stdio_definitions.lib");
|
||||
|
||||
// msvcrt depends on kernel32 and ntdll
|
||||
try argv.append("kernel32.lib");
|
||||
try argv.append("ntdll.lib");
|
||||
}
|
||||
} else {
|
||||
try argv.append("-NODEFAULTLIB");
|
||||
if (!is_lib) {
|
||||
if (self.base.options.module) |module| {
|
||||
if (module.stage1_flags.have_winmain_crt_startup) {
|
||||
try argv.append("-ENTRY:WinMainCRTStartup");
|
||||
} else {
|
||||
try argv.append("-ENTRY:wWinMainCRTStartup");
|
||||
}
|
||||
} else {
|
||||
try argv.append("-ENTRY:wWinMainCRTStartup");
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// libc++ dep
|
||||
if (self.base.options.link_libcpp) {
|
||||
try argv.append(comp.libcxxabi_static_lib.?.full_object_path);
|
||||
try argv.append(comp.libcxx_static_lib.?.full_object_path);
|
||||
}
|
||||
|
||||
// libunwind dep
|
||||
if (self.base.options.link_libunwind) {
|
||||
try argv.append(comp.libunwind_static_lib.?.full_object_path);
|
||||
}
|
||||
|
||||
if (is_exe_or_dyn_lib and !self.base.options.skip_linker_dependencies) {
|
||||
if (!self.base.options.link_libc) {
|
||||
if (comp.libc_static_lib) |lib| {
|
||||
try argv.append(lib.full_object_path);
|
||||
}
|
||||
}
|
||||
// MinGW doesn't provide libssp symbols
|
||||
if (target.abi.isGnu()) {
|
||||
if (comp.libssp_static_lib) |lib| {
|
||||
try argv.append(lib.full_object_path);
|
||||
}
|
||||
}
|
||||
// MSVC compiler_rt is missing some stuff, so we build it unconditionally but
|
||||
// and rely on weak linkage to allow MSVC compiler_rt functions to override ours.
|
||||
if (comp.compiler_rt_lib) |lib| {
|
||||
try argv.append(lib.full_object_path);
|
||||
}
|
||||
}
|
||||
|
||||
try argv.ensureUnusedCapacity(self.base.options.system_libs.count());
|
||||
for (self.base.options.system_libs.keys()) |key| {
|
||||
const lib_basename = try allocPrint(arena, "{s}.lib", .{key});
|
||||
if (comp.crt_files.get(lib_basename)) |crt_file| {
|
||||
argv.appendAssumeCapacity(crt_file.full_object_path);
|
||||
continue;
|
||||
}
|
||||
if (try findLib(arena, lib_basename, self.base.options.lib_dirs)) |full_path| {
|
||||
argv.appendAssumeCapacity(full_path);
|
||||
continue;
|
||||
}
|
||||
if (target.abi.isGnu()) {
|
||||
const fallback_name = try allocPrint(arena, "lib{s}.dll.a", .{key});
|
||||
if (try findLib(arena, fallback_name, self.base.options.lib_dirs)) |full_path| {
|
||||
argv.appendAssumeCapacity(full_path);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
log.err("DLL import library for -l{s} not found", .{key});
|
||||
return error.DllImportLibraryNotFound;
|
||||
}
|
||||
|
||||
if (self.base.options.verbose_link) {
|
||||
// Skip over our own name so that the LLD linker name is the first argv item.
|
||||
Compilation.dump_argv(argv.items[1..]);
|
||||
}
|
||||
|
||||
if (std.process.can_spawn) {
|
||||
// If possible, we run LLD as a child process because it does not always
|
||||
// behave properly as a library, unfortunately.
|
||||
// https://github.com/ziglang/zig/issues/3825
|
||||
var child = std.ChildProcess.init(argv.items, arena);
|
||||
if (comp.clang_passthrough_mode) {
|
||||
child.stdin_behavior = .Inherit;
|
||||
child.stdout_behavior = .Inherit;
|
||||
child.stderr_behavior = .Inherit;
|
||||
|
||||
const term = child.spawnAndWait() catch |err| {
|
||||
log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
|
||||
return error.UnableToSpawnSelf;
|
||||
};
|
||||
switch (term) {
|
||||
.Exited => |code| {
|
||||
if (code != 0) {
|
||||
std.process.exit(code);
|
||||
}
|
||||
},
|
||||
else => std.process.abort(),
|
||||
}
|
||||
} else {
|
||||
child.stdin_behavior = .Ignore;
|
||||
child.stdout_behavior = .Ignore;
|
||||
child.stderr_behavior = .Pipe;
|
||||
|
||||
try child.spawn();
|
||||
|
||||
const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024);
|
||||
|
||||
const term = child.wait() catch |err| {
|
||||
log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) });
|
||||
return error.UnableToSpawnSelf;
|
||||
};
|
||||
|
||||
switch (term) {
|
||||
.Exited => |code| {
|
||||
if (code != 0) {
|
||||
// TODO parse this output and surface with the Compilation API rather than
|
||||
// directly outputting to stderr here.
|
||||
std.debug.print("{s}", .{stderr});
|
||||
return error.LLDReportedFailure;
|
||||
}
|
||||
},
|
||||
else => {
|
||||
log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr });
|
||||
return error.LLDCrashed;
|
||||
},
|
||||
}
|
||||
|
||||
if (stderr.len != 0) {
|
||||
log.warn("unexpected LLD stderr:\n{s}", .{stderr});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const exit_code = try lldMain(arena, argv.items, false);
|
||||
if (exit_code != 0) {
|
||||
if (comp.clang_passthrough_mode) {
|
||||
std.process.exit(exit_code);
|
||||
} else {
|
||||
return error.LLDReportedFailure;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!self.base.options.disable_lld_caching) {
|
||||
// Update the file with the digest. If it fails we can continue; it only
|
||||
// means that the next invocation will have an unnecessary cache miss.
|
||||
Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| {
|
||||
log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)});
|
||||
};
|
||||
// Again failure here only means an unnecessary cache miss.
|
||||
man.writeManifest() catch |err| {
|
||||
log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)});
|
||||
};
|
||||
// We hang on to this lock so that the output file path can be used without
|
||||
// other processes clobbering it.
|
||||
self.base.lock = man.toOwnedLock();
|
||||
}
|
||||
}
|
||||
|
||||
fn findLib(arena: Allocator, name: []const u8, lib_dirs: []const []const u8) !?[]const u8 {
|
||||
for (lib_dirs) |lib_dir| {
|
||||
const full_path = try fs.path.join(arena, &.{ lib_dir, name });
|
||||
fs.cwd().access(full_path, .{}) catch |err| switch (err) {
|
||||
error.FileNotFound => continue,
|
||||
else => |e| return e,
|
||||
};
|
||||
return full_path;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
+1
-2
@@ -26,7 +26,7 @@ const trace = @import("../tracy.zig").trace;
|
||||
const Air = @import("../Air.zig");
|
||||
const Allocator = mem.Allocator;
|
||||
const Archive = @import("MachO/Archive.zig");
|
||||
const Atom = @import("MachO/Atom.zig");
|
||||
pub const Atom = @import("MachO/Atom.zig");
|
||||
const Cache = @import("../Cache.zig");
|
||||
const CodeSignature = @import("MachO/CodeSignature.zig");
|
||||
const Compilation = @import("../Compilation.zig");
|
||||
@@ -44,7 +44,6 @@ const Type = @import("../type.zig").Type;
|
||||
const TypedValue = @import("../TypedValue.zig");
|
||||
const Value = @import("../value.zig").Value;
|
||||
|
||||
pub const TextBlock = Atom;
|
||||
pub const DebugSymbols = @import("MachO/DebugSymbols.zig");
|
||||
|
||||
pub const base_tag: File.Tag = File.Tag.macho;
|
||||
|
||||
@@ -18,7 +18,6 @@ const Dwarf = @import("../Dwarf.zig");
|
||||
const MachO = @import("../MachO.zig");
|
||||
const Module = @import("../../Module.zig");
|
||||
const StringTable = @import("../strtab.zig").StringTable;
|
||||
const TextBlock = MachO.TextBlock;
|
||||
const Type = @import("../../type.zig").Type;
|
||||
|
||||
base: *MachO,
|
||||
|
||||
@@ -109,5 +109,9 @@ pub fn StringTable(comptime log_scope: @Type(.EnumLiteral)) type {
|
||||
pub fn getAssumeExists(self: Self, off: u32) []const u8 {
|
||||
return self.get(off) orelse unreachable;
|
||||
}
|
||||
|
||||
pub fn len(self: Self) usize {
|
||||
return self.buffer.items.len;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
// output_mode=Exe
|
||||
// target=aarch64-macos
|
||||
//
|
||||
// :107:9: error: struct 'tmp.tmp' has no member named 'main'
|
||||
// :105:9: error: struct 'tmp.tmp' has no member named 'main'
|
||||
// :7:1: note: struct declared here
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
// output_mode=Exe
|
||||
// target=x86_64-linux
|
||||
//
|
||||
// :107:9: error: struct 'tmp.tmp' has no member named 'main'
|
||||
// :105:9: error: struct 'tmp.tmp' has no member named 'main'
|
||||
// :7:1: note: struct declared here
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
// output_mode=Exe
|
||||
// target=x86_64-macos
|
||||
//
|
||||
// :107:9: error: struct 'tmp.tmp' has no member named 'main'
|
||||
// :105:9: error: struct 'tmp.tmp' has no member named 'main'
|
||||
// :7:1: note: struct declared here
|
||||
|
||||
Reference in New Issue
Block a user