zig/src-self-hosted/link.zig
Andrew Kelley 193ad413f0 stage2: compiling C objects with clang
* add target_util.zig which has ported code from src/target.cpp
 * Module gains an arena that owns memory used during initialization
   that has the same lifetime as the Module. Useful for constructing
   file paths and lists of strings that have mixed lifetimes.
   - The Module memory itself is allocated in this arena. init/deinit
     are modified to be create/destroy.
   - root_name moves to the arena and no longer needs manual free
 * implement the ability to invoke `zig clang` as a subprocess
   - there are lots of TODOs that should be solved before merging
 * Module now requires a Random object and zig_lib_dir
 * Module now requires a path to its own executable or any zig
   executable that can do `zig clang`.
 * Wire up more CLI options.
 * Module creates "zig-cache" directory and "tmp" and "o" subdirectories
   ("h" is created by the cache_hash)
 * stubbed out some of the things linker code needs to do with TODO
   prints
 * delete dead code for computing compiler id. the previous commit
   eliminated the need for it.
 * add `zig translate-c` CLI option but it's not fully hooked up yet.
   It should be possible for this to be fully wired up before merging
   this branch.
 * `zig targets` now uses canonical data for available_libcs
2020-09-09 09:28:05 -07:00

304 lines
12 KiB
Zig

const std = @import("std");
const Allocator = std.mem.Allocator;
const Module = @import("Module.zig");
const fs = std.fs;
const trace = @import("tracy.zig").trace;
const Package = @import("Package.zig");
const Type = @import("type.zig").Type;
const build_options = @import("build_options");
pub const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version;
pub const Options = struct {
target: std.Target,
output_mode: std.builtin.OutputMode,
link_mode: std.builtin.LinkMode,
object_format: std.builtin.ObjectFormat,
optimize_mode: std.builtin.Mode,
root_name: []const u8,
root_pkg: ?*const Package,
/// Used for calculating how much space to reserve for symbols in case the binary file
/// does not already have a symbol table.
symbol_count_hint: u64 = 32,
/// Used for calculating how much space to reserve for executable program code in case
/// the binary file does not already have such a section.
program_code_size_hint: u64 = 256 * 1024,
entry_addr: ?u64 = null,
/// Set to `true` to omit debug info.
strip: bool = false,
/// If this is true then this link code is responsible for outputting an object
/// file and then using LLD to link it together with the link options and other objects.
/// Otherwise (depending on `use_llvm`) this link code directly outputs and updates the final binary.
use_lld: bool = false,
/// If this is true then this link code is responsible for making an LLVM IR Module,
/// outputting it to an object file, and then linking that together with link options and
/// other objects.
/// Otherwise (depending on `use_lld`) this link code directly outputs and updates the final binary.
use_llvm: bool = false,
link_libc: bool = false,
link_libcpp: bool = false,
function_sections: bool = false,
objects: []const []const u8 = &[0][]const u8{},
framework_dirs: []const []const u8 = &[0][]const u8{},
frameworks: []const []const u8 = &[0][]const u8{},
system_libs: []const []const u8 = &[0][]const u8{},
lib_dirs: []const []const u8 = &[0][]const u8{},
rpath_list: []const []const u8 = &[0][]const u8{},
pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode {
return if (options.use_lld) .Obj else options.output_mode;
}
};
pub const File = struct {
tag: Tag,
options: Options,
file: ?fs.File,
allocator: *Allocator,
pub const LinkBlock = union {
elf: Elf.TextBlock,
coff: Coff.TextBlock,
macho: MachO.TextBlock,
c: void,
wasm: void,
};
pub const LinkFn = union {
elf: Elf.SrcFn,
coff: Coff.SrcFn,
macho: MachO.SrcFn,
c: void,
wasm: ?Wasm.FnData,
};
/// For DWARF .debug_info.
pub const DbgInfoTypeRelocsTable = std.HashMapUnmanaged(Type, DbgInfoTypeReloc, Type.hash, Type.eql, std.hash_map.DefaultMaxLoadPercentage);
/// For DWARF .debug_info.
pub const DbgInfoTypeReloc = struct {
/// Offset from `TextBlock.dbg_info_off` (the buffer that is local to a Decl).
/// This is where the .debug_info tag for the type is.
off: u32,
/// Offset from `TextBlock.dbg_info_off` (the buffer that is local to a Decl).
/// List of DW.AT_type / DW.FORM_ref4 that points to the type.
relocs: std.ArrayListUnmanaged(u32),
};
/// Attempts incremental linking, if the file already exists. If
/// incremental linking fails, falls back to truncating the file and
/// rewriting it. A malicious file is detected as incremental link failure
/// and does not cause Illegal Behavior. This operation is not atomic.
pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: Options) !*File {
switch (options.object_format) {
.coff, .pe => return Coff.openPath(allocator, dir, sub_path, options),
.elf => return Elf.openPath(allocator, dir, sub_path, options),
.macho => return MachO.openPath(allocator, dir, sub_path, options),
.wasm => return Wasm.openPath(allocator, dir, sub_path, options),
.c => return C.openPath(allocator, dir, sub_path, options),
.hex => return error.HexObjectFormatUnimplemented,
.raw => return error.RawObjectFormatUnimplemented,
}
}
pub fn cast(base: *File, comptime T: type) ?*T {
if (base.tag != T.base_tag)
return null;
return @fieldParentPtr(T, "base", base);
}
pub fn makeWritable(base: *File, dir: fs.Dir, sub_path: []const u8) !void {
switch (base.tag) {
.coff, .elf, .macho => {
if (base.file != null) return;
base.file = try dir.createFile(sub_path, .{
.truncate = false,
.read = true,
.mode = determineMode(base.options),
});
},
.c, .wasm => {},
}
}
pub fn makeExecutable(base: *File) !void {
switch (base.tag) {
.c => unreachable,
.wasm => {},
else => if (base.file) |f| {
f.close();
base.file = null;
},
}
}
/// May be called before or after updateDeclExports but must be called
/// after allocateDeclIndexes for any given Decl.
pub fn updateDecl(base: *File, module: *Module, decl: *Module.Decl) !void {
switch (base.tag) {
.coff => return @fieldParentPtr(Coff, "base", base).updateDecl(module, decl),
.elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl),
.macho => return @fieldParentPtr(MachO, "base", base).updateDecl(module, decl),
.c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl),
.wasm => return @fieldParentPtr(Wasm, "base", base).updateDecl(module, decl),
}
}
pub fn updateDeclLineNumber(base: *File, module: *Module, decl: *Module.Decl) !void {
switch (base.tag) {
.coff => return @fieldParentPtr(Coff, "base", base).updateDeclLineNumber(module, decl),
.elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl),
.macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl),
.c, .wasm => {},
}
}
/// Must be called before any call to updateDecl or updateDeclExports for
/// any given Decl.
pub fn allocateDeclIndexes(base: *File, decl: *Module.Decl) !void {
switch (base.tag) {
.coff => return @fieldParentPtr(Coff, "base", base).allocateDeclIndexes(decl),
.elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
.macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl),
.c, .wasm => {},
}
}
pub fn deinit(base: *File) void {
if (base.file) |f| f.close();
switch (base.tag) {
.coff => @fieldParentPtr(Coff, "base", base).deinit(),
.elf => @fieldParentPtr(Elf, "base", base).deinit(),
.macho => @fieldParentPtr(MachO, "base", base).deinit(),
.c => @fieldParentPtr(C, "base", base).deinit(),
.wasm => @fieldParentPtr(Wasm, "base", base).deinit(),
}
}
pub fn destroy(base: *File) void {
switch (base.tag) {
.coff => {
const parent = @fieldParentPtr(Coff, "base", base);
parent.deinit();
base.allocator.destroy(parent);
},
.elf => {
const parent = @fieldParentPtr(Elf, "base", base);
parent.deinit();
base.allocator.destroy(parent);
},
.macho => {
const parent = @fieldParentPtr(MachO, "base", base);
parent.deinit();
base.allocator.destroy(parent);
},
.c => {
const parent = @fieldParentPtr(C, "base", base);
parent.deinit();
base.allocator.destroy(parent);
},
.wasm => {
const parent = @fieldParentPtr(Wasm, "base", base);
parent.deinit();
base.allocator.destroy(parent);
},
}
}
pub fn flush(base: *File, module: *Module) !void {
const tracy = trace(@src());
defer tracy.end();
try switch (base.tag) {
.coff => @fieldParentPtr(Coff, "base", base).flush(module),
.elf => @fieldParentPtr(Elf, "base", base).flush(module),
.macho => @fieldParentPtr(MachO, "base", base).flush(module),
.c => @fieldParentPtr(C, "base", base).flush(module),
.wasm => @fieldParentPtr(Wasm, "base", base).flush(module),
};
}
pub fn freeDecl(base: *File, decl: *Module.Decl) void {
switch (base.tag) {
.coff => @fieldParentPtr(Coff, "base", base).freeDecl(decl),
.elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl),
.macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl),
.c => unreachable,
.wasm => @fieldParentPtr(Wasm, "base", base).freeDecl(decl),
}
}
pub fn errorFlags(base: *File) ErrorFlags {
return switch (base.tag) {
.coff => @fieldParentPtr(Coff, "base", base).error_flags,
.elf => @fieldParentPtr(Elf, "base", base).error_flags,
.macho => @fieldParentPtr(MachO, "base", base).error_flags,
.c => return .{ .no_entry_point_found = false },
.wasm => return ErrorFlags{},
};
}
/// May be called before or after updateDecl, but must be called after
/// allocateDeclIndexes for any given Decl.
pub fn updateDeclExports(
base: *File,
module: *Module,
decl: *const Module.Decl,
exports: []const *Module.Export,
) !void {
switch (base.tag) {
.coff => return @fieldParentPtr(Coff, "base", base).updateDeclExports(module, decl, exports),
.elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports),
.macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl, exports),
.c => return {},
.wasm => return @fieldParentPtr(Wasm, "base", base).updateDeclExports(module, decl, exports),
}
}
pub fn getDeclVAddr(base: *File, decl: *const Module.Decl) u64 {
switch (base.tag) {
.coff => return @fieldParentPtr(Coff, "base", base).getDeclVAddr(decl),
.elf => return @fieldParentPtr(Elf, "base", base).getDeclVAddr(decl),
.macho => return @fieldParentPtr(MachO, "base", base).getDeclVAddr(decl),
.c => unreachable,
.wasm => unreachable,
}
}
pub const Tag = enum {
coff,
elf,
macho,
c,
wasm,
};
pub const ErrorFlags = struct {
no_entry_point_found: bool = false,
};
pub const C = @import("link/C.zig");
pub const Coff = @import("link/Coff.zig");
pub const Elf = @import("link/Elf.zig");
pub const MachO = @import("link/MachO.zig");
pub const Wasm = @import("link/Wasm.zig");
};
pub fn determineMode(options: Options) fs.File.Mode {
// On common systems with a 0o022 umask, 0o777 will still result in a file created
// with 0o755 permissions, but it works appropriately if the system is configured
// more leniently. As another data point, C's fopen seems to open files with the
// 666 mode.
const executable_mode = if (std.Target.current.os.tag == .windows) 0 else 0o777;
switch (options.output_mode) {
.Lib => return switch (options.link_mode) {
.Dynamic => executable_mode,
.Static => fs.File.default_mode,
},
.Exe => return executable_mode,
.Obj => return fs.File.default_mode,
}
}