diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 04b6cc5ca7..10a26a4aa8 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -10,6 +10,7 @@ const log = std.log.scoped(.module); const BigIntConst = std.math.big.int.Const; const BigIntMutable = std.math.big.int.Mutable; const Target = std.Target; +const target_util = @import("target.zig"); const Package = @import("Package.zig"); const link = @import("link.zig"); const ir = @import("ir.zig"); @@ -26,6 +27,8 @@ const build_options = @import("build_options"); /// 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, /// Pointer to externally managed resource. `null` if there is no zig file being compiled. root_pkg: ?*Package, /// Module owns this resource. @@ -85,6 +88,12 @@ deletion_set: std.ArrayListUnmanaged(*Decl) = .{}, root_name: []u8, 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, /// Error tags and their values, tag names are duped with mod.gpa. global_error_set: std.StringHashMapUnmanaged(u16) = .{}, @@ -92,6 +101,12 @@ global_error_set: std.StringHashMapUnmanaged(u16) = .{}, c_source_files: []const []const u8, clang_argv: []const []const u8, cache: std.cache_hash.CacheHash, +/// Path to own executable for invoking `zig clang`. +self_exe_path: ?[]const u8, +zig_lib_dir: []const u8, +zig_cache_dir_path: []const u8, +libc_include_dir_list: []const []const u8, +rand: *std.rand.Random, pub const InnerError = error{ OutOfMemory, AnalysisFail }; @@ -913,10 +928,12 @@ pub const AllErrors = struct { }; pub const InitOptions = struct { - target: std.Target, + zig_lib_dir: []const u8, + target: Target, root_name: []const u8, root_pkg: ?*Package, output_mode: std.builtin.OutputMode, + rand: *std.rand.Random, bin_file_dir: ?std.fs.Dir = null, bin_file_path: []const u8, emit_h: ?[]const u8 = null, @@ -932,8 +949,8 @@ pub const InitOptions = struct { framework_dirs: []const []const u8 = &[0][]const u8{}, frameworks: []const []const u8 = &[0][]const u8{}, system_libs: []const []const u8 = &[0][]const u8{}, - have_libc: bool = false, - have_libcpp: bool = false, + link_libc: bool = false, + link_libcpp: bool = false, want_pic: ?bool = null, want_sanitize_c: ?bool = null, use_llvm: ?bool = null, @@ -943,170 +960,232 @@ pub const InitOptions = struct { strip: bool = false, linker_script: ?[]const u8 = null, version_script: ?[]const u8 = null, - disable_c_depfile: bool = false, override_soname: ?[]const u8 = null, linker_optimization: ?[]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, stack_size_override: u64 = 0, + self_exe_path: ?[]const u8 = null, }; -pub fn init(gpa: *Allocator, options: InitOptions) !Module { - const root_name = try gpa.dupe(u8, options.root_name); - errdefer gpa.free(root_name); +pub fn create(gpa: *Allocator, options: InitOptions) !*Module { + const mod: *Module = mod: { + // 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; - const ofmt = options.object_format orelse options.target.getObjectFormat(); + // 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 mod = try arena.create(Module); + const root_name = try arena.dupe(u8, options.root_name); - // 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; + const ofmt = options.object_format orelse options.target.getObjectFormat(); - if (ofmt == .c) - break :blk false; + // 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; - // 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.have_libc or options.have_libcpp or - options.linker_script != null or options.version_script != null) - { - break :blk true; - } - break :blk false; - }; + if (ofmt == .c) + 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 bin_file_dir = options.bin_file_dir orelse std.fs.cwd(); - const bin_file = try link.File.openPath(gpa, bin_file_dir, options.bin_file_path, .{ - .root_name = root_name, - .root_pkg = options.root_pkg, - .target = options.target, - .output_mode = options.output_mode, - .link_mode = options.link_mode orelse .Static, - .object_format = ofmt, - .optimize_mode = options.optimize_mode, - .use_lld = use_lld, - .use_llvm = use_llvm, - .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, - }); - errdefer bin_file.destroy(); - - const root_scope = blk: { - if (options.root_pkg) |root_pkg| { - if (mem.endsWith(u8, root_pkg.root_src_path, ".zig")) { - const root_scope = try gpa.create(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 :blk &root_scope.base; - } else if (mem.endsWith(u8, root_pkg.root_src_path, ".zir")) { - const root_scope = try gpa.create(Scope.ZIRModule); - root_scope.* = .{ - .sub_file_path = root_pkg.root_src_path, - .source = .{ .unloaded = {} }, - .contents = .{ .not_available = {} }, - .status = .never_loaded, - .decls = .{}, - }; - break :blk &root_scope.base; - } else { - unreachable; + // 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.linker_script != null or options.version_script != null) + { + break :blk true; } - } else { - const root_scope = try gpa.create(Scope.None); - root_scope.* = .{}; - break :blk &root_scope.base; - } - }; - - // We put everything into the cache hash except for the root source file, because we want to - // find the same binary and incrementally update it even if the file contents changed. - // TODO Look into storing this information in memory rather than on disk and solving - // serialization/deserialization of *all* incremental compilation state in a more generic way. - const cache_dir = if (options.root_pkg) |root_pkg| root_pkg.root_src_dir else std.fs.cwd(); - var cache = try std.cache_hash.CacheHash.init(gpa, cache_dir, "zig-cache"); - errdefer cache.release(); - - // Now we will prepare hash state initializations to avoid redundantly computing hashes. - // First we add common things between things that apply to zig source and all c source files. - cache.addBytes(build_options.version); - cache.add(options.optimize_mode); - cache.add(options.target.cpu.arch); - cache.addBytes(options.target.cpu.model.name); - cache.add(options.target.cpu.features.ints); - cache.add(options.target.os.tag); - switch (options.target.os.tag) { - .linux => { - cache.add(options.target.os.version_range.linux.range.min); - cache.add(options.target.os.version_range.linux.range.max); - cache.add(options.target.os.version_range.linux.glibc); - }, - .windows => { - cache.add(options.target.os.version_range.windows.min); - cache.add(options.target.os.version_range.windows.max); - }, - .freebsd, - .macosx, - .ios, - .tvos, - .watchos, - .netbsd, - .openbsd, - .dragonfly, - => { - cache.add(options.target.os.version_range.semver.min); - cache.add(options.target.os.version_range.semver.max); - }, - else => {}, - } - cache.add(options.target.abi); - cache.add(ofmt); - // TODO PIC (see detect_pic from codegen.cpp) - cache.add(bin_file.options.link_mode); - cache.add(options.strip); - - // 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; + }; + + // 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 bin_file_dir = options.bin_file_dir orelse std.fs.cwd(); + const bin_file = try link.File.openPath(gpa, bin_file_dir, options.bin_file_path, .{ + .root_name = root_name, + .root_pkg = options.root_pkg, + .target = options.target, + .output_mode = options.output_mode, + .link_mode = options.link_mode orelse .Static, + .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, + .function_sections = options.function_sections orelse false, + }); + errdefer bin_file.destroy(); + + // We arena-allocate the root scope so there is no free needed. + const root_scope = blk: { + if (options.root_pkg) |root_pkg| { + if (mem.endsWith(u8, root_pkg.root_src_path, ".zig")) { + const root_scope = try gpa.create(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 :blk &root_scope.base; + } else if (mem.endsWith(u8, root_pkg.root_src_path, ".zir")) { + const root_scope = try gpa.create(Scope.ZIRModule); + root_scope.* = .{ + .sub_file_path = root_pkg.root_src_path, + .source = .{ .unloaded = {} }, + .contents = .{ .not_available = {} }, + .status = .never_loaded, + .decls = .{}, + }; + break :blk &root_scope.base; + } else { + unreachable; + } + } else { + const root_scope = try gpa.create(Scope.None); + root_scope.* = .{}; + break :blk &root_scope.base; + } + }; + + // We put everything into the cache hash except for the root source file, because we want to + // find the same binary and incrementally update it even if the file contents changed. + // TODO Look into storing this information in memory rather than on disk and solving + // serialization/deserialization of *all* incremental compilation state in a more generic way. + const cache_parent_dir = if (options.root_pkg) |root_pkg| root_pkg.root_src_dir else std.fs.cwd(); + var cache_dir = try cache_parent_dir.makeOpenPath("zig-cache", .{}); + defer cache_dir.close(); + + try cache_dir.makePath("tmp"); + try cache_dir.makePath("o"); + // We need this string because of sending paths to clang as a child process. + const zig_cache_dir_path = if (options.root_pkg) |root_pkg| + try std.fmt.allocPrint(arena, "{}" ++ std.fs.path.sep_str ++ "zig-cache", .{root_pkg.root_src_dir_path}) + else + "zig-cache"; + + var cache = try std.cache_hash.CacheHash.init(gpa, cache_dir, "h"); + errdefer cache.release(); + + // Now we will prepare hash state initializations to avoid redundantly computing hashes. + // First we add common things between things that apply to zig source and all c source files. + cache.addBytes(build_options.version); + cache.add(options.optimize_mode); + cache.add(options.target.cpu.arch); + cache.addBytes(options.target.cpu.model.name); + cache.add(options.target.cpu.features.ints); + cache.add(options.target.os.tag); + switch (options.target.os.tag) { + .linux => { + cache.add(options.target.os.version_range.linux.range.min); + cache.add(options.target.os.version_range.linux.range.max); + cache.add(options.target.os.version_range.linux.glibc); + }, + .windows => { + cache.add(options.target.os.version_range.windows.min); + cache.add(options.target.os.version_range.windows.max); + }, + .freebsd, + .macosx, + .ios, + .tvos, + .watchos, + .netbsd, + .openbsd, + .dragonfly, + => { + cache.add(options.target.os.version_range.semver.min); + cache.add(options.target.os.version_range.semver.max); + }, + else => {}, } - // It's not planned to do our own translate-c or C compilation. - break :blk true; + cache.add(options.target.abi); + cache.add(ofmt); + // TODO PIC (see detect_pic from codegen.cpp) + cache.add(bin_file.options.link_mode); + cache.add(options.strip); + + // 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 libc_include_dir_list = try detectLibCIncludeDirs( + arena, + options.zig_lib_dir, + options.target, + options.link_libc, + ); + + const sanitize_c: bool = options.want_sanitize_c orelse switch (options.optimize_mode) { + .Debug, .ReleaseSafe => true, + .ReleaseSmall, .ReleaseFast => false, + }; + + mod.* = .{ + .gpa = gpa, + .arena_state = arena_allocator.state, + .zig_lib_dir = options.zig_lib_dir, + .zig_cache_dir_path = zig_cache_dir_path, + .root_name = root_name, + .root_pkg = options.root_pkg, + .root_scope = root_scope, + .bin_file_dir = bin_file_dir, + .bin_file_path = options.bin_file_path, + .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 = cache, + .self_exe_path = options.self_exe_path, + .libc_include_dir_list = libc_include_dir_list, + .sanitize_c = sanitize_c, + .rand = options.rand, + .clang_passthrough_mode = options.clang_passthrough_mode, + }; + break :mod mod; }; - var c_object_table = std.AutoArrayHashMapUnmanaged(*CObject, void){}; - errdefer { - for (c_object_table.items()) |entry| entry.key.destroy(gpa); - c_object_table.deinit(gpa); - } + errdefer mod.destroy(); + // Add a `CObject` for each `c_source_files`. - try c_object_table.ensureCapacity(gpa, options.c_source_files.len); + try mod.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(); @@ -1120,31 +1199,15 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { .extra_flags = &[0][]const u8{}, .arena = local_arena.state, }; - c_object_table.putAssumeCapacityNoClobber(c_object, {}); + mod.c_object_table.putAssumeCapacityNoClobber(c_object, {}); } - return Module{ - .gpa = gpa, - .root_name = root_name, - .root_pkg = options.root_pkg, - .root_scope = root_scope, - .bin_file_dir = bin_file_dir, - .bin_file_path = options.bin_file_path, - .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 = cache, - .c_object_table = c_object_table, - }; + return mod; } -pub fn deinit(self: *Module) void { +pub fn destroy(self: *Module) void { self.bin_file.destroy(); const gpa = self.gpa; - self.gpa.free(self.root_name); self.deletion_set.deinit(gpa); self.work_queue.deinit(); @@ -1198,7 +1261,9 @@ pub fn deinit(self: *Module) void { } self.global_error_set.deinit(gpa); self.cache.release(); - self.* = undefined; + + // This destroys `self`. + self.arena_state.promote(gpa).deinit(); } fn freeExportList(gpa: *Allocator, export_list: []*Export) void { @@ -1209,7 +1274,7 @@ fn freeExportList(gpa: *Allocator, export_list: []*Export) void { gpa.free(export_list); } -pub fn target(self: Module) std.Target { +pub fn getTarget(self: Module) Target { return self.bin_file.options.target; } @@ -1440,29 +1505,335 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { c_object.status = .{ .new = {} }; }, } - if (!build_options.have_llvm) { - 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, - "clang not available: compiler not built with LLVM extensions enabled", - .{}, - )); - c_object.status = .{ .failure = "" }; - continue; - } - 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, - "TODO: implement invoking clang to compile C source files", - .{}, - )); - c_object.status = .{ .failure = "" }; + self.buildCObject(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 = "" }; + }, + }; }, }; } +fn buildCObject(mod: *Module, c_object: *CObject) !void { + const tracy = trace(@src()); + defer tracy.end(); + + if (!build_options.have_llvm) { + return mod.failCObj(c_object, "clang not available: compiler not built with LLVM extensions enabled", .{}); + } + const self_exe_path = mod.self_exe_path orelse + return mod.failCObj(c_object, "clang compilation disabled", .{}); + + var arena_allocator = std.heap.ArenaAllocator.init(mod.gpa); + defer arena_allocator.deinit(); + const arena = &arena_allocator.allocator; + + var argv = std.ArrayList([]const u8).init(mod.gpa); + defer argv.deinit(); + + 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 = mod.c_source_files.len == 1 and mod.root_pkg == null and + mod.bin_file.options.output_mode == .Obj and mod.bin_file.options.objects.len == 0; + const o_basename_noext = if (direct_o) mod.root_name else mem.split(c_source_basename, ".").next().?; + const o_basename = try std.fmt.allocPrint(arena, "{}{}", .{ o_basename_noext, mod.getTarget().oFileExt() }); + + // We can't know the digest until we do the C compiler invocation, so we need a temporary filename. + const out_obj_path = try mod.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 mod.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); + + //for (argv.items) |arg| { + // std.debug.print("{} ", .{arg}); + //} + + const child = try std.ChildProcess.init(argv.items, arena); + defer child.deinit(); + + if (mod.clang_passthrough_mode) { + child.stdin_behavior = .Inherit; + child.stdout_behavior = .Inherit; + child.stderr_behavior = .Inherit; + + const term = child.spawnAndWait() catch |err| { + return mod.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 mod.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 mod.failCObj(c_object, "clang exited with code {}", .{code}); + } + }, + else => { + std.log.err("clang terminated with stderr: {}", .{stderr}); + return mod.failCObj(c_object, "clang terminated unexpectedly", .{}); + }, + } + } + + // TODO handle .d files + + // TODO rename into place + std.debug.print("TODO rename {} into cache dir\n", .{out_obj_path}); + + // TODO use the cache file name instead of tmp file name + const success_file_path = try mod.gpa.dupe(u8, out_obj_path); + c_object.status = .{ .success = success_file_path }; +} + +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_dir_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_dir, "libcxx", "include", + }); + const libcxxabi_include_path = try std.fs.path.join(arena, &[_][]const u8{ + mod.zig_lib_dir, "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_dir, "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); + } + }, + .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"); + }, + } + + // TODO add CLI args for PIC + //if (target_supports_fpic(g->zig_target) and g->have_pic) { + // try argv.append("-fPIC"); + //} + + try argv.appendSlice(mod.clang_argv); +} + pub fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { const tracy = trace(@src()); defer tracy.end(); @@ -3041,7 +3412,7 @@ pub fn cmpNumeric( } else if (rhs_ty_tag == .ComptimeFloat) { break :x lhs.ty; } - if (lhs.ty.floatBits(self.target()) >= rhs.ty.floatBits(self.target())) { + if (lhs.ty.floatBits(self.getTarget()) >= rhs.ty.floatBits(self.getTarget())) { break :x lhs.ty; } else { break :x rhs.ty; @@ -3100,7 +3471,7 @@ pub fn cmpNumeric( } else if (lhs_is_float) { dest_float_type = lhs.ty; } else { - const int_info = lhs.ty.intInfo(self.target()); + const int_info = lhs.ty.intInfo(self.getTarget()); lhs_bits = int_info.bits + @boolToInt(!int_info.signed and dest_int_is_signed); } @@ -3135,7 +3506,7 @@ pub fn cmpNumeric( } else if (rhs_is_float) { dest_float_type = rhs.ty; } else { - const int_info = rhs.ty.intInfo(self.target()); + const int_info = rhs.ty.intInfo(self.getTarget()); rhs_bits = int_info.bits + @boolToInt(!int_info.signed and dest_int_is_signed); } @@ -3200,13 +3571,13 @@ pub fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Ty next_inst.ty.isInt() and prev_inst.ty.isSignedInt() == next_inst.ty.isSignedInt()) { - if (prev_inst.ty.intInfo(self.target()).bits < next_inst.ty.intInfo(self.target()).bits) { + if (prev_inst.ty.intInfo(self.getTarget()).bits < next_inst.ty.intInfo(self.getTarget()).bits) { prev_inst = next_inst; } continue; } if (prev_inst.ty.isFloat() and next_inst.ty.isFloat()) { - if (prev_inst.ty.floatBits(self.target()) < next_inst.ty.floatBits(self.target())) { + if (prev_inst.ty.floatBits(self.getTarget()) < next_inst.ty.floatBits(self.getTarget())) { prev_inst = next_inst; } continue; @@ -3274,8 +3645,8 @@ pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst if (inst.ty.zigTypeTag() == .Int and dest_type.zigTypeTag() == .Int) { assert(inst.value() == null); // handled above - const src_info = inst.ty.intInfo(self.target()); - const dst_info = dest_type.intInfo(self.target()); + const src_info = inst.ty.intInfo(self.getTarget()); + const dst_info = dest_type.intInfo(self.getTarget()); if ((src_info.signed == dst_info.signed and dst_info.bits >= src_info.bits) or // small enough unsigned ints can get casted to large enough signed ints (src_info.signed and !dst_info.signed and dst_info.bits > src_info.bits)) @@ -3289,8 +3660,8 @@ pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst if (inst.ty.zigTypeTag() == .Float and dest_type.zigTypeTag() == .Float) { assert(inst.value() == null); // handled above - const src_bits = inst.ty.floatBits(self.target()); - const dst_bits = dest_type.floatBits(self.target()); + const src_bits = inst.ty.floatBits(self.getTarget()); + const dst_bits = dest_type.floatBits(self.getTarget()); if (dst_bits >= src_bits) { const b = try self.requireRuntimeBlock(scope, inst.src); return self.addUnOp(b, inst.src, dest_type, .floatcast, inst); @@ -3312,14 +3683,14 @@ pub fn coerceNum(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !?* } return self.fail(scope, inst.src, "TODO float to int", .{}); } else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) { - if (!val.intFitsInType(dest_type, self.target())) { + if (!val.intFitsInType(dest_type, self.getTarget())) { return self.fail(scope, inst.src, "type {} cannot represent integer value {}", .{ inst.ty, val }); } return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val }); } } else if (dst_zig_tag == .ComptimeFloat or dst_zig_tag == .Float) { if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) { - const res = val.floatCast(scope.arena(), dest_type, self.target()) catch |err| switch (err) { + const res = val.floatCast(scope.arena(), dest_type, self.getTarget()) catch |err| switch (err) { error.Overflow => return self.fail( scope, inst.src, @@ -3370,6 +3741,22 @@ fn coerceArrayPtrToSlice(self: *Module, scope: *Scope, dest_type: Type, inst: *I return self.fail(scope, inst.src, "TODO implement coerceArrayPtrToSlice runtime instruction", .{}); } +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 fn fail(self: *Module, scope: *Scope, src: usize, comptime format: []const u8, args: anytype) InnerError { @setCold(true); const err_msg = try ErrorMsg.create(self.gpa, src, format, args); @@ -3560,7 +3947,7 @@ pub fn intSub(allocator: *Allocator, lhs: Value, rhs: Value) !Value { pub fn floatAdd(self: *Module, scope: *Scope, float_type: Type, src: usize, lhs: Value, rhs: Value) !Value { var bit_count = switch (float_type.tag()) { .comptime_float => 128, - else => float_type.floatBits(self.target()), + else => float_type.floatBits(self.getTarget()), }; const allocator = scope.arena(); @@ -3594,7 +3981,7 @@ pub fn floatAdd(self: *Module, scope: *Scope, float_type: Type, src: usize, lhs: pub fn floatSub(self: *Module, scope: *Scope, float_type: Type, src: usize, lhs: Value, rhs: Value) !Value { var bit_count = switch (float_type.tag()) { .comptime_float => 128, - else => float_type.floatBits(self.target()), + else => float_type.floatBits(self.getTarget()), }; const allocator = scope.arena(); @@ -3865,3 +4252,106 @@ pub fn safetyPanic(mod: *Module, block: *Scope.Block, src: usize, panic_id: Pani _ = try mod.addNoOp(block, src, Type.initTag(.void), .breakpoint); return mod.addNoOp(block, src, Type.initTag(.noreturn), .unreach); } + +pub const FileExt = enum { + c, + cpp, + h, + ll, + bc, + assembly, + 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 { + // TODO look for .so, .so.X, .so.X.Y, .so.X.Y.Z + return .unknown; + } +} + +fn haveFramePointer(mod: *Module) bool { + return switch (mod.bin_file.options.optimize_mode) { + .Debug, .ReleaseSafe => !mod.bin_file.options.strip, + .ReleaseSmall, .ReleaseFast => false, + }; +} + +fn detectLibCIncludeDirs( + arena: *Allocator, + zig_lib_dir: []const u8, + target: Target, + link_libc: bool, +) ![]const []const u8 { + if (!link_libc) return &[0][]u8{}; + + // TODO Support --libc file explicitly providing libc paths. Or not? Maybe we are better off + // deleting that feature. + + 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 list; + } + + // TODO finish porting detect_libc from codegen.cpp + return error.LibCDetectionUnimplemented; +} diff --git a/src-self-hosted/codegen/llvm.zig b/src-self-hosted/codegen/llvm.zig new file mode 100644 index 0000000000..01fa0baf02 --- /dev/null +++ b/src-self-hosted/codegen/llvm.zig @@ -0,0 +1,125 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +pub fn targetTriple(allocator: *Allocator, target: std.Target) ![]u8 { + const llvm_arch = switch (target.cpu.arch) { + .arm => "arm", + .armeb => "armeb", + .aarch64 => "aarch64", + .aarch64_be => "aarch64_be", + .aarch64_32 => "aarch64_32", + .arc => "arc", + .avr => "avr", + .bpfel => "bpfel", + .bpfeb => "bpfeb", + .hexagon => "hexagon", + .mips => "mips", + .mipsel => "mipsel", + .mips64 => "mips64", + .mips64el => "mips64el", + .msp430 => "msp430", + .powerpc => "powerpc", + .powerpc64 => "powerpc64", + .powerpc64le => "powerpc64le", + .r600 => "r600", + .amdgcn => "amdgcn", + .riscv32 => "riscv32", + .riscv64 => "riscv64", + .sparc => "sparc", + .sparcv9 => "sparcv9", + .sparcel => "sparcel", + .s390x => "s390x", + .tce => "tce", + .tcele => "tcele", + .thumb => "thumb", + .thumbeb => "thumbeb", + .i386 => "i386", + .x86_64 => "x86_64", + .xcore => "xcore", + .nvptx => "nvptx", + .nvptx64 => "nvptx64", + .le32 => "le32", + .le64 => "le64", + .amdil => "amdil", + .amdil64 => "amdil64", + .hsail => "hsail", + .hsail64 => "hsail64", + .spir => "spir", + .spir64 => "spir64", + .kalimba => "kalimba", + .shave => "shave", + .lanai => "lanai", + .wasm32 => "wasm32", + .wasm64 => "wasm64", + .renderscript32 => "renderscript32", + .renderscript64 => "renderscript64", + .ve => "ve", + .spu_2 => return error.LLVMBackendDoesNotSupportSPUMarkII, + }; + // TODO Add a sub-arch for some architectures depending on CPU features. + + const llvm_os = switch (target.os.tag) { + .freestanding => "unknown", + .ananas => "ananas", + .cloudabi => "cloudabi", + .dragonfly => "dragonfly", + .freebsd => "freebsd", + .fuchsia => "fuchsia", + .ios => "ios", + .kfreebsd => "kfreebsd", + .linux => "linux", + .lv2 => "lv2", + .macosx => "macosx", + .netbsd => "netbsd", + .openbsd => "openbsd", + .solaris => "solaris", + .windows => "windows", + .haiku => "haiku", + .minix => "minix", + .rtems => "rtems", + .nacl => "nacl", + .cnk => "cnk", + .aix => "aix", + .cuda => "cuda", + .nvcl => "nvcl", + .amdhsa => "amdhsa", + .ps4 => "ps4", + .elfiamcu => "elfiamcu", + .tvos => "tvos", + .watchos => "watchos", + .mesa3d => "mesa3d", + .contiki => "contiki", + .amdpal => "amdpal", + .hermit => "hermit", + .hurd => "hurd", + .wasi => "wasi", + .emscripten => "emscripten", + .uefi => "windows", + .other => "unknown", + }; + + const llvm_abi = switch (target.abi) { + .none => "unknown", + .gnu => "gnu", + .gnuabin32 => "gnuabin32", + .gnuabi64 => "gnuabi64", + .gnueabi => "gnueabi", + .gnueabihf => "gnueabihf", + .gnux32 => "gnux32", + .code16 => "code16", + .eabi => "eabi", + .eabihf => "eabihf", + .android => "android", + .musl => "musl", + .musleabi => "musleabi", + .musleabihf => "musleabihf", + .msvc => "msvc", + .itanium => "itanium", + .cygnus => "cygnus", + .coreclr => "coreclr", + .simulator => "simulator", + .macabi => "macabi", + }; + + return std.fmt.allocPrint(allocator, "{}-unknown-{}-{}", .{ llvm_arch, llvm_os, llvm_abi }); +} diff --git a/src-self-hosted/introspect.zig b/src-self-hosted/introspect.zig index 80f10c8656..019da4a39e 100644 --- a/src-self-hosted/introspect.zig +++ b/src-self-hosted/introspect.zig @@ -93,46 +93,3 @@ pub fn openGlobalCacheDir() !fs.Dir { const path_name = try resolveGlobalCacheDir(&fba.allocator); return fs.cwd().makeOpenPath(path_name, .{}); } - -var compiler_id_mutex = std.Mutex{}; -var compiler_id: [16]u8 = undefined; -var compiler_id_computed = false; - -pub fn resolveCompilerId(gpa: *mem.Allocator) ![16]u8 { - const held = compiler_id_mutex.acquire(); - defer held.release(); - - if (compiler_id_computed) - return compiler_id; - compiler_id_computed = true; - - var cache_dir = try openGlobalCacheDir(); - defer cache_dir.close(); - - var ch = try CacheHash.init(gpa, cache_dir, "exe"); - defer ch.release(); - - const self_exe_path = try fs.selfExePathAlloc(gpa); - defer gpa.free(self_exe_path); - - _ = try ch.addFile(self_exe_path, null); - - if (try ch.hit()) |digest| { - compiler_id = digest[0..16].*; - return compiler_id; - } - - const libs = try std.process.getSelfExeSharedLibPaths(gpa); - defer { - for (libs) |lib| gpa.free(lib); - gpa.free(libs); - } - - for (libs) |lib| { - try ch.addFilePost(lib); - } - - const digest = ch.final(); - compiler_id = digest[0..16].*; - return compiler_id; -} diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 14166e7e30..3b07899aec 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -35,6 +35,9 @@ pub const Options = struct { /// other objects. /// Otherwise (depending on `use_lld`) this link code directly outputs and updates the final binary. use_llvm: bool = false, + link_libc: bool = false, + link_libcpp: bool = false, + function_sections: bool = false, objects: []const []const u8 = &[0][]const u8{}, framework_dirs: []const []const u8 = &[0][]const u8{}, diff --git a/src-self-hosted/link/Elf.zig b/src-self-hosted/link/Elf.zig index 8b75662d35..4bb2a4e940 100644 --- a/src-self-hosted/link/Elf.zig +++ b/src-self-hosted/link/Elf.zig @@ -219,8 +219,11 @@ pub const SrcFn = struct { pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: link.Options) !*File { assert(options.object_format == .elf); - if (options.use_llvm) return error.LLVM_BackendIsTODO_ForELF; // TODO - if (options.use_lld) return error.LLD_LinkingIsTODOForELF; // TODO + if (options.use_llvm) return error.LLVMBackendUnimplementedForELF; // TODO + + if (build_options.have_llvm and options.use_lld) { + std.debug.print("TODO open a temporary object file, not the final output file because we want to link with LLD\n", .{}); + } const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = link.determineMode(options) }); errdefer file.close(); @@ -741,8 +744,21 @@ pub const abbrev_base_type = 4; pub const abbrev_pad1 = 5; pub const abbrev_parameter = 6; -/// Commit pending changes and write headers. pub fn flush(self: *Elf, module: *Module) !void { + if (build_options.have_llvm and self.base.options.use_lld) { + // If there is no Zig code to compile, then we should skip flushing the output file because it + // will not be part of the linker line anyway. + if (module.root_pkg != null) { + try self.flushInner(module); + } + std.debug.print("TODO create an LLD command line and invoke it\n", .{}); + } else { + return self.flushInner(module); + } +} + +/// Commit pending changes and write headers. +fn flushInner(self: *Elf, module: *Module) !void { const target_endian = self.base.options.target.cpu.arch.endian(); const foreign_endian = target_endian != std.Target.current.cpu.arch.endian(); const ptr_width_bytes: u8 = self.ptrWidthBytes(); diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index a398a6a5d1..84bb6e9351 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -33,13 +33,14 @@ const usage = \\ \\Commands: \\ - \\ build-exe [source] Create executable from source or object files - \\ build-lib [source] Create library from source or object files - \\ build-obj [source] Create object from source or assembly + \\ build-exe [source] Create executable from source or object files + \\ build-lib [source] Create library from source or object files + \\ build-obj [source] Create object from source or assembly \\ cc Use Zig as a drop-in C compiler \\ c++ Use Zig as a drop-in C++ compiler \\ env Print lib path, std path, compiler id and version - \\ fmt [source] Parse file and render in canonical zig format + \\ fmt [source] Parse file and render in canonical zig format + \\ translate-c [source] Convert C code to Zig code \\ targets List available compilation targets \\ version Print version number and exit \\ zen Print zen of zig and exit @@ -47,15 +48,21 @@ const usage = \\ ; +pub const log_level: std.log.Level = switch (std.builtin.mode) { + .Debug => .debug, + .ReleaseSafe, .ReleaseFast => .info, + .ReleaseSmall => .crit, +}; + pub fn log( comptime level: std.log.Level, comptime scope: @TypeOf(.EnumLiteral), comptime format: []const u8, args: anytype, ) void { - // Hide anything more verbose than warn unless it was added with `-Dlog=foo`. + // Hide debug messages unless added with `-Dlog=foo`. if (@enumToInt(level) > @enumToInt(std.log.level) or - @enumToInt(level) > @enumToInt(std.log.Level.warn)) + @enumToInt(level) > @enumToInt(std.log.Level.info)) { const scope_name = @tagName(scope); const ok = comptime for (build_options.log_scopes) |log_scope| { @@ -67,13 +74,15 @@ pub fn log( return; } + // We only recognize 4 log levels in this application. const level_txt = switch (level) { - .emerg => "error", - .warn => "warning", - else => @tagName(level), + .emerg, .alert, .crit => "error", + .err, .warn => "warning", + .notice, .info => "info", + .debug => "debug", }; - const prefix1 = level_txt ++ ": "; - const prefix2 = if (scope == .default) "" else "(" ++ @tagName(scope) ++ "): "; + const prefix1 = level_txt; + const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): "; // Print the message to stderr, silently ignoring any errors std.debug.print(prefix1 ++ prefix2 ++ format ++ "\n", args); @@ -93,8 +102,8 @@ pub fn main() !void { const args = try process.argsAlloc(arena); if (args.len <= 1) { - std.debug.print("expected command argument\n\n{}", .{usage}); - process.exit(1); + std.log.info("{}", .{usage}); + fatal("expected command argument", .{}); } const cmd = args[1]; @@ -109,6 +118,8 @@ pub fn main() !void { return buildOutputType(gpa, arena, args, .cc); } else if (mem.eql(u8, cmd, "c++")) { return buildOutputType(gpa, arena, args, .cpp); + } else if (mem.eql(u8, cmd, "translate-c")) { + return buildOutputType(gpa, arena, args, .translate_c); } else if (mem.eql(u8, cmd, "clang") or mem.eql(u8, cmd, "-cc1") or mem.eql(u8, cmd, "-cc1as")) { @@ -128,8 +139,8 @@ pub fn main() !void { } else if (mem.eql(u8, cmd, "help")) { try io.getStdOut().writeAll(usage); } else { - std.debug.print("unknown command: {}\n\n{}", .{ args[1], usage }); - process.exit(1); + std.log.info("{}", .{usage}); + fatal("unknown command: {}", .{args[1]}); } } @@ -223,6 +234,7 @@ pub fn buildOutputType( build: std.builtin.OutputMode, cc, cpp, + translate_c, }, ) !void { var color: Color = .Auto; @@ -251,8 +263,8 @@ pub fn buildOutputType( var emit_h: Emit = undefined; var ensure_libc_on_non_freestanding = false; var ensure_libcpp_on_non_freestanding = false; - var have_libc = false; - var have_libcpp = false; + var link_libc = false; + var link_libcpp = false; var want_native_include_dirs = false; var enable_cache: ?bool = null; var want_pic: ?bool = null; @@ -298,13 +310,20 @@ pub fn buildOutputType( var frameworks = std.ArrayList([]const u8).init(gpa); defer frameworks.deinit(); - if (arg_mode == .build) { - output_mode = arg_mode.build; - emit_h = switch (output_mode) { - .Exe => .no, - .Obj, .Lib => .yes_default_path, + if (arg_mode == .build or arg_mode == .translate_c) { + output_mode = switch (arg_mode) { + .build => |m| m, + .translate_c => .Obj, + else => unreachable, }; - + switch (arg_mode) { + .build => switch (output_mode) { + .Exe => emit_h = .no, + .Obj, .Lib => emit_h = .yes_default_path, + }, + .translate_c => emit_h = .no, + else => unreachable, + } const args = all_args[2..]; var i: usize = 0; while (i < args.len) : (i += 1) { @@ -499,7 +518,7 @@ pub fn buildOutputType( mem.endsWith(u8, arg, ".lib")) { try link_objects.append(arg); - } else if (hasAsmExt(arg) or hasCExt(arg) or hasCppExt(arg)) { + } else if (Module.hasAsmExt(arg) or Module.hasCExt(arg) or Module.hasCppExt(arg)) { try c_source_files.append(arg); } else if (mem.endsWith(u8, arg, ".so") or mem.endsWith(u8, arg, ".dylib") or @@ -543,7 +562,7 @@ pub fn buildOutputType( try clang_argv.appendSlice(it.other_args); }, .positional => { - const file_ext = classify_file_ext(mem.spanZ(it.only_arg)); + const file_ext = Module.classifyFileExt(mem.spanZ(it.only_arg)); switch (file_ext) { .assembly, .c, .cpp, .ll, .bc, .h => try c_source_files.append(it.only_arg), .unknown => try link_objects.append(it.only_arg), @@ -819,28 +838,28 @@ pub fn buildOutputType( .diagnostics = &diags, }) catch |err| switch (err) { error.UnknownCpuModel => { - std.debug.print("Unknown CPU: '{}'\nAvailable CPUs for architecture '{}':\n", .{ - diags.cpu_name.?, - @tagName(diags.arch.?), - }); - for (diags.arch.?.allCpuModels()) |cpu| { - std.debug.print(" {}\n", .{cpu.name}); + help: { + var help_text = std.ArrayList(u8).init(arena); + for (diags.arch.?.allCpuModels()) |cpu| { + help_text.writer().print(" {}\n", .{cpu.name}) catch break :help; + } + std.log.info("Available CPUs for architecture '{}': {}", .{ + @tagName(diags.arch.?), help_text.items, + }); } - process.exit(1); + fatal("Unknown CPU: '{}'", .{diags.cpu_name.?}); }, error.UnknownCpuFeature => { - std.debug.print( - \\Unknown CPU feature: '{}' - \\Available CPU features for architecture '{}': - \\ - , .{ - diags.unknown_feature_name, - @tagName(diags.arch.?), - }); - for (diags.arch.?.allFeaturesList()) |feature| { - std.debug.print(" {}: {}\n", .{ feature.name, feature.description }); + help: { + var help_text = std.ArrayList(u8).init(arena); + for (diags.arch.?.allFeaturesList()) |feature| { + help_text.writer().print(" {}: {}\n", .{ feature.name, feature.description }) catch break :help; + } + std.log.info("Available CPU features for architecture '{}': {}", .{ + @tagName(diags.arch.?), help_text.items, + }); } - process.exit(1); + fatal("Unknown CPU feature: '{}'", .{diags.unknown_feature_name}); }, else => |e| return e, }; @@ -849,14 +868,16 @@ pub fn buildOutputType( if (target_info.cpu_detection_unimplemented) { // TODO We want to just use detected_info.target but implementing // CPU model & feature detection is todo so here we rely on LLVM. + // TODO The workaround to use LLVM to detect features needs to be used for + // `zig targets` as well. fatal("CPU features detection is not yet available for this system without LLVM extensions", .{}); } if (target_info.target.os.tag != .freestanding) { if (ensure_libc_on_non_freestanding) - have_libc = true; + link_libc = true; if (ensure_libcpp_on_non_freestanding) - have_libcpp = true; + link_libcpp = true; } // Now that we have target info, we can find out if any of the system libraries @@ -867,12 +888,12 @@ pub fn buildOutputType( while (i < system_libs.items.len) { const lib_name = system_libs.items[i]; if (is_libc_lib_name(target_info.target, lib_name)) { - have_libc = true; + link_libc = true; _ = system_libs.orderedRemove(i); continue; } if (is_libcpp_lib_name(target_info.target, lib_name)) { - have_libcpp = true; + link_libcpp = true; _ = system_libs.orderedRemove(i); continue; } @@ -960,7 +981,21 @@ pub fn buildOutputType( .yes_default_path => try std.fmt.allocPrint(arena, "{}.h", .{root_name}), }; - var module = Module.init(gpa, .{ + const self_exe_path = try fs.selfExePathAlloc(arena); + const zig_lib_dir = introspect.resolveZigLibDir(gpa) catch |err| { + fatal("unable to find zig installation directory: {}\n", .{@errorName(err)}); + }; + defer gpa.free(zig_lib_dir); + + const random_seed = blk: { + var random_seed: u64 = undefined; + try std.crypto.randomBytes(mem.asBytes(&random_seed)); + break :blk random_seed; + }; + var default_prng = std.rand.DefaultPrng.init(random_seed); + + const module = Module.create(gpa, .{ + .zig_lib_dir = zig_lib_dir, .root_name = root_name, .target = target_info.target, .output_mode = output_mode, @@ -980,8 +1015,8 @@ pub fn buildOutputType( .frameworks = frameworks.items, .system_libs = system_libs.items, .emit_h = emit_h_path, - .have_libc = have_libc, - .have_libcpp = have_libcpp, + .link_libc = link_libc, + .link_libcpp = link_libcpp, .want_pic = want_pic, .want_sanitize_c = want_sanitize_c, .use_llvm = use_llvm, @@ -1000,16 +1035,19 @@ pub fn buildOutputType( .linker_z_defs = linker_z_defs, .stack_size_override = stack_size_override, .strip = strip, + .self_exe_path = self_exe_path, + .rand = &default_prng.random, + .clang_passthrough_mode = arg_mode != .build, }) catch |err| { - fatal("unable to initialize module: {}", .{@errorName(err)}); + fatal("unable to create module: {}", .{@errorName(err)}); }; - defer module.deinit(); + defer module.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, module, zir_out_path); if (build_options.have_llvm and only_pp_or_asm) { // this may include dumping the output to stdout @@ -1031,7 +1069,7 @@ pub fn buildOutputType( if (output_mode == .Exe) { try module.makeBinFileWritable(); } - try updateModule(gpa, &module, zir_out_path); + try updateModule(gpa, module, zir_out_path); } else if (mem.eql(u8, actual_line, "exit")) { break; } else if (mem.eql(u8, actual_line, "help")) { @@ -1062,12 +1100,10 @@ fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !vo full_err_msg.msg, }); } - } else { - std.log.info("Update completed in {} ms", .{update_nanos / std.time.ns_per_ms}); } if (zir_out_path) |zop| { - var new_zir_module = try zir.emit(gpa, module.*); + var new_zir_module = try zir.emit(gpa, module); defer new_zir_module.deinit(gpa); const baf = try io.BufferedAtomicFile.create(gpa, fs.cwd(), zop, .{}); @@ -1422,50 +1458,6 @@ pub const info_zen = \\ ; -const FileExt = enum { - c, - cpp, - h, - ll, - bc, - assembly, - unknown, -}; - -fn hasCExt(filename: []const u8) bool { - return mem.endsWith(u8, filename, ".c"); -} - -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"); -} - -fn hasAsmExt(filename: []const u8) bool { - return mem.endsWith(u8, filename, ".s") or mem.endsWith(u8, filename, ".S"); -} - -fn classify_file_ext(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 { - // TODO look for .so, .so.X, .so.X.Y, .so.X.Y.Z - return .unknown; - } -} - extern "c" fn ZigClang_main(argc: c_int, argv: [*:null]?[*:0]u8) c_int; /// TODO make it so the return value can be !noreturn diff --git a/src-self-hosted/print_targets.zig b/src-self-hosted/print_targets.zig index 0fe755ffb4..33a513efd7 100644 --- a/src-self-hosted/print_targets.zig +++ b/src-self-hosted/print_targets.zig @@ -4,60 +4,11 @@ const io = std.io; const mem = std.mem; const Allocator = mem.Allocator; const Target = std.Target; +const target = @import("target.zig"); const assert = std.debug.assert; const introspect = @import("introspect.zig"); -// TODO this is hard-coded until self-hosted gains this information canonically -const available_libcs = [_][]const u8{ - "aarch64_be-linux-gnu", - "aarch64_be-linux-musl", - "aarch64_be-windows-gnu", - "aarch64-linux-gnu", - "aarch64-linux-musl", - "aarch64-windows-gnu", - "armeb-linux-gnueabi", - "armeb-linux-gnueabihf", - "armeb-linux-musleabi", - "armeb-linux-musleabihf", - "armeb-windows-gnu", - "arm-linux-gnueabi", - "arm-linux-gnueabihf", - "arm-linux-musleabi", - "arm-linux-musleabihf", - "arm-windows-gnu", - "i386-linux-gnu", - "i386-linux-musl", - "i386-windows-gnu", - "mips64el-linux-gnuabi64", - "mips64el-linux-gnuabin32", - "mips64el-linux-musl", - "mips64-linux-gnuabi64", - "mips64-linux-gnuabin32", - "mips64-linux-musl", - "mipsel-linux-gnu", - "mipsel-linux-musl", - "mips-linux-gnu", - "mips-linux-musl", - "powerpc64le-linux-gnu", - "powerpc64le-linux-musl", - "powerpc64-linux-gnu", - "powerpc64-linux-musl", - "powerpc-linux-gnu", - "powerpc-linux-musl", - "riscv64-linux-gnu", - "riscv64-linux-musl", - "s390x-linux-gnu", - "s390x-linux-musl", - "sparc-linux-gnu", - "sparcv9-linux-gnu", - "wasm32-freestanding-musl", - "x86_64-linux-gnu", - "x86_64-linux-gnux32", - "x86_64-linux-musl", - "x86_64-windows-gnu", -}; - pub fn cmdTargets( allocator: *Allocator, args: []const []const u8, @@ -127,9 +78,13 @@ pub fn cmdTargets( try jws.objectField("libc"); try jws.beginArray(); - for (available_libcs) |libc| { + for (target.available_libcs) |libc| { + const tmp = try std.fmt.allocPrint(allocator, "{}-{}-{}", .{ + @tagName(libc.arch), @tagName(libc.os), @tagName(libc.abi), + }); + defer allocator.free(tmp); try jws.arrayElem(); - try jws.emitString(libc); + try jws.emitString(tmp); } try jws.endArray(); diff --git a/src-self-hosted/target.zig b/src-self-hosted/target.zig new file mode 100644 index 0000000000..cb09779d74 --- /dev/null +++ b/src-self-hosted/target.zig @@ -0,0 +1,111 @@ +const std = @import("std"); + +pub const ArchOsAbi = struct { + arch: std.Target.Cpu.Arch, + os: std.Target.Os.Tag, + abi: std.Target.Abi, +}; + +pub const available_libcs = [_]ArchOsAbi{ + .{ .arch = .aarch64_be, .os = .linux, .abi = .gnu }, + .{ .arch = .aarch64_be, .os = .linux, .abi = .musl }, + .{ .arch = .aarch64_be, .os = .windows, .abi = .gnu }, + .{ .arch = .aarch64, .os = .linux, .abi = .gnu }, + .{ .arch = .aarch64, .os = .linux, .abi = .musl }, + .{ .arch = .aarch64, .os = .windows, .abi = .gnu }, + .{ .arch = .armeb, .os = .linux, .abi = .gnueabi }, + .{ .arch = .armeb, .os = .linux, .abi = .gnueabihf }, + .{ .arch = .armeb, .os = .linux, .abi = .musleabi }, + .{ .arch = .armeb, .os = .linux, .abi = .musleabihf }, + .{ .arch = .armeb, .os = .windows, .abi = .gnu }, + .{ .arch = .arm, .os = .linux, .abi = .gnueabi }, + .{ .arch = .arm, .os = .linux, .abi = .gnueabihf }, + .{ .arch = .arm, .os = .linux, .abi = .musleabi }, + .{ .arch = .arm, .os = .linux, .abi = .musleabihf }, + .{ .arch = .arm, .os = .windows, .abi = .gnu }, + .{ .arch = .i386, .os = .linux, .abi = .gnu }, + .{ .arch = .i386, .os = .linux, .abi = .musl }, + .{ .arch = .i386, .os = .windows, .abi = .gnu }, + .{ .arch = .mips64el, .os = .linux, .abi = .gnuabi64 }, + .{ .arch = .mips64el, .os = .linux, .abi = .gnuabin32 }, + .{ .arch = .mips64el, .os = .linux, .abi = .musl }, + .{ .arch = .mips64, .os = .linux, .abi = .gnuabi64 }, + .{ .arch = .mips64, .os = .linux, .abi = .gnuabin32 }, + .{ .arch = .mips64, .os = .linux, .abi = .musl }, + .{ .arch = .mipsel, .os = .linux, .abi = .gnu }, + .{ .arch = .mipsel, .os = .linux, .abi = .musl }, + .{ .arch = .mips, .os = .linux, .abi = .gnu }, + .{ .arch = .mips, .os = .linux, .abi = .musl }, + .{ .arch = .powerpc64le, .os = .linux, .abi = .gnu }, + .{ .arch = .powerpc64le, .os = .linux, .abi = .musl }, + .{ .arch = .powerpc64, .os = .linux, .abi = .gnu }, + .{ .arch = .powerpc64, .os = .linux, .abi = .musl }, + .{ .arch = .powerpc, .os = .linux, .abi = .gnu }, + .{ .arch = .powerpc, .os = .linux, .abi = .musl }, + .{ .arch = .riscv64, .os = .linux, .abi = .gnu }, + .{ .arch = .riscv64, .os = .linux, .abi = .musl }, + .{ .arch = .s390x, .os = .linux, .abi = .gnu }, + .{ .arch = .s390x, .os = .linux, .abi = .musl }, + .{ .arch = .sparc, .os = .linux, .abi = .gnu }, + .{ .arch = .sparcv9, .os = .linux, .abi = .gnu }, + .{ .arch = .wasm32, .os = .freestanding, .abi = .musl }, + .{ .arch = .x86_64, .os = .linux, .abi = .gnu }, + .{ .arch = .x86_64, .os = .linux, .abi = .gnux32 }, + .{ .arch = .x86_64, .os = .linux, .abi = .musl }, + .{ .arch = .x86_64, .os = .windows, .abi = .gnu }, +}; + +pub fn libCGenericName(target: std.Target) [:0]const u8 { + if (target.os.tag == .windows) + return "mingw"; + switch (target.abi) { + .gnu, + .gnuabin32, + .gnuabi64, + .gnueabi, + .gnueabihf, + .gnux32, + => return "glibc", + .musl, + .musleabi, + .musleabihf, + .none, + => return "musl", + .code16, + .eabi, + .eabihf, + .android, + .msvc, + .itanium, + .cygnus, + .coreclr, + .simulator, + .macabi, + => unreachable, + } +} + +pub fn archMuslName(arch: std.Target.Cpu.Arch) [:0]const u8 { + switch (arch) { + .aarch64, .aarch64_be => return "aarch64", + .arm, .armeb => return "arm", + .mips, .mipsel => return "mips", + .mips64el, .mips64 => return "mips64", + .powerpc => return "powerpc", + .powerpc64, .powerpc64le => return "powerpc64", + .s390x => return "s390x", + .i386 => return "i386", + .x86_64 => return "x86_64", + .riscv64 => return "riscv64", + else => unreachable, + } +} + +pub fn canBuildLibC(target: std.Target) bool { + for (available_libcs) |libc| { + if (target.cpu.arch == libc.arch and target.os.tag == libc.os and target.abi == libc.abi) { + return true; + } + } + return false; +} diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index cd41ff0d78..004b56b029 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -4,6 +4,7 @@ const Module = @import("Module.zig"); const Allocator = std.mem.Allocator; const zir = @import("zir.zig"); const Package = @import("Package.zig"); +const introspect = @import("introspect.zig"); const build_options = @import("build_options"); const enable_qemu: bool = build_options.enable_qemu; const enable_wine: bool = build_options.enable_wine; @@ -406,6 +407,16 @@ pub const TestContext = struct { const root_node = try progress.start("tests", self.cases.items.len); defer root_node.end(); + const zig_lib_dir = try introspect.resolveZigLibDir(std.testing.allocator); + defer std.testing.allocator.free(zig_lib_dir); + + const random_seed = blk: { + var random_seed: u64 = undefined; + try std.crypto.randomBytes(std.mem.asBytes(&random_seed)); + break :blk random_seed; + }; + var default_prng = std.rand.DefaultPrng.init(random_seed); + for (self.cases.items) |case| { var prg_node = root_node.start(case.name, case.updates.items.len); prg_node.activate(); @@ -416,11 +427,18 @@ pub const TestContext = struct { progress.initial_delay_ns = 0; progress.refresh_rate_ns = 0; - try self.runOneCase(std.testing.allocator, &prg_node, case); + try self.runOneCase(std.testing.allocator, &prg_node, case, zig_lib_dir, &default_prng.random); } } - fn runOneCase(self: *TestContext, allocator: *Allocator, root_node: *std.Progress.Node, case: Case) !void { + fn runOneCase( + self: *TestContext, + allocator: *Allocator, + root_node: *std.Progress.Node, + case: Case, + zig_lib_dir: []const u8, + rand: *std.rand.Random, + ) !void { const target_info = try std.zig.system.NativeTargetInfo.detect(allocator, case.target); const target = target_info.target; @@ -438,7 +456,9 @@ 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); - var module = try Module.init(allocator, .{ + const module = try Module.create(allocator, .{ + .zig_lib_dir = zig_lib_dir, + .rand = rand, .root_name = "test_case", .target = target, // TODO: support tests for object file building, and library builds @@ -453,7 +473,7 @@ pub const TestContext = struct { .keep_source_files_loaded = true, .object_format = ofmt, }); - defer module.deinit(); + defer module.destroy(); for (case.updates.items) |update, update_index| { var update_node = root_node.start("update", 3); diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index b6d7fab4c5..7e723fc674 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -1700,12 +1700,12 @@ const Parser = struct { } }; -pub fn emit(allocator: *Allocator, old_module: IrModule) !Module { +pub fn emit(allocator: *Allocator, old_module: *IrModule) !Module { var ctx: EmitZIR = .{ .allocator = allocator, .decls = .{}, .arena = std.heap.ArenaAllocator.init(allocator), - .old_module = &old_module, + .old_module = old_module, .next_auto_name = 0, .names = std.StringArrayHashMap(void).init(allocator), .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator), @@ -2539,7 +2539,7 @@ const EmitZIR = struct { return self.emitUnnamedDecl(&fntype_inst.base); }, .Int => { - const info = ty.intInfo(self.old_module.target()); + const info = ty.intInfo(self.old_module.getTarget()); const signed = try self.emitPrimitive(src, if (info.signed) .@"true" else .@"false"); const bits_payload = try self.arena.allocator.create(Value.Payload.Int_u64); bits_payload.* = .{ .int = info.bits }; diff --git a/src/main.cpp b/src/main.cpp index af3bc4b2a1..9f6d2b3d7b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -78,7 +78,7 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) { " -fno-emit-asm (default) do not output .s (assembly code)\n" " -femit-llvm-ir produce a .ll file with LLVM IR\n" " -fno-emit-llvm-ir (default) do not produce a .ll file with LLVM IR\n" - " -femit-h generate a C header file (.h)\n" + " -femit-h generate a C header file (.h)\n" " -fno-emit-h (default) do not generate a C header file (.h)\n" " --libc [file] Provide a file which specifies libc paths\n" " --name [name] override output name\n"