diff --git a/src-self-hosted/Compilation.zig b/src-self-hosted/Compilation.zig new file mode 100644 index 0000000000000000000000000000000000000000..ab105deb07f07b999128aa90b6c3002290990546 --- /dev/null +++ b/src-self-hosted/Compilation.zig @@ -0,0 +1,1527 @@ +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 Compilation'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 Compilation 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 Compilation. + 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 `Compilation`. + directory: ?Compilation.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) !*Compilation { + const comp: *Compilation = comp: { + // For allocations that have the same lifetime as Compilation. 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 `Compilation` 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(Compilation); + 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: *Compilation) 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: Compilation) Target { + return self.bin_file.options.target; +} + +/// Detect changes to source files, perform semantic analysis, and update the output files. +pub fn update(self: *Compilation) !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: *Compilation) !void { + return self.bin_file.makeExecutable(); +} + +pub fn makeBinFileWritable(self: *Compilation) !void { + return self.bin_file.makeWritable(); +} + +pub fn totalErrorCount(self: *Compilation) 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: *Compilation) !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: *Compilation) 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(comp: *Compilation, 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}-{}", + .{ comp.zig_cache_directory.path.?, comp.rand.int(u64), suffix }, + ); +} + +/// Add common C compiler args between translate-c and C object compilation. +fn addCCArgs( + comp: *Compilation, + arena: *Allocator, + argv: *std.ArrayList([]const u8), + ext: FileExt, + translate_c: bool, + out_dep_path: ?[]const u8, +) !void { + const target = comp.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 (!comp.clang_passthrough_mode) { + // Make stderr more easily parseable. + try argv.append("-fno-caret-diagnostics"); + } + + if (comp.bin_file.options.function_sections) { + try argv.append("-ffunction-sections"); + } + + try argv.ensureCapacity(argv.items.len + comp.bin_file.options.framework_dirs.len * 2); + for (comp.bin_file.options.framework_dirs) |framework_dir| { + argv.appendAssumeCapacity("-iframework"); + argv.appendAssumeCapacity(framework_dir); + } + + if (comp.bin_file.options.link_libcpp) { + const libcxx_include_path = try std.fs.path.join(arena, &[_][]const u8{ + comp.zig_lib_directory.path.?, "libcxx", "include", + }); + const libcxxabi_include_path = try std.fs.path.join(arena, &[_][]const u8{ + comp.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{ comp.zig_lib_directory.path.?, "include" }); + try argv.append("-isystem"); + try argv.append(c_headers_dir); + + for (comp.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> 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 (!comp.bin_file.options.strip) { + try argv.append("-g"); + } + + if (comp.haveFramePointer()) { + try argv.append("-fno-omit-frame-pointer"); + } else { + try argv.append("-fomit-frame-pointer"); + } + + if (comp.sanitize_c) { + try argv.append("-fsanitize=undefined"); + try argv.append("-fsanitize-trap=undefined"); + } + + switch (comp.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 (comp.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 (comp.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 comp.bin_file.options.pic) { + try argv.append("-fPIC"); + } + + try argv.appendSlice(comp.clang_argv); +} + +fn failCObj(comp: *Compilation, c_object: *CObject, comptime format: []const u8, args: anytype) InnerError { + @setCold(true); + const err_msg = try ErrorMsg.create(comp.gpa, 0, "unable to build C object: " ++ format, args); + return comp.failCObjWithOwnedErrorMsg(c_object, err_msg); +} + +fn failCObjWithOwnedErrorMsg(comp: *Compilation, c_object: *CObject, err_msg: *ErrorMsg) InnerError { + { + errdefer err_msg.destroy(comp.gpa); + try comp.failed_c_objects.ensureCapacity(comp.gpa, comp.failed_c_objects.items().len + 1); + } + comp.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(comp: *Compilation) bool { + return switch (comp.bin_file.options.optimize_mode) { + .Debug, .ReleaseSafe => !comp.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(comp: *Compilation, arena: *Allocator, basename: []const u8) ![]const u8 { + if (comp.wantBuildGLibCFromSource()) { + return comp.crt_files.get(basename).?; + } + const lci = comp.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(comp: *Compilation) !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 comp.work_queue.ensureUnusedCapacity(static_file_work_items.len + glibc.libs.len); + comp.work_queue.writeAssumeCapacity(&static_file_work_items); + for (glibc.libs) |*glibc_so| { + comp.work_queue.writeItemAssumeCapacity(.{ .glibc_so = glibc_so }); + } +} + +fn wantBuildGLibCFromSource(comp: *Compilation) bool { + return comp.bin_file.options.link_libc and + comp.bin_file.options.libc_installation == null and + comp.bin_file.options.target.isGnuLibC(); +} diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig deleted file mode 100644 index 557b5dcfcdf8e1d80c1c43bb179d363b140e16ce..0000000000000000000000000000000000000000 --- a/src-self-hosted/Module.zig +++ /dev/null @@ -1,1529 +0,0 @@ -//! 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> 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(); -} diff --git a/src-self-hosted/Package.zig b/src-self-hosted/Package.zig index 11b1436293657a597f88c7d3b4c2dec4f10b5e00..027aaafe7d5882663d1e618c66877a53943f23ea 100644 --- a/src-self-hosted/Package.zig +++ b/src-self-hosted/Package.zig @@ -1,6 +1,6 @@ pub const Table = std.StringHashMapUnmanaged(*Package); -root_src_directory: Module.Directory, +root_src_directory: Compilation.Directory, /// Relative to `root_src_directory`. root_src_path: []u8, table: Table, @@ -56,4 +56,4 @@ const mem = std.mem; const Allocator = std.mem.Allocator; const assert = std.debug.assert; const Package = @This(); -const Module = @import("Module.zig"); +const Compilation = @import("Compilation.zig"); diff --git a/src-self-hosted/ZigModule.zig b/src-self-hosted/ZigModule.zig index 1f21ce76535a659f645946d169191111410037e2..53d35fb41bd29777f11ac76bbe038d1ee9da938e 100644 --- a/src-self-hosted/ZigModule.zig +++ b/src-self-hosted/ZigModule.zig @@ -1,9 +1,7 @@ -//! TODO This is going to get renamed from ZigModule to Module (but first we have to rename -//! Module to Compilation). +//! TODO This is going to get renamed from ZigModule to Module const Module = @This(); -const Compilation = @import("Module.zig"); - const std = @import("std"); +const Compilation = @import("Compilation.zig"); const mem = std.mem; const Allocator = std.mem.Allocator; const ArrayListUnmanaged = std.ArrayListUnmanaged; diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index f35092639beb08a37fc279a1a2cc87cd71e5993c..212843a38ccda48217f0543824525564064f67e4 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -8,7 +8,7 @@ const Value = @import("value.zig").Value; const TypedValue = @import("TypedValue.zig"); const link = @import("link.zig"); const Module = @import("ZigModule.zig"); -const Compilation = @import("Module.zig"); +const Compilation = @import("Compilation.zig"); const ErrorMsg = Compilation.ErrorMsg; const Target = std.Target; const Allocator = mem.Allocator; diff --git a/src-self-hosted/glibc.zig b/src-self-hosted/glibc.zig index d366f74286280a597dee639e4f63e4e33d54ddae..3577442121c85bd0ee48be66313dfefab878dcc7 100644 --- a/src-self-hosted/glibc.zig +++ b/src-self-hosted/glibc.zig @@ -2,7 +2,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const target_util = @import("target.zig"); const mem = std.mem; -const Module = @import("Module.zig"); +const Compilation = @import("Compilation.zig"); const path = std.fs.path; const build_options = @import("build_options"); const trace = @import("tracy.zig").trace; @@ -246,11 +246,11 @@ pub const CRTFile = enum { libc_nonshared_a, }; -pub fn buildCRTFile(mod: *Module, crt_file: CRTFile) !void { +pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { if (!build_options.have_llvm) { return error.ZigCompilerNotBuiltWithLLVMExtensions; } - const gpa = mod.gpa; + const gpa = comp.gpa; var arena_allocator = std.heap.ArenaAllocator.init(gpa); errdefer arena_allocator.deinit(); const arena = &arena_allocator.allocator; @@ -258,29 +258,29 @@ pub fn buildCRTFile(mod: *Module, crt_file: CRTFile) !void { switch (crt_file) { .crti_o => { var args = std.ArrayList([]const u8).init(arena); - try add_include_dirs(mod, arena, &args); + try add_include_dirs(comp, arena, &args); try args.appendSlice(&[_][]const u8{ "-D_LIBC_REENTRANT", "-include", - try lib_path(mod, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-modules.h"), + try lib_path(comp, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-modules.h"), "-DMODULE_NAME=libc", "-Wno-nonportable-include-path", "-include", - try lib_path(mod, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-symbols.h"), + try lib_path(comp, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-symbols.h"), "-DTOP_NAMESPACE=glibc", "-DASSEMBLER", "-g", "-Wa,--noexecstack", }); - const c_source_file: Module.CSourceFile = .{ - .src_path = try start_asm_path(mod, arena, "crti.S"), + const c_source_file: Compilation.CSourceFile = .{ + .src_path = try start_asm_path(comp, arena, "crti.S"), .extra_flags = args.items, }; - return build_libc_object(mod, "crti.o", c_source_file); + return build_libc_object(comp, "crti.o", c_source_file); }, .crtn_o => { var args = std.ArrayList([]const u8).init(arena); - try add_include_dirs(mod, arena, &args); + try add_include_dirs(comp, arena, &args); try args.appendSlice(&[_][]const u8{ "-D_LIBC_REENTRANT", "-DMODULE_NAME=libc", @@ -289,23 +289,23 @@ pub fn buildCRTFile(mod: *Module, crt_file: CRTFile) !void { "-g", "-Wa,--noexecstack", }); - const c_source_file: Module.CSourceFile = .{ - .src_path = try start_asm_path(mod, arena, "crtn.S"), + const c_source_file: Compilation.CSourceFile = .{ + .src_path = try start_asm_path(comp, arena, "crtn.S"), .extra_flags = args.items, }; - return build_libc_object(mod, "crtn.o", c_source_file); + return build_libc_object(comp, "crtn.o", c_source_file); }, .start_os => { var args = std.ArrayList([]const u8).init(arena); - try add_include_dirs(mod, arena, &args); + try add_include_dirs(comp, arena, &args); try args.appendSlice(&[_][]const u8{ "-D_LIBC_REENTRANT", "-include", - try lib_path(mod, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-modules.h"), + try lib_path(comp, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-modules.h"), "-DMODULE_NAME=libc", "-Wno-nonportable-include-path", "-include", - try lib_path(mod, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-symbols.h"), + try lib_path(comp, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-symbols.h"), "-DPIC", "-DSHARED", "-DTOP_NAMESPACE=glibc", @@ -313,19 +313,19 @@ pub fn buildCRTFile(mod: *Module, crt_file: CRTFile) !void { "-g", "-Wa,--noexecstack", }); - const c_source_file: Module.CSourceFile = .{ - .src_path = try start_asm_path(mod, arena, "start.S"), + const c_source_file: Compilation.CSourceFile = .{ + .src_path = try start_asm_path(comp, arena, "start.S"), .extra_flags = args.items, }; - return build_libc_object(mod, "start.os", c_source_file); + return build_libc_object(comp, "start.os", c_source_file); }, .abi_note_o => { var args = std.ArrayList([]const u8).init(arena); try args.appendSlice(&[_][]const u8{ "-I", - try lib_path(mod, arena, lib_libc_glibc ++ "glibc" ++ path.sep_str ++ "csu"), + try lib_path(comp, arena, lib_libc_glibc ++ "glibc" ++ path.sep_str ++ "csu"), }); - try add_include_dirs(mod, arena, &args); + try add_include_dirs(comp, arena, &args); try args.appendSlice(&[_][]const u8{ "-D_LIBC_REENTRANT", "-DMODULE_NAME=libc", @@ -334,11 +334,11 @@ pub fn buildCRTFile(mod: *Module, crt_file: CRTFile) !void { "-g", "-Wa,--noexecstack", }); - const c_source_file: Module.CSourceFile = .{ - .src_path = try lib_path(mod, arena, lib_libc_glibc ++ "csu" ++ path.sep_str ++ "abi-note.S"), + const c_source_file: Compilation.CSourceFile = .{ + .src_path = try lib_path(comp, arena, lib_libc_glibc ++ "csu" ++ path.sep_str ++ "abi-note.S"), .extra_flags = args.items, }; - return build_libc_object(mod, "abi-note.o", c_source_file); + return build_libc_object(comp, "abi-note.o", c_source_file); }, .scrt1_o => { return error.Unimplemented; // TODO @@ -349,8 +349,8 @@ pub fn buildCRTFile(mod: *Module, crt_file: CRTFile) !void { } } -fn start_asm_path(mod: *Module, arena: *Allocator, basename: []const u8) ![]const u8 { - const arch = mod.getTarget().cpu.arch; +fn start_asm_path(comp: *Compilation, arena: *Allocator, basename: []const u8) ![]const u8 { + const arch = comp.getTarget().cpu.arch; const is_ppc = arch == .powerpc or arch == .powerpc64 or arch == .powerpc64le; const is_aarch64 = arch == .aarch64 or arch == .aarch64_be; const is_sparc = arch == .sparc or arch == .sparcel or arch == .sparcv9; @@ -359,7 +359,7 @@ fn start_asm_path(mod: *Module, arena: *Allocator, basename: []const u8) ![]cons const s = path.sep_str; var result = std.ArrayList(u8).init(arena); - try result.appendSlice(mod.zig_lib_directory.path.?); + try result.appendSlice(comp.zig_lib_directory.path.?); try result.appendSlice(s ++ "libc" ++ s ++ "glibc" ++ s ++ "sysdeps" ++ s); if (is_sparc) { if (is_64) { @@ -392,76 +392,76 @@ fn start_asm_path(mod: *Module, arena: *Allocator, basename: []const u8) ![]cons return result.items; } -fn add_include_dirs(mod: *Module, arena: *Allocator, args: *std.ArrayList([]const u8)) error{OutOfMemory}!void { - const target = mod.getTarget(); +fn add_include_dirs(comp: *Compilation, arena: *Allocator, args: *std.ArrayList([]const u8)) error{OutOfMemory}!void { + const target = comp.getTarget(); const arch = target.cpu.arch; const opt_nptl: ?[]const u8 = if (target.os.tag == .linux) "nptl" else "htl"; - const glibc = try lib_path(mod, arena, lib_libc ++ "glibc"); + const glibc = try lib_path(comp, arena, lib_libc ++ "glibc"); const s = path.sep_str; try args.append("-I"); - try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "include")); + try args.append(try lib_path(comp, arena, lib_libc_glibc ++ "include")); if (target.os.tag == .linux) { - try add_include_dirs_arch(arena, args, arch, null, try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix" ++ s ++ "sysv" ++ s ++ "linux")); + try add_include_dirs_arch(arena, args, arch, null, try lib_path(comp, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix" ++ s ++ "sysv" ++ s ++ "linux")); } if (opt_nptl) |nptl| { - try add_include_dirs_arch(arena, args, arch, nptl, try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps")); + try add_include_dirs_arch(arena, args, arch, nptl, try lib_path(comp, arena, lib_libc_glibc ++ "sysdeps")); } if (target.os.tag == .linux) { try args.append("-I"); - try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ + try args.append(try lib_path(comp, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix" ++ s ++ "sysv" ++ s ++ "linux" ++ s ++ "generic")); try args.append("-I"); - try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ + try args.append(try lib_path(comp, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix" ++ s ++ "sysv" ++ s ++ "linux" ++ s ++ "include")); try args.append("-I"); - try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ + try args.append(try lib_path(comp, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix" ++ s ++ "sysv" ++ s ++ "linux")); } if (opt_nptl) |nptl| { try args.append("-I"); - try args.append(try path.join(arena, &[_][]const u8{ mod.zig_lib_directory.path.?, lib_libc_glibc ++ "sysdeps", nptl })); + try args.append(try path.join(arena, &[_][]const u8{ comp.zig_lib_directory.path.?, lib_libc_glibc ++ "sysdeps", nptl })); } try args.append("-I"); - try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "pthread")); + try args.append(try lib_path(comp, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "pthread")); try args.append("-I"); - try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix" ++ s ++ "sysv")); + try args.append(try lib_path(comp, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix" ++ s ++ "sysv")); - try add_include_dirs_arch(arena, args, arch, null, try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix")); + try add_include_dirs_arch(arena, args, arch, null, try lib_path(comp, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix")); try args.append("-I"); - try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix")); + try args.append(try lib_path(comp, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "unix")); - try add_include_dirs_arch(arena, args, arch, null, try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps")); + try add_include_dirs_arch(arena, args, arch, null, try lib_path(comp, arena, lib_libc_glibc ++ "sysdeps")); try args.append("-I"); - try args.append(try lib_path(mod, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "generic")); + try args.append(try lib_path(comp, arena, lib_libc_glibc ++ "sysdeps" ++ s ++ "generic")); try args.append("-I"); - try args.append(try path.join(arena, &[_][]const u8{ mod.zig_lib_directory.path.?, lib_libc ++ "glibc" })); + try args.append(try path.join(arena, &[_][]const u8{ comp.zig_lib_directory.path.?, lib_libc ++ "glibc" })); try args.append("-I"); try args.append(try std.fmt.allocPrint(arena, "{}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{}-{}-{}", .{ - mod.zig_lib_directory.path.?, @tagName(arch), @tagName(target.os.tag), @tagName(target.abi), + comp.zig_lib_directory.path.?, @tagName(arch), @tagName(target.os.tag), @tagName(target.abi), })); try args.append("-I"); - try args.append(try lib_path(mod, arena, lib_libc ++ "include" ++ s ++ "generic-glibc")); + try args.append(try lib_path(comp, arena, lib_libc ++ "include" ++ s ++ "generic-glibc")); try args.append("-I"); try args.append(try std.fmt.allocPrint(arena, "{}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{}-linux-any", .{ - mod.zig_lib_directory.path.?, @tagName(arch), + comp.zig_lib_directory.path.?, @tagName(arch), })); try args.append("-I"); - try args.append(try lib_path(mod, arena, lib_libc ++ "include" ++ s ++ "any-linux-any")); + try args.append(try lib_path(comp, arena, lib_libc ++ "include" ++ s ++ "any-linux-any")); } fn add_include_dirs_arch( @@ -576,60 +576,60 @@ fn add_include_dirs_arch( } } -fn path_from_lib(mod: *Module, arena: *Allocator, sub_path: []const u8) ![]const u8 { - return path.join(arena, &[_][]const u8{ mod.zig_lib_directory.path.?, sub_path }); +fn path_from_lib(comp: *Compilation, arena: *Allocator, sub_path: []const u8) ![]const u8 { + return path.join(arena, &[_][]const u8{ comp.zig_lib_directory.path.?, sub_path }); } const lib_libc = "libc" ++ path.sep_str; const lib_libc_glibc = lib_libc ++ "glibc" ++ path.sep_str; -fn lib_path(mod: *Module, arena: *Allocator, sub_path: []const u8) ![]const u8 { - return path.join(arena, &[_][]const u8{ mod.zig_lib_directory.path.?, sub_path }); +fn lib_path(comp: *Compilation, arena: *Allocator, sub_path: []const u8) ![]const u8 { + return path.join(arena, &[_][]const u8{ comp.zig_lib_directory.path.?, sub_path }); } -fn build_libc_object(mod: *Module, basename: []const u8, c_source_file: Module.CSourceFile) !void { +fn build_libc_object(comp: *Compilation, basename: []const u8, c_source_file: Compilation.CSourceFile) !void { const tracy = trace(@src()); defer tracy.end(); // TODO: This is extracted into a local variable to work around a stage1 miscompilation. - const emit_bin = Module.EmitLoc{ + const emit_bin = Compilation.EmitLoc{ .directory = null, // Put it in the cache directory. .basename = basename, }; - const sub_module = try Module.create(mod.gpa, .{ + const sub_compilation = try Compilation.create(comp.gpa, .{ // TODO use the global cache directory here - .zig_cache_directory = mod.zig_cache_directory, - .zig_lib_directory = mod.zig_lib_directory, - .target = mod.getTarget(), + .zig_cache_directory = comp.zig_cache_directory, + .zig_lib_directory = comp.zig_lib_directory, + .target = comp.getTarget(), .root_name = mem.split(basename, ".").next().?, .root_pkg = null, .output_mode = .Obj, - .rand = mod.rand, - .libc_installation = mod.bin_file.options.libc_installation, + .rand = comp.rand, + .libc_installation = comp.bin_file.options.libc_installation, .emit_bin = emit_bin, - .optimize_mode = mod.bin_file.options.optimize_mode, + .optimize_mode = comp.bin_file.options.optimize_mode, .want_sanitize_c = false, .want_stack_check = false, .want_valgrind = false, - .want_pic = mod.bin_file.options.pic, + .want_pic = comp.bin_file.options.pic, .emit_h = null, - .strip = mod.bin_file.options.strip, - .is_native_os = mod.bin_file.options.is_native_os, - .self_exe_path = mod.self_exe_path, - .c_source_files = &[1]Module.CSourceFile{c_source_file}, - .debug_cc = mod.debug_cc, - .debug_link = mod.bin_file.options.debug_link, + .strip = comp.bin_file.options.strip, + .is_native_os = comp.bin_file.options.is_native_os, + .self_exe_path = comp.self_exe_path, + .c_source_files = &[1]Compilation.CSourceFile{c_source_file}, + .debug_cc = comp.debug_cc, + .debug_link = comp.bin_file.options.debug_link, }); - defer sub_module.destroy(); + defer sub_compilation.destroy(); - try sub_module.update(); + try sub_compilation.update(); - try mod.crt_files.ensureCapacity(mod.gpa, mod.crt_files.count() + 1); - const artifact_path = if (sub_module.bin_file.options.directory.path) |p| - try std.fs.path.join(mod.gpa, &[_][]const u8{ p, basename }) + try comp.crt_files.ensureCapacity(comp.gpa, comp.crt_files.count() + 1); + const artifact_path = if (sub_compilation.bin_file.options.directory.path) |p| + try std.fs.path.join(comp.gpa, &[_][]const u8{ p, basename }) else - try mod.gpa.dupe(u8, basename); + try comp.gpa.dupe(u8, basename); // TODO obtain a lock on the artifact and put that in crt_files as well. - mod.crt_files.putAssumeCapacityNoClobber(basename, artifact_path); + comp.crt_files.putAssumeCapacityNoClobber(basename, artifact_path); } diff --git a/src-self-hosted/introspect.zig b/src-self-hosted/introspect.zig index 3dd040881ccdf4e6ad7156926f5b5fcd988440fb..8041142a6793eba2a00987f56f34f6af510bdc90 100644 --- a/src-self-hosted/introspect.zig +++ b/src-self-hosted/introspect.zig @@ -2,12 +2,12 @@ const std = @import("std"); const mem = std.mem; const fs = std.fs; const CacheHash = std.cache_hash.CacheHash; -const Module = @import("Module.zig"); +const Compilation = @import("Compilation.zig"); /// Returns the sub_path that worked, or `null` if none did. /// The path of the returned Directory is relative to `base`. /// The handle of the returned Directory is open. -fn testZigInstallPrefix(base_dir: fs.Dir) ?Module.Directory { +fn testZigInstallPrefix(base_dir: fs.Dir) ?Compilation.Directory { const test_index_file = "std" ++ fs.path.sep_str ++ "std.zig"; zig_dir: { @@ -19,7 +19,7 @@ fn testZigInstallPrefix(base_dir: fs.Dir) ?Module.Directory { break :zig_dir; }; file.close(); - return Module.Directory{ .handle = test_zig_dir, .path = lib_zig }; + return Compilation.Directory{ .handle = test_zig_dir, .path = lib_zig }; } // Try lib/std/std.zig @@ -29,11 +29,11 @@ fn testZigInstallPrefix(base_dir: fs.Dir) ?Module.Directory { return null; }; file.close(); - return Module.Directory{ .handle = test_zig_dir, .path = "lib" }; + return Compilation.Directory{ .handle = test_zig_dir, .path = "lib" }; } /// Both the directory handle and the path are newly allocated resources which the caller now owns. -pub fn findZigLibDir(gpa: *mem.Allocator) !Module.Directory { +pub fn findZigLibDir(gpa: *mem.Allocator) !Compilation.Directory { const self_exe_path = try fs.selfExePathAlloc(gpa); defer gpa.free(self_exe_path); @@ -44,7 +44,7 @@ pub fn findZigLibDir(gpa: *mem.Allocator) !Module.Directory { pub fn findZigLibDirFromSelfExe( allocator: *mem.Allocator, self_exe_path: []const u8, -) error{ OutOfMemory, FileNotFound }!Module.Directory { +) error{ OutOfMemory, FileNotFound }!Compilation.Directory { const cwd = fs.cwd(); var cur_path: []const u8 = self_exe_path; while (fs.path.dirname(cur_path)) |dirname| : (cur_path = dirname) { @@ -52,7 +52,7 @@ pub fn findZigLibDirFromSelfExe( defer base_dir.close(); const sub_directory = testZigInstallPrefix(base_dir) orelse continue; - return Module.Directory{ + return Compilation.Directory{ .handle = sub_directory.handle, .path = try fs.path.join(allocator, &[_][]const u8{ dirname, sub_directory.path.? }), }; diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 50f133f792f33dd2927dd1929793d59dcb58a6cb..b2d9a0416fab4ba56d2c08f36144a61f4d85be26 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const Compilation = @import("Module.zig"); +const Compilation = @import("Compilation.zig"); const ZigModule = @import("ZigModule.zig"); const fs = std.fs; const trace = @import("tracy.zig").trace; @@ -23,7 +23,6 @@ pub const Options = struct { optimize_mode: std.builtin.Mode, root_name: []const u8, /// Not every Compilation compiles .zig code! For example you could do `zig build-exe foo.o`. - /// TODO rename Module to Compilation and then (as a separate commit) ZigModule to Module. zig_module: ?*ZigModule, dynamic_linker: ?[]const u8 = null, /// Used for calculating how much space to reserve for symbols in case the binary file diff --git a/src-self-hosted/link/C.zig b/src-self-hosted/link/C.zig index edfa05557821b207898d05323364d7df97e6612a..6cf406f35379e783024e64f649541a33458782c1 100644 --- a/src-self-hosted/link/C.zig +++ b/src-self-hosted/link/C.zig @@ -3,7 +3,7 @@ const mem = std.mem; const assert = std.debug.assert; const Allocator = std.mem.Allocator; const Module = @import("../ZigModule.zig"); -const Compilation = @import("../Module.zig"); +const Compilation = @import("../Compilation.zig"); const fs = std.fs; const codegen = @import("../codegen/c.zig"); const link = @import("../link.zig"); diff --git a/src-self-hosted/link/Coff.zig b/src-self-hosted/link/Coff.zig index 7e3fc2fa72ad3c3cf7ef43404862f03b1949ad24..7d45f61f2406b4f5c53788cab5e1e14114cabd25 100644 --- a/src-self-hosted/link/Coff.zig +++ b/src-self-hosted/link/Coff.zig @@ -8,7 +8,7 @@ const fs = std.fs; const trace = @import("../tracy.zig").trace; const Module = @import("../ZigModule.zig"); -const Compilation = @import("../Module.zig"); +const Compilation = @import("../Compilation.zig"); const codegen = @import("../codegen.zig"); const link = @import("../link.zig"); diff --git a/src-self-hosted/link/Elf.zig b/src-self-hosted/link/Elf.zig index cd8391c925ccff3961614e12a18e5fd7ae9b8d79..b88790a8cc93b3251c6b1e9ea9d0275274389ce1 100644 --- a/src-self-hosted/link/Elf.zig +++ b/src-self-hosted/link/Elf.zig @@ -4,7 +4,7 @@ const assert = std.debug.assert; const Allocator = std.mem.Allocator; const ir = @import("../ir.zig"); const Module = @import("../ZigModule.zig"); -const Compilation = @import("../Module.zig"); +const Compilation = @import("../Compilation.zig"); const fs = std.fs; const elf = std.elf; const codegen = @import("../codegen.zig"); diff --git a/src-self-hosted/link/MachO.zig b/src-self-hosted/link/MachO.zig index c412c84e9e4a8ff2c38c888dabaf5ba406326202..dd70cc7c2bc34d02bc0bcfc2593f47624fa49943 100644 --- a/src-self-hosted/link/MachO.zig +++ b/src-self-hosted/link/MachO.zig @@ -13,7 +13,7 @@ const trace = @import("../tracy.zig").trace; const Type = @import("../type.zig").Type; const Module = @import("../ZigModule.zig"); -const Compilation = @import("../Module.zig"); +const Compilation = @import("../Compilation.zig"); const link = @import("../link.zig"); const File = link.File; diff --git a/src-self-hosted/link/Wasm.zig b/src-self-hosted/link/Wasm.zig index f3586489d64ebb9d35b18e097a84c840227e7b3a..85634daca5990626751dc78b4a6d90c565409f04 100644 --- a/src-self-hosted/link/Wasm.zig +++ b/src-self-hosted/link/Wasm.zig @@ -7,7 +7,7 @@ const fs = std.fs; const leb = std.debug.leb; const Module = @import("../ZigModule.zig"); -const Compilation = @import("../Module.zig"); +const Compilation = @import("../Compilation.zig"); const codegen = @import("../codegen/wasm.zig"); const link = @import("../link.zig"); diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 6453cdaeb185bf6825ce7d95a4edb5844e646287..d0bce3e19f399ac48b6cdc7915d12cd133c29950 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -7,7 +7,7 @@ const process = std.process; const Allocator = mem.Allocator; const ArrayList = std.ArrayList; const ast = std.zig.ast; -const Module = @import("Module.zig"); +const Compilation = @import("Compilation.zig"); const link = @import("link.zig"); const Package = @import("Package.zig"); const zir = @import("zir.zig"); @@ -331,7 +331,7 @@ pub fn buildOutputType( var rpath_list = std.ArrayList([]const u8).init(gpa); defer rpath_list.deinit(); - var c_source_files = std.ArrayList(Module.CSourceFile).init(gpa); + var c_source_files = std.ArrayList(Compilation.CSourceFile).init(gpa); defer c_source_files.deinit(); var link_objects = std.ArrayList([]const u8).init(gpa); @@ -566,7 +566,7 @@ pub fn buildOutputType( mem.endsWith(u8, arg, ".lib")) { try link_objects.append(arg); - } else if (Module.hasAsmExt(arg) or Module.hasCExt(arg) or Module.hasCppExt(arg)) { + } else if (Compilation.hasAsmExt(arg) or Compilation.hasCExt(arg) or Compilation.hasCppExt(arg)) { // TODO a way to pass extra flags on the CLI try c_source_files.append(.{ .src_path = arg }); } else if (mem.endsWith(u8, arg, ".so") or @@ -611,7 +611,7 @@ pub fn buildOutputType( try clang_argv.appendSlice(it.other_args); }, .positional => { - const file_ext = Module.classifyFileExt(mem.spanZ(it.only_arg)); + const file_ext = Compilation.classifyFileExt(mem.spanZ(it.only_arg)); switch (file_ext) { .assembly, .c, .cpp, .ll, .bc, .h => try c_source_files.append(.{ .src_path = it.only_arg }), .unknown, .so => try link_objects.append(it.only_arg), @@ -998,9 +998,9 @@ pub fn buildOutputType( var cleanup_emit_bin_dir: ?fs.Dir = null; defer if (cleanup_emit_bin_dir) |*dir| dir.close(); - const emit_bin_loc: ?Module.EmitLoc = switch (emit_bin) { + const emit_bin_loc: ?Compilation.EmitLoc = switch (emit_bin) { .no => null, - .yes_default_path => Module.EmitLoc{ + .yes_default_path => Compilation.EmitLoc{ .directory = .{ .path = null, .handle = fs.cwd() }, .basename = try std.zig.binNameAlloc( arena, @@ -1016,7 +1016,7 @@ pub fn buildOutputType( if (fs.path.dirname(full_path)) |dirname| { const handle = try fs.cwd().openDir(dirname, .{}); cleanup_emit_bin_dir = handle; - break :b Module.EmitLoc{ + break :b Compilation.EmitLoc{ .basename = basename, .directory = .{ .path = dirname, @@ -1024,7 +1024,7 @@ pub fn buildOutputType( }, }; } else { - break :b Module.EmitLoc{ + break :b Compilation.EmitLoc{ .basename = basename, .directory = .{ .path = null, .handle = fs.cwd() }, }; @@ -1035,9 +1035,9 @@ pub fn buildOutputType( var cleanup_emit_h_dir: ?fs.Dir = null; defer if (cleanup_emit_h_dir) |*dir| dir.close(); - const emit_h_loc: ?Module.EmitLoc = switch (emit_h) { + const emit_h_loc: ?Compilation.EmitLoc = switch (emit_h) { .no => null, - .yes_default_path => Module.EmitLoc{ + .yes_default_path => Compilation.EmitLoc{ .directory = .{ .path = null, .handle = fs.cwd() }, .basename = try std.fmt.allocPrint(arena, "{}.h", .{root_name}), }, @@ -1046,7 +1046,7 @@ pub fn buildOutputType( if (fs.path.dirname(full_path)) |dirname| { const handle = try fs.cwd().openDir(dirname, .{}); cleanup_emit_h_dir = handle; - break :b Module.EmitLoc{ + break :b Compilation.EmitLoc{ .basename = basename, .directory = .{ .path = dirname, @@ -1054,7 +1054,7 @@ pub fn buildOutputType( }, }; } else { - break :b Module.EmitLoc{ + break :b Compilation.EmitLoc{ .basename = basename, .directory = .{ .path = null, .handle = fs.cwd() }, }; @@ -1103,7 +1103,7 @@ pub fn buildOutputType( const cache_parent_dir = if (root_pkg) |pkg| pkg.root_src_directory.handle else fs.cwd(); var cache_dir = try cache_parent_dir.makeOpenPath("zig-cache", .{}); defer cache_dir.close(); - const zig_cache_directory: Module.Directory = .{ + const zig_cache_directory: Compilation.Directory = .{ .handle = cache_dir, .path = blk: { if (root_pkg) |pkg| { @@ -1115,7 +1115,7 @@ pub fn buildOutputType( }, }; - const module = Module.create(gpa, .{ + const comp = Compilation.create(gpa, .{ .zig_lib_directory = zig_lib_directory, .zig_cache_directory = zig_cache_directory, .root_name = root_name, @@ -1170,15 +1170,15 @@ pub fn buildOutputType( .debug_cc = debug_cc, .debug_link = debug_link, }) catch |err| { - fatal("unable to create module: {}", .{@errorName(err)}); + fatal("unable to create compilation: {}", .{@errorName(err)}); }; - defer module.destroy(); + defer comp.destroy(); const stdin = std.io.getStdIn().inStream(); const stderr = std.io.getStdErr().outStream(); var repl_buf: [1024]u8 = undefined; - try updateModule(gpa, module, zir_out_path); + try updateModule(gpa, comp, zir_out_path); if (build_options.have_llvm and only_pp_or_asm) { // this may include dumping the output to stdout @@ -1188,7 +1188,7 @@ pub fn buildOutputType( while (watch) { try stderr.print("🦎 ", .{}); if (output_mode == .Exe) { - try module.makeBinFileExecutable(); + try comp.makeBinFileExecutable(); } if (stdin.readUntilDelimiterOrEof(&repl_buf, '\n') catch |err| { try stderr.print("\nUnable to parse command: {}\n", .{@errorName(err)}); @@ -1198,9 +1198,9 @@ pub fn buildOutputType( if (mem.eql(u8, actual_line, "update")) { if (output_mode == .Exe) { - try module.makeBinFileWritable(); + try comp.makeBinFileWritable(); } - try updateModule(gpa, module, zir_out_path); + try updateModule(gpa, comp, zir_out_path); } else if (mem.eql(u8, actual_line, "exit")) { break; } else if (mem.eql(u8, actual_line, "help")) { @@ -1214,11 +1214,11 @@ pub fn buildOutputType( } } -fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !void { - try module.update(); +fn updateModule(gpa: *Allocator, comp: *Compilation, zir_out_path: ?[]const u8) !void { + try comp.update(); - var errors = try module.getAllErrorsAlloc(); - defer errors.deinit(module.gpa); + var errors = try comp.getAllErrorsAlloc(); + defer errors.deinit(comp.gpa); if (errors.list.len != 0) { for (errors.list) |full_err_msg| { @@ -1232,7 +1232,7 @@ fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !vo } if (zir_out_path) |zop| { - const zig_module = module.bin_file.options.zig_module orelse + const zig_module = comp.bin_file.options.zig_module orelse fatal("-femit-zir with no zig source code", .{}); var new_zir_module = try zir.emit(gpa, zig_module); defer new_zir_module.deinit(gpa); diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 40eb8332732aa8f42eebaca81631ead95b79352f..edff8aa2627b2ac29d48cebefc697ec80c170f77 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -1,6 +1,6 @@ const std = @import("std"); const link = @import("link.zig"); -const Module = @import("Module.zig"); +const Compilation = @import("Compilation.zig"); const Allocator = std.mem.Allocator; const zir = @import("zir.zig"); const Package = @import("Package.zig"); @@ -61,7 +61,7 @@ pub const TestContext = struct { ZIR, }; - /// A Case consists of a set of *updates*. The same Module is used for each + /// A Case consists of a set of *updates*. The same Compilation is used for each /// update, so each update's source is treated as a single file being /// updated by the test harness and incrementally compiled. pub const Case = struct { @@ -437,7 +437,7 @@ pub const TestContext = struct { allocator: *Allocator, root_node: *std.Progress.Node, case: Case, - zig_lib_directory: Module.Directory, + zig_lib_directory: Compilation.Directory, rand: *std.rand.Random, ) !void { const target_info = try std.zig.system.NativeTargetInfo.detect(allocator, case.target); @@ -453,7 +453,7 @@ pub const TestContext = struct { var cache_dir = try tmp.dir.makeOpenPath("zig-cache", .{}); defer cache_dir.close(); const bogus_path = "bogus"; // TODO this will need to be fixed before we can test LLVM extensions - const zig_cache_directory: Module.Directory = .{ + const zig_cache_directory: Compilation.Directory = .{ .handle = cache_dir, .path = try std.fs.path.join(arena, &[_][]const u8{ bogus_path, "zig-cache" }), }; @@ -465,15 +465,15 @@ pub const TestContext = struct { const ofmt: ?std.builtin.ObjectFormat = if (case.cbe) .c else null; const bin_name = try std.zig.binNameAlloc(arena, "test_case", target, case.output_mode, null, ofmt); - const emit_directory: Module.Directory = .{ + const emit_directory: Compilation.Directory = .{ .path = bogus_path, .handle = tmp.dir, }; - const emit_bin: Module.EmitLoc = .{ + const emit_bin: Compilation.EmitLoc = .{ .directory = emit_directory, .basename = bin_name, }; - const module = try Module.create(allocator, .{ + const comp = try Compilation.create(allocator, .{ .zig_cache_directory = zig_cache_directory, .zig_lib_directory = zig_lib_directory, .rand = rand, @@ -491,7 +491,7 @@ pub const TestContext = struct { .object_format = ofmt, .is_native_os = case.target.isNativeOs(), }); - defer module.destroy(); + defer comp.destroy(); for (case.updates.items) |update, update_index| { var update_node = root_node.start("update", 3); @@ -505,20 +505,20 @@ pub const TestContext = struct { var module_node = update_node.start("parse/analysis/codegen", null); module_node.activate(); - try module.makeBinFileWritable(); - try module.update(); + try comp.makeBinFileWritable(); + try comp.update(); module_node.end(); if (update.case != .Error) { - var all_errors = try module.getAllErrorsAlloc(); + var all_errors = try comp.getAllErrorsAlloc(); defer all_errors.deinit(allocator); if (all_errors.list.len != 0) { - std.debug.print("\nErrors occurred updating the module:\n================\n", .{}); + std.debug.print("\nErrors occurred updating the compilation:\n================\n", .{}); for (all_errors.list) |err| { std.debug.print(":{}:{}: error: {}\n================\n", .{ err.line + 1, err.column + 1, err.msg }); } if (case.cbe) { - const C = module.bin_file.cast(link.File.C).?; + const C = comp.bin_file.cast(link.File.C).?; std.debug.print("Generated C: \n===============\n{}\n\n===========\n\n", .{C.main.items}); } std.debug.print("Test failed.\n", .{}); @@ -549,7 +549,7 @@ pub const TestContext = struct { update_node.estimated_total_items = 5; var emit_node = update_node.start("emit", null); emit_node.activate(); - var new_zir_module = try zir.emit(allocator, module.bin_file.options.zig_module.?); + var new_zir_module = try zir.emit(allocator, comp.bin_file.options.zig_module.?); defer new_zir_module.deinit(allocator); emit_node.end(); @@ -584,7 +584,7 @@ pub const TestContext = struct { for (handled_errors) |*h| { h.* = false; } - var all_errors = try module.getAllErrorsAlloc(); + var all_errors = try comp.getAllErrorsAlloc(); defer all_errors.deinit(allocator); for (all_errors.list) |a| { for (e) |ex, i| { @@ -666,7 +666,7 @@ pub const TestContext = struct { }, } - try module.makeBinFileExecutable(); + try comp.makeBinFileExecutable(); break :x try std.ChildProcess.exec(.{ .allocator = allocator,