mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 22:33:08 +00:00
* update to the new cache hash API
* std.Target defaultVersionRange moves to std.Target.Os.Tag
* std.Target.Os gains getVersionRange which returns a tagged union
* start the process of splitting Module into Compilation and "zig
module".
- The parts of Module having to do with only compiling zig code are
extracted into ZigModule.zig.
- Next step is to rename Module to Compilation.
- After that rename ZigModule back to Module.
* implement proper cache hash usage when compiling C objects, and
properly manage the file lock of the build artifacts.
* make versions optional to match recent changes to master branch.
* proper cache hash integration for compiling zig code
* proper cache hash integration for linking even when not compiling zig
code.
* ELF LLD linking integrates with the caching system. A comment from
the source code:
Here we want to determine whether we can save time by not invoking LLD when the
output is unchanged. None of the linker options or the object files that are being
linked are in the hash that namespaces the directory we are outputting to. Therefore,
we must hash those now, and the resulting digest will form the "id" of the linking
job we are about to perform.
After a successful link, we store the id in the metadata of a symlink named "id.txt" in
the artifact directory. So, now, we check if this symlink exists, and if it matches
our digest. If so, we can skip linking. Otherwise, we proceed with invoking LLD.
* implement disable_c_depfile option
* add tracy to a few more functions
1530 lines
59 KiB
Zig
1530 lines
59 KiB
Zig
//! TODO This is going to get renamed from Module to Compilation.
|
|
const Module = @This();
|
|
const Compilation = @This();
|
|
|
|
const std = @import("std");
|
|
const mem = std.mem;
|
|
const Allocator = std.mem.Allocator;
|
|
const Value = @import("value.zig").Value;
|
|
const assert = std.debug.assert;
|
|
const log = std.log.scoped(.compilation);
|
|
const Target = std.Target;
|
|
const target_util = @import("target.zig");
|
|
const Package = @import("Package.zig");
|
|
const link = @import("link.zig");
|
|
const trace = @import("tracy.zig").trace;
|
|
const liveness = @import("liveness.zig");
|
|
const build_options = @import("build_options");
|
|
const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
|
|
const glibc = @import("glibc.zig");
|
|
const fatal = @import("main.zig").fatal;
|
|
const ZigModule = @import("ZigModule.zig");
|
|
|
|
/// General-purpose allocator. Used for both temporary and long-term storage.
|
|
gpa: *Allocator,
|
|
/// Arena-allocated memory used during initialization. Should be untouched until deinit.
|
|
arena_state: std.heap.ArenaAllocator.State,
|
|
bin_file: *link.File,
|
|
c_object_table: std.AutoArrayHashMapUnmanaged(*CObject, void) = .{},
|
|
|
|
link_error_flags: link.File.ErrorFlags = .{},
|
|
|
|
work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic),
|
|
|
|
/// The ErrorMsg memory is owned by the `CObject`, using Module's general purpose allocator.
|
|
failed_c_objects: std.AutoArrayHashMapUnmanaged(*CObject, *ErrorMsg) = .{},
|
|
|
|
keep_source_files_loaded: bool,
|
|
use_clang: bool,
|
|
sanitize_c: bool,
|
|
/// When this is `true` it means invoking clang as a sub-process is expected to inherit
|
|
/// stdin, stdout, stderr, and if it returns non success, to forward the exit code.
|
|
/// Otherwise we attempt to parse the error messages and expose them via the Module API.
|
|
/// This is `true` for `zig cc`, `zig c++`, and `zig translate-c`.
|
|
clang_passthrough_mode: bool,
|
|
/// Whether to print clang argvs to stdout.
|
|
debug_cc: bool,
|
|
disable_c_depfile: bool,
|
|
|
|
c_source_files: []const CSourceFile,
|
|
clang_argv: []const []const u8,
|
|
cache_parent: *std.cache_hash.Cache,
|
|
/// Path to own executable for invoking `zig clang`.
|
|
self_exe_path: ?[]const u8,
|
|
zig_lib_directory: Directory,
|
|
zig_cache_directory: Directory,
|
|
libc_include_dir_list: []const []const u8,
|
|
rand: *std.rand.Random,
|
|
|
|
/// Populated when we build libc++.a. A WorkItem to build this is placed in the queue
|
|
/// and resolved before calling linker.flush().
|
|
libcxx_static_lib: ?[]const u8 = null,
|
|
/// Populated when we build libc++abi.a. A WorkItem to build this is placed in the queue
|
|
/// and resolved before calling linker.flush().
|
|
libcxxabi_static_lib: ?[]const u8 = null,
|
|
/// Populated when we build libunwind.a. A WorkItem to build this is placed in the queue
|
|
/// and resolved before calling linker.flush().
|
|
libunwind_static_lib: ?[]const u8 = null,
|
|
/// Populated when we build c.a. A WorkItem to build this is placed in the queue
|
|
/// and resolved before calling linker.flush().
|
|
libc_static_lib: ?[]const u8 = null,
|
|
|
|
/// For example `Scrt1.o` and `libc.so.6`. These are populated after building libc from source,
|
|
/// The set of needed CRT (C runtime) files differs depending on the target and compilation settings.
|
|
/// The key is the basename, and the value is the absolute path to the completed build artifact.
|
|
crt_files: std.StringHashMapUnmanaged([]const u8) = .{},
|
|
|
|
/// Keeping track of this possibly open resource so we can close it later.
|
|
owned_link_dir: ?std.fs.Dir,
|
|
|
|
pub const InnerError = ZigModule.InnerError;
|
|
|
|
/// For passing to a C compiler.
|
|
pub const CSourceFile = struct {
|
|
src_path: []const u8,
|
|
extra_flags: []const []const u8 = &[0][]const u8{},
|
|
};
|
|
|
|
const WorkItem = union(enum) {
|
|
/// Write the machine code for a Decl to the output file.
|
|
codegen_decl: *ZigModule.Decl,
|
|
/// The Decl needs to be analyzed and possibly export itself.
|
|
/// It may have already be analyzed, or it may have been determined
|
|
/// to be outdated; in this case perform semantic analysis again.
|
|
analyze_decl: *ZigModule.Decl,
|
|
/// The source file containing the Decl has been updated, and so the
|
|
/// Decl may need its line number information updated in the debug info.
|
|
update_line_number: *ZigModule.Decl,
|
|
/// Invoke the Clang compiler to create an object file, which gets linked
|
|
/// with the Module.
|
|
c_object: *CObject,
|
|
|
|
/// one of the glibc static objects
|
|
glibc_crt_file: glibc.CRTFile,
|
|
/// one of the glibc shared objects
|
|
glibc_so: *const glibc.Lib,
|
|
};
|
|
|
|
pub const CObject = struct {
|
|
/// Relative to cwd. Owned by arena.
|
|
src_path: []const u8,
|
|
/// Owned by arena.
|
|
extra_flags: []const []const u8,
|
|
arena: std.heap.ArenaAllocator.State,
|
|
status: union(enum) {
|
|
new,
|
|
success: struct {
|
|
/// The outputted result. Owned by gpa.
|
|
object_path: []u8,
|
|
/// This is a file system lock on the cache hash manifest representing this
|
|
/// object. It prevents other invocations of the Zig compiler from interfering
|
|
/// with this object until released.
|
|
lock: std.cache_hash.Lock,
|
|
},
|
|
/// There will be a corresponding ErrorMsg in Compilation.failed_c_objects.
|
|
failure,
|
|
},
|
|
|
|
/// Returns if there was failure.
|
|
pub fn clearStatus(self: *CObject, gpa: *Allocator) bool {
|
|
switch (self.status) {
|
|
.new => return false,
|
|
.failure => {
|
|
self.status = .new;
|
|
return true;
|
|
},
|
|
.success => |*success| {
|
|
gpa.free(success.object_path);
|
|
success.lock.release();
|
|
self.status = .new;
|
|
return false;
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn destroy(self: *CObject, gpa: *Allocator) void {
|
|
_ = self.clearStatus(gpa);
|
|
self.arena.promote(gpa).deinit();
|
|
}
|
|
};
|
|
|
|
pub const AllErrors = struct {
|
|
arena: std.heap.ArenaAllocator.State,
|
|
list: []const Message,
|
|
|
|
pub const Message = struct {
|
|
src_path: []const u8,
|
|
line: usize,
|
|
column: usize,
|
|
byte_offset: usize,
|
|
msg: []const u8,
|
|
};
|
|
|
|
pub fn deinit(self: *AllErrors, gpa: *Allocator) void {
|
|
self.arena.promote(gpa).deinit();
|
|
}
|
|
|
|
fn add(
|
|
arena: *std.heap.ArenaAllocator,
|
|
errors: *std.ArrayList(Message),
|
|
sub_file_path: []const u8,
|
|
source: []const u8,
|
|
simple_err_msg: ErrorMsg,
|
|
) !void {
|
|
const loc = std.zig.findLineColumn(source, simple_err_msg.byte_offset);
|
|
try errors.append(.{
|
|
.src_path = try arena.allocator.dupe(u8, sub_file_path),
|
|
.msg = try arena.allocator.dupe(u8, simple_err_msg.msg),
|
|
.byte_offset = simple_err_msg.byte_offset,
|
|
.line = loc.line,
|
|
.column = loc.column,
|
|
});
|
|
}
|
|
};
|
|
|
|
pub const Directory = struct {
|
|
/// This field is redundant for operations that can act on the open directory handle
|
|
/// directly, but it is needed when passing the directory to a child process.
|
|
/// `null` means cwd.
|
|
path: ?[]const u8,
|
|
handle: std.fs.Dir,
|
|
};
|
|
|
|
pub const EmitLoc = struct {
|
|
/// If this is `null` it means the file will be output to the cache directory.
|
|
/// When provided, both the open file handle and the path name must outlive the `Module`.
|
|
directory: ?Module.Directory,
|
|
/// This may not have sub-directories in it.
|
|
basename: []const u8,
|
|
};
|
|
|
|
pub const InitOptions = struct {
|
|
zig_lib_directory: Directory,
|
|
zig_cache_directory: Directory,
|
|
target: Target,
|
|
root_name: []const u8,
|
|
root_pkg: ?*Package,
|
|
output_mode: std.builtin.OutputMode,
|
|
rand: *std.rand.Random,
|
|
dynamic_linker: ?[]const u8 = null,
|
|
/// `null` means to not emit a binary file.
|
|
emit_bin: ?EmitLoc,
|
|
/// `null` means to not emit a C header file.
|
|
emit_h: ?EmitLoc = null,
|
|
link_mode: ?std.builtin.LinkMode = null,
|
|
object_format: ?std.builtin.ObjectFormat = null,
|
|
optimize_mode: std.builtin.Mode = .Debug,
|
|
keep_source_files_loaded: bool = false,
|
|
clang_argv: []const []const u8 = &[0][]const u8{},
|
|
lld_argv: []const []const u8 = &[0][]const u8{},
|
|
lib_dirs: []const []const u8 = &[0][]const u8{},
|
|
rpath_list: []const []const u8 = &[0][]const u8{},
|
|
c_source_files: []const CSourceFile = &[0]CSourceFile{},
|
|
link_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{},
|
|
link_libc: bool = false,
|
|
link_libcpp: bool = false,
|
|
want_pic: ?bool = null,
|
|
want_sanitize_c: ?bool = null,
|
|
want_stack_check: ?bool = null,
|
|
want_valgrind: ?bool = null,
|
|
use_llvm: ?bool = null,
|
|
use_lld: ?bool = null,
|
|
use_clang: ?bool = null,
|
|
rdynamic: bool = false,
|
|
strip: bool = false,
|
|
single_threaded: bool = false,
|
|
is_native_os: bool,
|
|
link_eh_frame_hdr: bool = false,
|
|
linker_script: ?[]const u8 = null,
|
|
version_script: ?[]const u8 = null,
|
|
override_soname: ?[]const u8 = null,
|
|
linker_gc_sections: ?bool = null,
|
|
function_sections: ?bool = null,
|
|
linker_allow_shlib_undefined: ?bool = null,
|
|
linker_bind_global_refs_locally: ?bool = null,
|
|
disable_c_depfile: bool = false,
|
|
linker_z_nodelete: bool = false,
|
|
linker_z_defs: bool = false,
|
|
clang_passthrough_mode: bool = false,
|
|
debug_cc: bool = false,
|
|
debug_link: bool = false,
|
|
stack_size_override: ?u64 = null,
|
|
self_exe_path: ?[]const u8 = null,
|
|
version: ?std.builtin.Version = null,
|
|
libc_installation: ?*const LibCInstallation = null,
|
|
};
|
|
|
|
pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
|
|
const comp: *Module = comp: {
|
|
// For allocations that have the same lifetime as Module. This arena is used only during this
|
|
// initialization and then is freed in deinit().
|
|
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
|
|
errdefer arena_allocator.deinit();
|
|
const arena = &arena_allocator.allocator;
|
|
|
|
// We put the `Module` itself in the arena. Freeing the arena will free the module.
|
|
// It's initialized later after we prepare the initialization options.
|
|
const comp = try arena.create(Module);
|
|
const root_name = try arena.dupe(u8, options.root_name);
|
|
|
|
const ofmt = options.object_format orelse options.target.getObjectFormat();
|
|
|
|
// Make a decision on whether to use LLD or our own linker.
|
|
const use_lld = if (options.use_lld) |explicit| explicit else blk: {
|
|
if (!build_options.have_llvm)
|
|
break :blk false;
|
|
|
|
if (ofmt == .c)
|
|
break :blk false;
|
|
|
|
// Our linker can't handle objects or most advanced options yet.
|
|
if (options.link_objects.len != 0 or
|
|
options.c_source_files.len != 0 or
|
|
options.frameworks.len != 0 or
|
|
options.system_libs.len != 0 or
|
|
options.link_libc or options.link_libcpp or
|
|
options.link_eh_frame_hdr or
|
|
options.linker_script != null or options.version_script != null)
|
|
{
|
|
break :blk true;
|
|
}
|
|
break :blk false;
|
|
};
|
|
|
|
// Make a decision on whether to use LLVM or our own backend.
|
|
const use_llvm = if (options.use_llvm) |explicit| explicit else blk: {
|
|
// We would want to prefer LLVM for release builds when it is available, however
|
|
// we don't have an LLVM backend yet :)
|
|
// We would also want to prefer LLVM for architectures that we don't have self-hosted support for too.
|
|
break :blk false;
|
|
};
|
|
|
|
const must_dynamic_link = dl: {
|
|
if (target_util.cannotDynamicLink(options.target))
|
|
break :dl false;
|
|
if (target_util.osRequiresLibC(options.target))
|
|
break :dl true;
|
|
if (options.link_libc and options.target.isGnuLibC())
|
|
break :dl true;
|
|
if (options.system_libs.len != 0)
|
|
break :dl true;
|
|
|
|
break :dl false;
|
|
};
|
|
const default_link_mode: std.builtin.LinkMode = if (must_dynamic_link) .Dynamic else .Static;
|
|
const link_mode: std.builtin.LinkMode = if (options.link_mode) |lm| blk: {
|
|
if (lm == .Static and must_dynamic_link) {
|
|
return error.UnableToStaticLink;
|
|
}
|
|
break :blk lm;
|
|
} else default_link_mode;
|
|
|
|
const libc_dirs = try detectLibCIncludeDirs(
|
|
arena,
|
|
options.zig_lib_directory.path.?,
|
|
options.target,
|
|
options.is_native_os,
|
|
options.link_libc,
|
|
options.libc_installation,
|
|
);
|
|
|
|
const must_pic: bool = b: {
|
|
if (target_util.requiresPIC(options.target, options.link_libc))
|
|
break :b true;
|
|
break :b link_mode == .Dynamic;
|
|
};
|
|
const pic = options.want_pic orelse must_pic;
|
|
|
|
if (options.emit_h != null) fatal("-femit-h not supported yet", .{}); // TODO
|
|
|
|
const emit_bin = options.emit_bin orelse fatal("-fno-emit-bin not supported yet", .{}); // TODO
|
|
|
|
// Make a decision on whether to use Clang for translate-c and compiling C files.
|
|
const use_clang = if (options.use_clang) |explicit| explicit else blk: {
|
|
if (build_options.have_llvm) {
|
|
// Can't use it if we don't have it!
|
|
break :blk false;
|
|
}
|
|
// It's not planned to do our own translate-c or C compilation.
|
|
break :blk true;
|
|
};
|
|
|
|
const is_safe_mode = switch (options.optimize_mode) {
|
|
.Debug, .ReleaseSafe => true,
|
|
.ReleaseFast, .ReleaseSmall => false,
|
|
};
|
|
|
|
const sanitize_c = options.want_sanitize_c orelse is_safe_mode;
|
|
|
|
const stack_check: bool = b: {
|
|
if (!target_util.supportsStackProbing(options.target))
|
|
break :b false;
|
|
break :b options.want_stack_check orelse is_safe_mode;
|
|
};
|
|
|
|
const valgrind: bool = b: {
|
|
if (!target_util.hasValgrindSupport(options.target))
|
|
break :b false;
|
|
break :b options.want_valgrind orelse (options.optimize_mode == .Debug);
|
|
};
|
|
|
|
const single_threaded = options.single_threaded or target_util.isSingleThreaded(options.target);
|
|
|
|
// We put everything into the cache hash that *cannot be modified during an incremental update*.
|
|
// For example, one cannot change the target between updates, but one can change source files,
|
|
// so the target goes into the cache hash, but source files do not. This is so that we can
|
|
// find the same binary and incrementally update it even if there are modified source files.
|
|
// We do this even if outputting to the current directory because we need somewhere to store
|
|
// incremental compilation metadata.
|
|
const cache = try arena.create(std.cache_hash.Cache);
|
|
cache.* = .{
|
|
.gpa = gpa,
|
|
.manifest_dir = try options.zig_cache_directory.handle.makeOpenPath("h", .{}),
|
|
};
|
|
errdefer cache.manifest_dir.close();
|
|
|
|
// This is shared hasher state common to zig source and all C source files.
|
|
cache.hash.addBytes(build_options.version);
|
|
cache.hash.addBytes(options.zig_lib_directory.path orelse ".");
|
|
cache.hash.add(options.optimize_mode);
|
|
cache.hash.add(options.target.cpu.arch);
|
|
cache.hash.addBytes(options.target.cpu.model.name);
|
|
cache.hash.add(options.target.cpu.features.ints);
|
|
cache.hash.add(options.target.os.tag);
|
|
cache.hash.add(options.target.abi);
|
|
cache.hash.add(ofmt);
|
|
cache.hash.add(pic);
|
|
cache.hash.add(stack_check);
|
|
cache.hash.add(link_mode);
|
|
cache.hash.add(options.strip);
|
|
cache.hash.add(options.link_libc);
|
|
cache.hash.add(options.output_mode);
|
|
// TODO audit this and make sure everything is in it
|
|
|
|
const zig_module: ?*ZigModule = if (options.root_pkg) |root_pkg| blk: {
|
|
// Options that are specific to zig source files, that cannot be
|
|
// modified between incremental updates.
|
|
var hash = cache.hash;
|
|
|
|
hash.add(valgrind);
|
|
hash.add(single_threaded);
|
|
switch (options.target.os.getVersionRange()) {
|
|
.linux => |linux| {
|
|
hash.add(linux.range.min);
|
|
hash.add(linux.range.max);
|
|
hash.add(linux.glibc);
|
|
},
|
|
.windows => |windows| {
|
|
hash.add(windows.min);
|
|
hash.add(windows.max);
|
|
},
|
|
.semver => |semver| {
|
|
hash.add(semver.min);
|
|
hash.add(semver.max);
|
|
},
|
|
.none => {},
|
|
}
|
|
|
|
const digest = hash.final();
|
|
const artifact_sub_dir = try std.fs.path.join(arena, &[_][]const u8{ "o", &digest });
|
|
var artifact_dir = try options.zig_cache_directory.handle.makeOpenPath(artifact_sub_dir, .{});
|
|
errdefer artifact_dir.close();
|
|
const zig_cache_artifact_directory: Directory = .{
|
|
.handle = artifact_dir,
|
|
.path = if (options.zig_cache_directory.path) |p|
|
|
try std.fs.path.join(arena, &[_][]const u8{ p, artifact_sub_dir })
|
|
else
|
|
artifact_sub_dir,
|
|
};
|
|
|
|
// TODO when we implement serialization and deserialization of incremental compilation metadata,
|
|
// this is where we would load it. We have open a handle to the directory where
|
|
// the output either already is, or will be.
|
|
// However we currently do not have serialization of such metadata, so for now
|
|
// we set up an empty ZigModule that does the entire compilation fresh.
|
|
|
|
const root_scope = rs: {
|
|
if (mem.endsWith(u8, root_pkg.root_src_path, ".zig")) {
|
|
const root_scope = try gpa.create(ZigModule.Scope.File);
|
|
root_scope.* = .{
|
|
.sub_file_path = root_pkg.root_src_path,
|
|
.source = .{ .unloaded = {} },
|
|
.contents = .{ .not_available = {} },
|
|
.status = .never_loaded,
|
|
.root_container = .{
|
|
.file_scope = root_scope,
|
|
.decls = .{},
|
|
},
|
|
};
|
|
break :rs &root_scope.base;
|
|
} else if (mem.endsWith(u8, root_pkg.root_src_path, ".zir")) {
|
|
const root_scope = try gpa.create(ZigModule.Scope.ZIRModule);
|
|
root_scope.* = .{
|
|
.sub_file_path = root_pkg.root_src_path,
|
|
.source = .{ .unloaded = {} },
|
|
.contents = .{ .not_available = {} },
|
|
.status = .never_loaded,
|
|
.decls = .{},
|
|
};
|
|
break :rs &root_scope.base;
|
|
} else {
|
|
unreachable;
|
|
}
|
|
};
|
|
|
|
const zig_module = try arena.create(ZigModule);
|
|
zig_module.* = .{
|
|
.gpa = gpa,
|
|
.comp = comp,
|
|
.root_pkg = root_pkg,
|
|
.root_scope = root_scope,
|
|
.zig_cache_artifact_directory = zig_cache_artifact_directory,
|
|
};
|
|
break :blk zig_module;
|
|
} else null;
|
|
errdefer if (zig_module) |zm| zm.deinit();
|
|
|
|
// For resource management purposes.
|
|
var owned_link_dir: ?std.fs.Dir = null;
|
|
errdefer if (owned_link_dir) |*dir| dir.close();
|
|
|
|
const bin_directory = emit_bin.directory orelse blk: {
|
|
if (zig_module) |zm| break :blk zm.zig_cache_artifact_directory;
|
|
|
|
const digest = cache.hash.peek();
|
|
const artifact_sub_dir = try std.fs.path.join(arena, &[_][]const u8{ "o", &digest });
|
|
var artifact_dir = try options.zig_cache_directory.handle.makeOpenPath(artifact_sub_dir, .{});
|
|
owned_link_dir = artifact_dir;
|
|
const link_artifact_directory: Directory = .{
|
|
.handle = artifact_dir,
|
|
.path = if (options.zig_cache_directory.path) |p|
|
|
try std.fs.path.join(arena, &[_][]const u8{ p, artifact_sub_dir })
|
|
else
|
|
artifact_sub_dir,
|
|
};
|
|
break :blk link_artifact_directory;
|
|
};
|
|
|
|
const bin_file = try link.File.openPath(gpa, .{
|
|
.directory = bin_directory,
|
|
.sub_path = emit_bin.basename,
|
|
.root_name = root_name,
|
|
.zig_module = zig_module,
|
|
.target = options.target,
|
|
.dynamic_linker = options.dynamic_linker,
|
|
.output_mode = options.output_mode,
|
|
.link_mode = link_mode,
|
|
.object_format = ofmt,
|
|
.optimize_mode = options.optimize_mode,
|
|
.use_lld = use_lld,
|
|
.use_llvm = use_llvm,
|
|
.link_libc = options.link_libc,
|
|
.link_libcpp = options.link_libcpp,
|
|
.objects = options.link_objects,
|
|
.frameworks = options.frameworks,
|
|
.framework_dirs = options.framework_dirs,
|
|
.system_libs = options.system_libs,
|
|
.lib_dirs = options.lib_dirs,
|
|
.rpath_list = options.rpath_list,
|
|
.strip = options.strip,
|
|
.is_native_os = options.is_native_os,
|
|
.function_sections = options.function_sections orelse false,
|
|
.allow_shlib_undefined = options.linker_allow_shlib_undefined,
|
|
.bind_global_refs_locally = options.linker_bind_global_refs_locally orelse false,
|
|
.z_nodelete = options.linker_z_nodelete,
|
|
.z_defs = options.linker_z_defs,
|
|
.stack_size_override = options.stack_size_override,
|
|
.linker_script = options.linker_script,
|
|
.version_script = options.version_script,
|
|
.gc_sections = options.linker_gc_sections,
|
|
.eh_frame_hdr = options.link_eh_frame_hdr,
|
|
.rdynamic = options.rdynamic,
|
|
.extra_lld_args = options.lld_argv,
|
|
.override_soname = options.override_soname,
|
|
.version = options.version,
|
|
.libc_installation = libc_dirs.libc_installation,
|
|
.pic = pic,
|
|
.valgrind = valgrind,
|
|
.stack_check = stack_check,
|
|
.single_threaded = single_threaded,
|
|
.debug_link = options.debug_link,
|
|
});
|
|
errdefer bin_file.destroy();
|
|
|
|
comp.* = .{
|
|
.gpa = gpa,
|
|
.arena_state = arena_allocator.state,
|
|
.zig_lib_directory = options.zig_lib_directory,
|
|
.zig_cache_directory = options.zig_cache_directory,
|
|
.bin_file = bin_file,
|
|
.work_queue = std.fifo.LinearFifo(WorkItem, .Dynamic).init(gpa),
|
|
.keep_source_files_loaded = options.keep_source_files_loaded,
|
|
.use_clang = use_clang,
|
|
.clang_argv = options.clang_argv,
|
|
.c_source_files = options.c_source_files,
|
|
.cache_parent = cache,
|
|
.self_exe_path = options.self_exe_path,
|
|
.libc_include_dir_list = libc_dirs.libc_include_dir_list,
|
|
.sanitize_c = sanitize_c,
|
|
.rand = options.rand,
|
|
.clang_passthrough_mode = options.clang_passthrough_mode,
|
|
.debug_cc = options.debug_cc,
|
|
.disable_c_depfile = options.disable_c_depfile,
|
|
.owned_link_dir = owned_link_dir,
|
|
};
|
|
break :comp comp;
|
|
};
|
|
errdefer comp.destroy();
|
|
|
|
// Add a `CObject` for each `c_source_files`.
|
|
try comp.c_object_table.ensureCapacity(gpa, options.c_source_files.len);
|
|
for (options.c_source_files) |c_source_file| {
|
|
var local_arena = std.heap.ArenaAllocator.init(gpa);
|
|
errdefer local_arena.deinit();
|
|
|
|
const c_object = try local_arena.allocator.create(CObject);
|
|
|
|
c_object.* = .{
|
|
.status = .{ .new = {} },
|
|
// TODO look into refactoring to turn these 2 fields simply into a CSourceFile
|
|
.src_path = try local_arena.allocator.dupe(u8, c_source_file.src_path),
|
|
.extra_flags = try local_arena.allocator.dupe([]const u8, c_source_file.extra_flags),
|
|
.arena = local_arena.state,
|
|
};
|
|
comp.c_object_table.putAssumeCapacityNoClobber(c_object, {});
|
|
}
|
|
|
|
// If we need to build glibc for the target, add work items for it.
|
|
// We go through the work queue so that building can be done in parallel.
|
|
if (comp.wantBuildGLibCFromSource()) {
|
|
try comp.addBuildingGLibCWorkItems();
|
|
}
|
|
|
|
return comp;
|
|
}
|
|
|
|
pub fn destroy(self: *Module) void {
|
|
const optional_zig_module = self.bin_file.options.zig_module;
|
|
self.bin_file.destroy();
|
|
if (optional_zig_module) |zig_module| zig_module.deinit();
|
|
|
|
const gpa = self.gpa;
|
|
self.work_queue.deinit();
|
|
|
|
{
|
|
var it = self.crt_files.iterator();
|
|
while (it.next()) |entry| {
|
|
gpa.free(entry.key);
|
|
gpa.free(entry.value);
|
|
}
|
|
self.crt_files.deinit(gpa);
|
|
}
|
|
|
|
for (self.c_object_table.items()) |entry| {
|
|
entry.key.destroy(gpa);
|
|
}
|
|
self.c_object_table.deinit(gpa);
|
|
|
|
for (self.failed_c_objects.items()) |entry| {
|
|
entry.value.destroy(gpa);
|
|
}
|
|
self.failed_c_objects.deinit(gpa);
|
|
|
|
self.cache_parent.manifest_dir.close();
|
|
if (self.owned_link_dir) |*dir| dir.close();
|
|
|
|
// This destroys `self`.
|
|
self.arena_state.promote(gpa).deinit();
|
|
}
|
|
|
|
pub fn getTarget(self: Module) Target {
|
|
return self.bin_file.options.target;
|
|
}
|
|
|
|
/// Detect changes to source files, perform semantic analysis, and update the output files.
|
|
pub fn update(self: *Module) !void {
|
|
const tracy = trace(@src());
|
|
defer tracy.end();
|
|
|
|
// For compiling C objects, we rely on the cache hash system to avoid duplicating work.
|
|
// TODO Look into caching this data in memory to improve performance.
|
|
// Add a WorkItem for each C object.
|
|
try self.work_queue.ensureUnusedCapacity(self.c_object_table.items().len);
|
|
for (self.c_object_table.items()) |entry| {
|
|
self.work_queue.writeItemAssumeCapacity(.{ .c_object = entry.key });
|
|
}
|
|
|
|
if (self.bin_file.options.zig_module) |zig_module| {
|
|
zig_module.generation += 1;
|
|
|
|
// TODO Detect which source files changed.
|
|
// Until then we simulate a full cache miss. Source files could have been loaded for any reason;
|
|
// to force a refresh we unload now.
|
|
if (zig_module.root_scope.cast(ZigModule.Scope.File)) |zig_file| {
|
|
zig_file.unload(zig_module.gpa);
|
|
zig_module.analyzeContainer(&zig_file.root_container) catch |err| switch (err) {
|
|
error.AnalysisFail => {
|
|
assert(self.totalErrorCount() != 0);
|
|
},
|
|
else => |e| return e,
|
|
};
|
|
} else if (zig_module.root_scope.cast(ZigModule.Scope.ZIRModule)) |zir_module| {
|
|
zir_module.unload(zig_module.gpa);
|
|
zig_module.analyzeRootZIRModule(zir_module) catch |err| switch (err) {
|
|
error.AnalysisFail => {
|
|
assert(self.totalErrorCount() != 0);
|
|
},
|
|
else => |e| return e,
|
|
};
|
|
}
|
|
}
|
|
|
|
try self.performAllTheWork();
|
|
|
|
if (self.bin_file.options.zig_module) |zig_module| {
|
|
// Process the deletion set.
|
|
while (zig_module.deletion_set.popOrNull()) |decl| {
|
|
if (decl.dependants.items().len != 0) {
|
|
decl.deletion_flag = false;
|
|
continue;
|
|
}
|
|
try zig_module.deleteDecl(decl);
|
|
}
|
|
}
|
|
|
|
// This is needed before reading the error flags.
|
|
try self.bin_file.flush(self);
|
|
|
|
self.link_error_flags = self.bin_file.errorFlags();
|
|
|
|
// If there are any errors, we anticipate the source files being loaded
|
|
// to report error messages. Otherwise we unload all source files to save memory.
|
|
if (self.totalErrorCount() == 0 and !self.keep_source_files_loaded) {
|
|
if (self.bin_file.options.zig_module) |zig_module| {
|
|
zig_module.root_scope.unload(self.gpa);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Having the file open for writing is problematic as far as executing the
|
|
/// binary is concerned. This will remove the write flag, or close the file,
|
|
/// or whatever is needed so that it can be executed.
|
|
/// After this, one must call` makeFileWritable` before calling `update`.
|
|
pub fn makeBinFileExecutable(self: *Module) !void {
|
|
return self.bin_file.makeExecutable();
|
|
}
|
|
|
|
pub fn makeBinFileWritable(self: *Module) !void {
|
|
return self.bin_file.makeWritable();
|
|
}
|
|
|
|
pub fn totalErrorCount(self: *Module) usize {
|
|
var total: usize = self.failed_c_objects.items().len;
|
|
|
|
if (self.bin_file.options.zig_module) |zig_module| {
|
|
total += zig_module.failed_decls.items().len +
|
|
zig_module.failed_exports.items().len +
|
|
zig_module.failed_files.items().len;
|
|
}
|
|
|
|
// The "no entry point found" error only counts if there are no other errors.
|
|
if (total == 0) {
|
|
return @boolToInt(self.link_error_flags.no_entry_point_found);
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
pub fn getAllErrorsAlloc(self: *Module) !AllErrors {
|
|
var arena = std.heap.ArenaAllocator.init(self.gpa);
|
|
errdefer arena.deinit();
|
|
|
|
var errors = std.ArrayList(AllErrors.Message).init(self.gpa);
|
|
defer errors.deinit();
|
|
|
|
for (self.failed_c_objects.items()) |entry| {
|
|
const c_object = entry.key;
|
|
const err_msg = entry.value;
|
|
try AllErrors.add(&arena, &errors, c_object.src_path, "", err_msg.*);
|
|
}
|
|
if (self.bin_file.options.zig_module) |zig_module| {
|
|
for (zig_module.failed_files.items()) |entry| {
|
|
const scope = entry.key;
|
|
const err_msg = entry.value;
|
|
const source = try scope.getSource(zig_module);
|
|
try AllErrors.add(&arena, &errors, scope.subFilePath(), source, err_msg.*);
|
|
}
|
|
for (zig_module.failed_decls.items()) |entry| {
|
|
const decl = entry.key;
|
|
const err_msg = entry.value;
|
|
const source = try decl.scope.getSource(zig_module);
|
|
try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*);
|
|
}
|
|
for (zig_module.failed_exports.items()) |entry| {
|
|
const decl = entry.key.owner_decl;
|
|
const err_msg = entry.value;
|
|
const source = try decl.scope.getSource(zig_module);
|
|
try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*);
|
|
}
|
|
}
|
|
|
|
if (errors.items.len == 0 and self.link_error_flags.no_entry_point_found) {
|
|
const global_err_src_path = blk: {
|
|
if (self.bin_file.options.zig_module) |zig_module| break :blk zig_module.root_pkg.root_src_path;
|
|
if (self.c_source_files.len != 0) break :blk self.c_source_files[0].src_path;
|
|
if (self.bin_file.options.objects.len != 0) break :blk self.bin_file.options.objects[0];
|
|
break :blk "(no file)";
|
|
};
|
|
try errors.append(.{
|
|
.src_path = global_err_src_path,
|
|
.line = 0,
|
|
.column = 0,
|
|
.byte_offset = 0,
|
|
.msg = try std.fmt.allocPrint(&arena.allocator, "no entry point found", .{}),
|
|
});
|
|
}
|
|
|
|
assert(errors.items.len == self.totalErrorCount());
|
|
|
|
return AllErrors{
|
|
.list = try arena.allocator.dupe(AllErrors.Message, errors.items),
|
|
.arena = arena.state,
|
|
};
|
|
}
|
|
|
|
pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void {
|
|
while (self.work_queue.readItem()) |work_item| switch (work_item) {
|
|
.codegen_decl => |decl| switch (decl.analysis) {
|
|
.unreferenced => unreachable,
|
|
.in_progress => unreachable,
|
|
.outdated => unreachable,
|
|
|
|
.sema_failure,
|
|
.codegen_failure,
|
|
.dependency_failure,
|
|
.sema_failure_retryable,
|
|
=> continue,
|
|
|
|
.complete, .codegen_failure_retryable => {
|
|
const zig_module = self.bin_file.options.zig_module.?;
|
|
if (decl.typed_value.most_recent.typed_value.val.cast(Value.Payload.Function)) |payload| {
|
|
switch (payload.func.analysis) {
|
|
.queued => zig_module.analyzeFnBody(decl, payload.func) catch |err| switch (err) {
|
|
error.AnalysisFail => {
|
|
assert(payload.func.analysis != .in_progress);
|
|
continue;
|
|
},
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
},
|
|
.in_progress => unreachable,
|
|
.sema_failure, .dependency_failure => continue,
|
|
.success => {},
|
|
}
|
|
// Here we tack on additional allocations to the Decl's arena. The allocations are
|
|
// lifetime annotations in the ZIR.
|
|
var decl_arena = decl.typed_value.most_recent.arena.?.promote(zig_module.gpa);
|
|
defer decl.typed_value.most_recent.arena.?.* = decl_arena.state;
|
|
log.debug("analyze liveness of {}\n", .{decl.name});
|
|
try liveness.analyze(zig_module.gpa, &decl_arena.allocator, payload.func.analysis.success);
|
|
}
|
|
|
|
assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits());
|
|
|
|
self.bin_file.updateDecl(zig_module, decl) catch |err| switch (err) {
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
error.AnalysisFail => {
|
|
decl.analysis = .dependency_failure;
|
|
},
|
|
else => {
|
|
try zig_module.failed_decls.ensureCapacity(zig_module.gpa, zig_module.failed_decls.items().len + 1);
|
|
zig_module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
|
|
zig_module.gpa,
|
|
decl.src(),
|
|
"unable to codegen: {}",
|
|
.{@errorName(err)},
|
|
));
|
|
decl.analysis = .codegen_failure_retryable;
|
|
},
|
|
};
|
|
},
|
|
},
|
|
.analyze_decl => |decl| {
|
|
const zig_module = self.bin_file.options.zig_module.?;
|
|
zig_module.ensureDeclAnalyzed(decl) catch |err| switch (err) {
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
error.AnalysisFail => continue,
|
|
};
|
|
},
|
|
.update_line_number => |decl| {
|
|
const zig_module = self.bin_file.options.zig_module.?;
|
|
self.bin_file.updateDeclLineNumber(zig_module, decl) catch |err| {
|
|
try zig_module.failed_decls.ensureCapacity(zig_module.gpa, zig_module.failed_decls.items().len + 1);
|
|
zig_module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
|
|
zig_module.gpa,
|
|
decl.src(),
|
|
"unable to update line number: {}",
|
|
.{@errorName(err)},
|
|
));
|
|
decl.analysis = .codegen_failure_retryable;
|
|
};
|
|
},
|
|
.c_object => |c_object| {
|
|
self.updateCObject(c_object) catch |err| switch (err) {
|
|
error.AnalysisFail => continue,
|
|
else => {
|
|
try self.failed_c_objects.ensureCapacity(self.gpa, self.failed_c_objects.items().len + 1);
|
|
self.failed_c_objects.putAssumeCapacityNoClobber(c_object, try ErrorMsg.create(
|
|
self.gpa,
|
|
0,
|
|
"unable to build C object: {}",
|
|
.{@errorName(err)},
|
|
));
|
|
c_object.status = .{ .failure = {} };
|
|
},
|
|
};
|
|
},
|
|
.glibc_crt_file => |crt_file| {
|
|
glibc.buildCRTFile(self, crt_file) catch |err| {
|
|
// This is a problem with the Zig installation. It's mostly OK to crash here,
|
|
// but TODO because it would be even better if we could recover gracefully
|
|
// from temporary problems such as out-of-disk-space.
|
|
fatal("unable to build glibc CRT file: {}", .{@errorName(err)});
|
|
};
|
|
},
|
|
.glibc_so => |glibc_lib| {
|
|
fatal("TODO build glibc shared object '{}.so.{}'", .{ glibc_lib.name, glibc_lib.sover });
|
|
},
|
|
};
|
|
}
|
|
|
|
fn updateCObject(comp: *Compilation, c_object: *CObject) !void {
|
|
const tracy = trace(@src());
|
|
defer tracy.end();
|
|
|
|
if (!build_options.have_llvm) {
|
|
return comp.failCObj(c_object, "clang not available: compiler not built with LLVM extensions enabled", .{});
|
|
}
|
|
const self_exe_path = comp.self_exe_path orelse
|
|
return comp.failCObj(c_object, "clang compilation disabled", .{});
|
|
|
|
if (c_object.clearStatus(comp.gpa)) {
|
|
// There was previous failure.
|
|
comp.failed_c_objects.removeAssertDiscard(c_object);
|
|
}
|
|
|
|
var ch = comp.cache_parent.obtain();
|
|
defer ch.deinit();
|
|
|
|
ch.hash.add(comp.sanitize_c);
|
|
ch.hash.addListOfBytes(comp.clang_argv);
|
|
ch.hash.add(comp.bin_file.options.link_libcpp);
|
|
ch.hash.addListOfBytes(comp.libc_include_dir_list);
|
|
// TODO
|
|
//cache_int(cache_hash, g->code_model);
|
|
//cache_bool(cache_hash, codegen_have_frame_pointer(g));
|
|
_ = try ch.addFile(c_object.src_path, null);
|
|
{
|
|
// Hash the extra flags, with special care to call addFile for file parameters.
|
|
// TODO this logic can likely be improved by utilizing clang_options_data.zig.
|
|
const file_args = [_][]const u8{"-include"};
|
|
var arg_i: usize = 0;
|
|
while (arg_i < c_object.extra_flags.len) : (arg_i += 1) {
|
|
const arg = c_object.extra_flags[arg_i];
|
|
ch.hash.addBytes(arg);
|
|
for (file_args) |file_arg| {
|
|
if (mem.eql(u8, file_arg, arg) and arg_i + 1 < c_object.extra_flags.len) {
|
|
arg_i += 1;
|
|
_ = try ch.addFile(c_object.extra_flags[arg_i], null);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa);
|
|
defer arena_allocator.deinit();
|
|
const arena = &arena_allocator.allocator;
|
|
|
|
const c_source_basename = std.fs.path.basename(c_object.src_path);
|
|
// Special case when doing build-obj for just one C file. When there are more than one object
|
|
// file and building an object we need to link them together, but with just one it should go
|
|
// directly to the output file.
|
|
const direct_o = comp.c_source_files.len == 1 and comp.bin_file.options.zig_module == null and
|
|
comp.bin_file.options.output_mode == .Obj and comp.bin_file.options.objects.len == 0;
|
|
const o_basename_noext = if (direct_o)
|
|
comp.bin_file.options.root_name
|
|
else
|
|
mem.split(c_source_basename, ".").next().?;
|
|
const o_basename = try std.fmt.allocPrint(arena, "{}{}", .{ o_basename_noext, comp.getTarget().oFileExt() });
|
|
|
|
const full_object_path = if (!(try ch.hit()) or comp.disable_c_depfile) blk: {
|
|
var argv = std.ArrayList([]const u8).init(comp.gpa);
|
|
defer argv.deinit();
|
|
|
|
// We can't know the digest until we do the C compiler invocation, so we need a temporary filename.
|
|
const out_obj_path = try comp.tmpFilePath(arena, o_basename);
|
|
|
|
try argv.appendSlice(&[_][]const u8{ self_exe_path, "clang", "-c" });
|
|
|
|
const ext = classifyFileExt(c_object.src_path);
|
|
// TODO capture the .d file and deal with caching stuff
|
|
try comp.addCCArgs(arena, &argv, ext, false, null);
|
|
|
|
try argv.append("-o");
|
|
try argv.append(out_obj_path);
|
|
|
|
try argv.append(c_object.src_path);
|
|
try argv.appendSlice(c_object.extra_flags);
|
|
|
|
if (comp.debug_cc) {
|
|
for (argv.items[0 .. argv.items.len - 1]) |arg| {
|
|
std.debug.print("{} ", .{arg});
|
|
}
|
|
std.debug.print("{}\n", .{argv.items[argv.items.len - 1]});
|
|
}
|
|
|
|
const child = try std.ChildProcess.init(argv.items, arena);
|
|
defer child.deinit();
|
|
|
|
if (comp.clang_passthrough_mode) {
|
|
child.stdin_behavior = .Inherit;
|
|
child.stdout_behavior = .Inherit;
|
|
child.stderr_behavior = .Inherit;
|
|
|
|
const term = child.spawnAndWait() catch |err| {
|
|
return comp.failCObj(c_object, "unable to spawn {}: {}", .{ argv.items[0], @errorName(err) });
|
|
};
|
|
switch (term) {
|
|
.Exited => |code| {
|
|
if (code != 0) {
|
|
// TODO make std.process.exit and std.ChildProcess exit code have the same type
|
|
// and forward it here. Currently it is u32 vs u8.
|
|
std.process.exit(1);
|
|
}
|
|
},
|
|
else => std.process.exit(1),
|
|
}
|
|
} else {
|
|
child.stdin_behavior = .Ignore;
|
|
child.stdout_behavior = .Pipe;
|
|
child.stderr_behavior = .Pipe;
|
|
|
|
try child.spawn();
|
|
|
|
const stdout_reader = child.stdout.?.reader();
|
|
const stderr_reader = child.stderr.?.reader();
|
|
|
|
// TODO Need to poll to read these streams to prevent a deadlock (or rely on evented I/O).
|
|
const stdout = try stdout_reader.readAllAlloc(arena, std.math.maxInt(u32));
|
|
const stderr = try stderr_reader.readAllAlloc(arena, 10 * 1024 * 1024);
|
|
|
|
const term = child.wait() catch |err| {
|
|
return comp.failCObj(c_object, "unable to spawn {}: {}", .{ argv.items[0], @errorName(err) });
|
|
};
|
|
|
|
switch (term) {
|
|
.Exited => |code| {
|
|
if (code != 0) {
|
|
// TODO parse clang stderr and turn it into an error message
|
|
// and then call failCObjWithOwnedErrorMsg
|
|
std.log.err("clang failed with stderr: {}", .{stderr});
|
|
return comp.failCObj(c_object, "clang exited with code {}", .{code});
|
|
}
|
|
},
|
|
else => {
|
|
std.log.err("clang terminated with stderr: {}", .{stderr});
|
|
return comp.failCObj(c_object, "clang terminated unexpectedly", .{});
|
|
},
|
|
}
|
|
}
|
|
|
|
// TODO handle .d files
|
|
|
|
// Rename into place.
|
|
const digest = ch.final();
|
|
const full_object_path = if (comp.zig_cache_directory.path) |p|
|
|
try std.fs.path.join(arena, &[_][]const u8{ p, "o", &digest, o_basename })
|
|
else
|
|
try std.fs.path.join(arena, &[_][]const u8{ "o", &digest, o_basename });
|
|
try std.fs.rename(out_obj_path, full_object_path);
|
|
|
|
ch.writeManifest() catch |err| {
|
|
std.log.warn("failed to write cache manifest when compiling '{}': {}", .{ c_object.src_path, @errorName(err) });
|
|
};
|
|
break :blk full_object_path;
|
|
} else blk: {
|
|
const digest = ch.final();
|
|
const full_object_path = if (comp.zig_cache_directory.path) |p|
|
|
try std.fs.path.join(arena, &[_][]const u8{ p, "o", &digest, o_basename })
|
|
else
|
|
try std.fs.path.join(arena, &[_][]const u8{ "o", &digest, o_basename });
|
|
break :blk full_object_path;
|
|
};
|
|
|
|
c_object.status = .{
|
|
.success = .{
|
|
.object_path = full_object_path,
|
|
.lock = ch.toOwnedLock(),
|
|
},
|
|
};
|
|
}
|
|
|
|
fn tmpFilePath(mod: *Module, arena: *Allocator, suffix: []const u8) error{OutOfMemory}![]const u8 {
|
|
const s = std.fs.path.sep_str;
|
|
return std.fmt.allocPrint(
|
|
arena,
|
|
"{}" ++ s ++ "tmp" ++ s ++ "{x}-{}",
|
|
.{ mod.zig_cache_directory.path.?, mod.rand.int(u64), suffix },
|
|
);
|
|
}
|
|
|
|
/// Add common C compiler args between translate-c and C object compilation.
|
|
fn addCCArgs(
|
|
mod: *Module,
|
|
arena: *Allocator,
|
|
argv: *std.ArrayList([]const u8),
|
|
ext: FileExt,
|
|
translate_c: bool,
|
|
out_dep_path: ?[]const u8,
|
|
) !void {
|
|
const target = mod.getTarget();
|
|
|
|
if (translate_c) {
|
|
try argv.appendSlice(&[_][]const u8{ "-x", "c" });
|
|
}
|
|
|
|
if (ext == .cpp) {
|
|
try argv.append("-nostdinc++");
|
|
}
|
|
try argv.appendSlice(&[_][]const u8{
|
|
"-nostdinc",
|
|
"-fno-spell-checking",
|
|
});
|
|
|
|
// We don't ever put `-fcolor-diagnostics` or `-fno-color-diagnostics` because in passthrough mode
|
|
// we want Clang to infer it, and in normal mode we always want it off, which will be true since
|
|
// clang will detect stderr as a pipe rather than a terminal.
|
|
if (!mod.clang_passthrough_mode) {
|
|
// Make stderr more easily parseable.
|
|
try argv.append("-fno-caret-diagnostics");
|
|
}
|
|
|
|
if (mod.bin_file.options.function_sections) {
|
|
try argv.append("-ffunction-sections");
|
|
}
|
|
|
|
try argv.ensureCapacity(argv.items.len + mod.bin_file.options.framework_dirs.len * 2);
|
|
for (mod.bin_file.options.framework_dirs) |framework_dir| {
|
|
argv.appendAssumeCapacity("-iframework");
|
|
argv.appendAssumeCapacity(framework_dir);
|
|
}
|
|
|
|
if (mod.bin_file.options.link_libcpp) {
|
|
const libcxx_include_path = try std.fs.path.join(arena, &[_][]const u8{
|
|
mod.zig_lib_directory.path.?, "libcxx", "include",
|
|
});
|
|
const libcxxabi_include_path = try std.fs.path.join(arena, &[_][]const u8{
|
|
mod.zig_lib_directory.path.?, "libcxxabi", "include",
|
|
});
|
|
|
|
try argv.append("-isystem");
|
|
try argv.append(libcxx_include_path);
|
|
|
|
try argv.append("-isystem");
|
|
try argv.append(libcxxabi_include_path);
|
|
|
|
if (target.abi.isMusl()) {
|
|
try argv.append("-D_LIBCPP_HAS_MUSL_LIBC");
|
|
}
|
|
try argv.append("-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS");
|
|
try argv.append("-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS");
|
|
}
|
|
|
|
const llvm_triple = try @import("codegen/llvm.zig").targetTriple(arena, target);
|
|
try argv.appendSlice(&[_][]const u8{ "-target", llvm_triple });
|
|
|
|
switch (ext) {
|
|
.c, .cpp, .h => {
|
|
// According to Rich Felker libc headers are supposed to go before C language headers.
|
|
// However as noted by @dimenus, appending libc headers before c_headers breaks intrinsics
|
|
// and other compiler specific items.
|
|
const c_headers_dir = try std.fs.path.join(arena, &[_][]const u8{ mod.zig_lib_directory.path.?, "include" });
|
|
try argv.append("-isystem");
|
|
try argv.append(c_headers_dir);
|
|
|
|
for (mod.libc_include_dir_list) |include_dir| {
|
|
try argv.append("-isystem");
|
|
try argv.append(include_dir);
|
|
}
|
|
|
|
if (target.cpu.model.llvm_name) |llvm_name| {
|
|
try argv.appendSlice(&[_][]const u8{
|
|
"-Xclang", "-target-cpu", "-Xclang", llvm_name,
|
|
});
|
|
}
|
|
// TODO CLI args for target features
|
|
//if (g->zig_target->llvm_cpu_features != nullptr) {
|
|
// // https://github.com/ziglang/zig/issues/5017
|
|
// SplitIterator it = memSplit(str(g->zig_target->llvm_cpu_features), str(","));
|
|
// Optional<Slice<uint8_t>> flag = SplitIterator_next(&it);
|
|
// while (flag.is_some) {
|
|
// try argv.append("-Xclang");
|
|
// try argv.append("-target-feature");
|
|
// try argv.append("-Xclang");
|
|
// try argv.append(buf_ptr(buf_create_from_slice(flag.value)));
|
|
// flag = SplitIterator_next(&it);
|
|
// }
|
|
//}
|
|
if (translate_c) {
|
|
// This gives us access to preprocessing entities, presumably at the cost of performance.
|
|
try argv.append("-Xclang");
|
|
try argv.append("-detailed-preprocessing-record");
|
|
}
|
|
if (out_dep_path) |p| {
|
|
try argv.append("-MD");
|
|
try argv.append("-MV");
|
|
try argv.append("-MF");
|
|
try argv.append(p);
|
|
}
|
|
},
|
|
.so, .assembly, .ll, .bc, .unknown => {},
|
|
}
|
|
// TODO CLI args for cpu features when compiling assembly
|
|
//for (size_t i = 0; i < g->zig_target->llvm_cpu_features_asm_len; i += 1) {
|
|
// try argv.append(g->zig_target->llvm_cpu_features_asm_ptr[i]);
|
|
//}
|
|
|
|
if (target.os.tag == .freestanding) {
|
|
try argv.append("-ffreestanding");
|
|
}
|
|
|
|
// windows.h has files such as pshpack1.h which do #pragma packing, triggering a clang warning.
|
|
// So for this target, we disable this warning.
|
|
if (target.os.tag == .windows and target.abi.isGnu()) {
|
|
try argv.append("-Wno-pragma-pack");
|
|
}
|
|
|
|
if (!mod.bin_file.options.strip) {
|
|
try argv.append("-g");
|
|
}
|
|
|
|
if (mod.haveFramePointer()) {
|
|
try argv.append("-fno-omit-frame-pointer");
|
|
} else {
|
|
try argv.append("-fomit-frame-pointer");
|
|
}
|
|
|
|
if (mod.sanitize_c) {
|
|
try argv.append("-fsanitize=undefined");
|
|
try argv.append("-fsanitize-trap=undefined");
|
|
}
|
|
|
|
switch (mod.bin_file.options.optimize_mode) {
|
|
.Debug => {
|
|
// windows c runtime requires -D_DEBUG if using debug libraries
|
|
try argv.append("-D_DEBUG");
|
|
try argv.append("-Og");
|
|
|
|
if (mod.bin_file.options.link_libc) {
|
|
try argv.append("-fstack-protector-strong");
|
|
try argv.append("--param");
|
|
try argv.append("ssp-buffer-size=4");
|
|
} else {
|
|
try argv.append("-fno-stack-protector");
|
|
}
|
|
},
|
|
.ReleaseSafe => {
|
|
// See the comment in the BuildModeFastRelease case for why we pass -O2 rather
|
|
// than -O3 here.
|
|
try argv.append("-O2");
|
|
if (mod.bin_file.options.link_libc) {
|
|
try argv.append("-D_FORTIFY_SOURCE=2");
|
|
try argv.append("-fstack-protector-strong");
|
|
try argv.append("--param");
|
|
try argv.append("ssp-buffer-size=4");
|
|
} else {
|
|
try argv.append("-fno-stack-protector");
|
|
}
|
|
},
|
|
.ReleaseFast => {
|
|
try argv.append("-DNDEBUG");
|
|
// Here we pass -O2 rather than -O3 because, although we do the equivalent of
|
|
// -O3 in Zig code, the justification for the difference here is that Zig
|
|
// has better detection and prevention of undefined behavior, so -O3 is safer for
|
|
// Zig code than it is for C code. Also, C programmers are used to their code
|
|
// running in -O2 and thus the -O3 path has been tested less.
|
|
try argv.append("-O2");
|
|
try argv.append("-fno-stack-protector");
|
|
},
|
|
.ReleaseSmall => {
|
|
try argv.append("-DNDEBUG");
|
|
try argv.append("-Os");
|
|
try argv.append("-fno-stack-protector");
|
|
},
|
|
}
|
|
|
|
if (target_util.supports_fpic(target) and mod.bin_file.options.pic) {
|
|
try argv.append("-fPIC");
|
|
}
|
|
|
|
try argv.appendSlice(mod.clang_argv);
|
|
}
|
|
|
|
fn failCObj(mod: *Module, c_object: *CObject, comptime format: []const u8, args: anytype) InnerError {
|
|
@setCold(true);
|
|
const err_msg = try ErrorMsg.create(mod.gpa, 0, "unable to build C object: " ++ format, args);
|
|
return mod.failCObjWithOwnedErrorMsg(c_object, err_msg);
|
|
}
|
|
|
|
fn failCObjWithOwnedErrorMsg(mod: *Module, c_object: *CObject, err_msg: *ErrorMsg) InnerError {
|
|
{
|
|
errdefer err_msg.destroy(mod.gpa);
|
|
try mod.failed_c_objects.ensureCapacity(mod.gpa, mod.failed_c_objects.items().len + 1);
|
|
}
|
|
mod.failed_c_objects.putAssumeCapacityNoClobber(c_object, err_msg);
|
|
c_object.status = .failure;
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
pub const ErrorMsg = struct {
|
|
byte_offset: usize,
|
|
msg: []const u8,
|
|
|
|
pub fn create(gpa: *Allocator, byte_offset: usize, comptime format: []const u8, args: anytype) !*ErrorMsg {
|
|
const self = try gpa.create(ErrorMsg);
|
|
errdefer gpa.destroy(self);
|
|
self.* = try init(gpa, byte_offset, format, args);
|
|
return self;
|
|
}
|
|
|
|
/// Assumes the ErrorMsg struct and msg were both allocated with allocator.
|
|
pub fn destroy(self: *ErrorMsg, gpa: *Allocator) void {
|
|
self.deinit(gpa);
|
|
gpa.destroy(self);
|
|
}
|
|
|
|
pub fn init(gpa: *Allocator, byte_offset: usize, comptime format: []const u8, args: anytype) !ErrorMsg {
|
|
return ErrorMsg{
|
|
.byte_offset = byte_offset,
|
|
.msg = try std.fmt.allocPrint(gpa, format, args),
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: *ErrorMsg, gpa: *Allocator) void {
|
|
gpa.free(self.msg);
|
|
self.* = undefined;
|
|
}
|
|
};
|
|
|
|
pub const FileExt = enum {
|
|
c,
|
|
cpp,
|
|
h,
|
|
ll,
|
|
bc,
|
|
assembly,
|
|
so,
|
|
unknown,
|
|
};
|
|
|
|
pub fn hasCExt(filename: []const u8) bool {
|
|
return mem.endsWith(u8, filename, ".c");
|
|
}
|
|
|
|
pub fn hasCppExt(filename: []const u8) bool {
|
|
return mem.endsWith(u8, filename, ".C") or
|
|
mem.endsWith(u8, filename, ".cc") or
|
|
mem.endsWith(u8, filename, ".cpp") or
|
|
mem.endsWith(u8, filename, ".cxx");
|
|
}
|
|
|
|
pub fn hasAsmExt(filename: []const u8) bool {
|
|
return mem.endsWith(u8, filename, ".s") or mem.endsWith(u8, filename, ".S");
|
|
}
|
|
|
|
pub fn classifyFileExt(filename: []const u8) FileExt {
|
|
if (hasCExt(filename)) {
|
|
return .c;
|
|
} else if (hasCppExt(filename)) {
|
|
return .cpp;
|
|
} else if (mem.endsWith(u8, filename, ".ll")) {
|
|
return .ll;
|
|
} else if (mem.endsWith(u8, filename, ".bc")) {
|
|
return .bc;
|
|
} else if (hasAsmExt(filename)) {
|
|
return .assembly;
|
|
} else if (mem.endsWith(u8, filename, ".h")) {
|
|
return .h;
|
|
} else if (mem.endsWith(u8, filename, ".so")) {
|
|
return .so;
|
|
}
|
|
// Look for .so.X, .so.X.Y, .so.X.Y.Z
|
|
var it = mem.split(filename, ".");
|
|
_ = it.next().?;
|
|
var so_txt = it.next() orelse return .unknown;
|
|
while (!mem.eql(u8, so_txt, "so")) {
|
|
so_txt = it.next() orelse return .unknown;
|
|
}
|
|
const n1 = it.next() orelse return .unknown;
|
|
const n2 = it.next();
|
|
const n3 = it.next();
|
|
|
|
_ = std.fmt.parseInt(u32, n1, 10) catch return .unknown;
|
|
if (n2) |x| _ = std.fmt.parseInt(u32, x, 10) catch return .unknown;
|
|
if (n3) |x| _ = std.fmt.parseInt(u32, x, 10) catch return .unknown;
|
|
if (it.next() != null) return .unknown;
|
|
|
|
return .so;
|
|
}
|
|
|
|
test "classifyFileExt" {
|
|
std.testing.expectEqual(FileExt.cpp, classifyFileExt("foo.cc"));
|
|
std.testing.expectEqual(FileExt.unknown, classifyFileExt("foo.nim"));
|
|
std.testing.expectEqual(FileExt.so, classifyFileExt("foo.so"));
|
|
std.testing.expectEqual(FileExt.so, classifyFileExt("foo.so.1"));
|
|
std.testing.expectEqual(FileExt.so, classifyFileExt("foo.so.1.2"));
|
|
std.testing.expectEqual(FileExt.so, classifyFileExt("foo.so.1.2.3"));
|
|
std.testing.expectEqual(FileExt.unknown, classifyFileExt("foo.so.1.2.3~"));
|
|
}
|
|
|
|
fn haveFramePointer(mod: *Module) bool {
|
|
return switch (mod.bin_file.options.optimize_mode) {
|
|
.Debug, .ReleaseSafe => !mod.bin_file.options.strip,
|
|
.ReleaseSmall, .ReleaseFast => false,
|
|
};
|
|
}
|
|
|
|
const LibCDirs = struct {
|
|
libc_include_dir_list: []const []const u8,
|
|
libc_installation: ?*const LibCInstallation,
|
|
};
|
|
|
|
fn detectLibCIncludeDirs(
|
|
arena: *Allocator,
|
|
zig_lib_dir: []const u8,
|
|
target: Target,
|
|
is_native_os: bool,
|
|
link_libc: bool,
|
|
libc_installation: ?*const LibCInstallation,
|
|
) !LibCDirs {
|
|
if (!link_libc) {
|
|
return LibCDirs{
|
|
.libc_include_dir_list = &[0][]u8{},
|
|
.libc_installation = null,
|
|
};
|
|
}
|
|
|
|
if (libc_installation) |lci| {
|
|
return detectLibCFromLibCInstallation(arena, target, lci);
|
|
}
|
|
|
|
if (target_util.canBuildLibC(target)) {
|
|
const generic_name = target_util.libCGenericName(target);
|
|
// Some architectures are handled by the same set of headers.
|
|
const arch_name = if (target.abi.isMusl()) target_util.archMuslName(target.cpu.arch) else @tagName(target.cpu.arch);
|
|
const os_name = @tagName(target.os.tag);
|
|
// Musl's headers are ABI-agnostic and so they all have the "musl" ABI name.
|
|
const abi_name = if (target.abi.isMusl()) "musl" else @tagName(target.abi);
|
|
const s = std.fs.path.sep_str;
|
|
const arch_include_dir = try std.fmt.allocPrint(
|
|
arena,
|
|
"{}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{}-{}-{}",
|
|
.{ zig_lib_dir, arch_name, os_name, abi_name },
|
|
);
|
|
const generic_include_dir = try std.fmt.allocPrint(
|
|
arena,
|
|
"{}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "generic-{}",
|
|
.{ zig_lib_dir, generic_name },
|
|
);
|
|
const arch_os_include_dir = try std.fmt.allocPrint(
|
|
arena,
|
|
"{}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{}-{}-any",
|
|
.{ zig_lib_dir, @tagName(target.cpu.arch), os_name },
|
|
);
|
|
const generic_os_include_dir = try std.fmt.allocPrint(
|
|
arena,
|
|
"{}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "any-{}-any",
|
|
.{ zig_lib_dir, os_name },
|
|
);
|
|
|
|
const list = try arena.alloc([]const u8, 4);
|
|
list[0] = arch_include_dir;
|
|
list[1] = generic_include_dir;
|
|
list[2] = arch_os_include_dir;
|
|
list[3] = generic_os_include_dir;
|
|
return LibCDirs{
|
|
.libc_include_dir_list = list,
|
|
.libc_installation = null,
|
|
};
|
|
}
|
|
|
|
if (is_native_os) {
|
|
const libc = try arena.create(LibCInstallation);
|
|
libc.* = try LibCInstallation.findNative(.{ .allocator = arena });
|
|
return detectLibCFromLibCInstallation(arena, target, libc);
|
|
}
|
|
|
|
return LibCDirs{
|
|
.libc_include_dir_list = &[0][]u8{},
|
|
.libc_installation = null,
|
|
};
|
|
}
|
|
|
|
fn detectLibCFromLibCInstallation(arena: *Allocator, target: Target, lci: *const LibCInstallation) !LibCDirs {
|
|
var list = std.ArrayList([]const u8).init(arena);
|
|
try list.ensureCapacity(4);
|
|
|
|
list.appendAssumeCapacity(lci.include_dir.?);
|
|
|
|
const is_redundant = mem.eql(u8, lci.sys_include_dir.?, lci.include_dir.?);
|
|
if (!is_redundant) list.appendAssumeCapacity(lci.sys_include_dir.?);
|
|
|
|
if (target.os.tag == .windows) {
|
|
if (std.fs.path.dirname(lci.include_dir.?)) |include_dir_parent| {
|
|
const um_dir = try std.fs.path.join(arena, &[_][]const u8{ include_dir_parent, "um" });
|
|
list.appendAssumeCapacity(um_dir);
|
|
|
|
const shared_dir = try std.fs.path.join(arena, &[_][]const u8{ include_dir_parent, "shared" });
|
|
list.appendAssumeCapacity(shared_dir);
|
|
}
|
|
}
|
|
return LibCDirs{
|
|
.libc_include_dir_list = list.items,
|
|
.libc_installation = lci,
|
|
};
|
|
}
|
|
|
|
pub fn get_libc_crt_file(mod: *Module, arena: *Allocator, basename: []const u8) ![]const u8 {
|
|
if (mod.wantBuildGLibCFromSource()) {
|
|
return mod.crt_files.get(basename).?;
|
|
}
|
|
const lci = mod.bin_file.options.libc_installation orelse return error.LibCInstallationNotAvailable;
|
|
const crt_dir_path = lci.crt_dir orelse return error.LibCInstallationMissingCRTDir;
|
|
const full_path = try std.fs.path.join(arena, &[_][]const u8{ crt_dir_path, basename });
|
|
return full_path;
|
|
}
|
|
|
|
fn addBuildingGLibCWorkItems(mod: *Module) !void {
|
|
const static_file_work_items = [_]WorkItem{
|
|
.{ .glibc_crt_file = .crti_o },
|
|
.{ .glibc_crt_file = .crtn_o },
|
|
.{ .glibc_crt_file = .start_os },
|
|
.{ .glibc_crt_file = .abi_note_o },
|
|
.{ .glibc_crt_file = .scrt1_o },
|
|
.{ .glibc_crt_file = .libc_nonshared_a },
|
|
};
|
|
try mod.work_queue.ensureUnusedCapacity(static_file_work_items.len + glibc.libs.len);
|
|
mod.work_queue.writeAssumeCapacity(&static_file_work_items);
|
|
for (glibc.libs) |*glibc_so| {
|
|
mod.work_queue.writeItemAssumeCapacity(.{ .glibc_so = glibc_so });
|
|
}
|
|
}
|
|
|
|
fn wantBuildGLibCFromSource(mod: *Module) bool {
|
|
return mod.bin_file.options.link_libc and
|
|
mod.bin_file.options.libc_installation == null and
|
|
mod.bin_file.options.target.isGnuLibC();
|
|
}
|