Merge pull request #12677 from ziglang/coff-linker

coff: initial rewrite of the COFF/PE linker
This commit is contained in:
Jakub Konka
2022-08-30 14:29:41 +02:00
committed by GitHub
23 changed files with 2169 additions and 1314 deletions

View File

@@ -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"

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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 },

View File

@@ -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 },

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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", .{});
}
}

View File

@@ -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:

View File

@@ -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,

File diff suppressed because it is too large Load Diff

110
src/link/Coff/Atom.zig Normal file
View File

@@ -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);
}

12
src/link/Coff/Object.zig Normal file
View File

@@ -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);
}

602
src/link/Coff/lld.zig Normal file
View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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,

View File

@@ -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;
}
};
}

View File

@@ -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

View File

@@ -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

View File

@@ -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