target: std.Target, zig_backend: std.builtin.CompilerBackend, output_mode: std.builtin.OutputMode, link_mode: std.builtin.LinkMode, unwind_tables: std.builtin.UnwindTables, is_test: bool, single_threaded: bool, link_libc: bool, link_libcpp: bool, optimize_mode: std.builtin.OptimizeMode, error_tracing: bool, valgrind: bool, sanitize_thread: bool, fuzz: bool, pic: bool, pie: bool, strip: bool, code_model: std.builtin.CodeModel, omit_frame_pointer: bool, wasi_exec_model: std.builtin.WasiExecModel, /// Compute an abstract hash representing this `Builtin`. This is *not* a hash /// of the resulting file contents. pub fn hash(opts: @This()) [std.Build.Cache.bin_digest_len]u8 { var h: Cache.Hasher = Cache.hasher_init; inline for (@typeInfo(@This()).@"struct".fields) |f| { if (comptime std.mem.eql(u8, f.name, "target")) { // This needs special handling. std.hash.autoHash(&h, opts.target.cpu); std.hash.autoHash(&h, opts.target.os.tag); std.hash.autoHash(&h, opts.target.os.versionRange()); std.hash.autoHash(&h, opts.target.abi); std.hash.autoHash(&h, opts.target.ofmt); std.hash.autoHash(&h, opts.target.dynamic_linker); } else { std.hash.autoHash(&h, @field(opts, f.name)); } } return h.finalResult(); } pub fn generate(opts: @This(), allocator: Allocator) Allocator.Error![:0]u8 { var buffer = std.array_list.Managed(u8).init(allocator); try append(opts, &buffer); return buffer.toOwnedSliceSentinel(0); } pub fn append(opts: @This(), buffer: *std.array_list.Managed(u8)) Allocator.Error!void { const target = opts.target; const arch_family_name = @tagName(target.cpu.arch.family()); const zig_backend = opts.zig_backend; @setEvalBranchQuota(4000); try buffer.print( \\const std = @import("std"); \\/// Zig version. When writing code that supports multiple versions of Zig, prefer \\/// feature detection (i.e. with `@hasDecl` or `@hasField`) over version checks. \\pub const zig_version = std.SemanticVersion.parse(zig_version_string) catch unreachable; \\pub const zig_version_string = "{s}"; \\pub const zig_backend = std.builtin.CompilerBackend.{f}; \\ \\pub const output_mode: std.builtin.OutputMode = .{f}; \\pub const link_mode: std.builtin.LinkMode = .{f}; \\pub const unwind_tables: std.builtin.UnwindTables = .{f}; \\pub const is_test = {}; \\pub const single_threaded = {}; \\pub const abi: std.Target.Abi = .{f}; \\pub const cpu: std.Target.Cpu = .{{ \\ .arch = .{f}, \\ .model = &std.Target.{f}.cpu.{f}, \\ .features = std.Target.{f}.featureSet(&.{{ \\ , .{ build_options.version, std.zig.fmtIdPU(@tagName(zig_backend)), std.zig.fmtIdPU(@tagName(opts.output_mode)), std.zig.fmtIdPU(@tagName(opts.link_mode)), std.zig.fmtIdPU(@tagName(opts.unwind_tables)), opts.is_test, opts.single_threaded, std.zig.fmtIdPU(@tagName(target.abi)), std.zig.fmtIdPU(@tagName(target.cpu.arch)), std.zig.fmtIdPU(arch_family_name), std.zig.fmtIdPU(target.cpu.model.name), std.zig.fmtIdPU(arch_family_name), }); for (target.cpu.arch.allFeaturesList(), 0..) |feature, index_usize| { const index = @as(std.Target.Cpu.Feature.Set.Index, @intCast(index_usize)); const is_enabled = target.cpu.features.isEnabled(index); if (is_enabled) { try buffer.print(" .{f},\n", .{std.zig.fmtIdPU(feature.name)}); } } try buffer.print( \\ }}), \\}}; \\pub const os: std.Target.Os = .{{ \\ .tag = .{f}, \\ .version_range = .{{ , .{std.zig.fmtIdPU(@tagName(target.os.tag))}, ); switch (target.os.versionRange()) { .none => try buffer.appendSlice(" .none = {} },\n"), .semver => |semver| try buffer.print( \\ .semver = .{{ \\ .min = .{{ \\ .major = {}, \\ .minor = {}, \\ .patch = {}, \\ }}, \\ .max = .{{ \\ .major = {}, \\ .minor = {}, \\ .patch = {}, \\ }}, \\ }}}}, \\ , .{ semver.min.major, semver.min.minor, semver.min.patch, semver.max.major, semver.max.minor, semver.max.patch, }), .linux => |linux| try buffer.print( \\ .linux = .{{ \\ .range = .{{ \\ .min = .{{ \\ .major = {}, \\ .minor = {}, \\ .patch = {}, \\ }}, \\ .max = .{{ \\ .major = {}, \\ .minor = {}, \\ .patch = {}, \\ }}, \\ }}, \\ .glibc = .{{ \\ .major = {}, \\ .minor = {}, \\ .patch = {}, \\ }}, \\ .android = {}, \\ }}}}, \\ , .{ linux.range.min.major, linux.range.min.minor, linux.range.min.patch, linux.range.max.major, linux.range.max.minor, linux.range.max.patch, linux.glibc.major, linux.glibc.minor, linux.glibc.patch, linux.android, }), .hurd => |hurd| try buffer.print( \\ .hurd = .{{ \\ .range = .{{ \\ .min = .{{ \\ .major = {}, \\ .minor = {}, \\ .patch = {}, \\ }}, \\ .max = .{{ \\ .major = {}, \\ .minor = {}, \\ .patch = {}, \\ }}, \\ }}, \\ .glibc = .{{ \\ .major = {}, \\ .minor = {}, \\ .patch = {}, \\ }}, \\ }}}}, \\ , .{ hurd.range.min.major, hurd.range.min.minor, hurd.range.min.patch, hurd.range.max.major, hurd.range.max.minor, hurd.range.max.patch, hurd.glibc.major, hurd.glibc.minor, hurd.glibc.patch, }), .windows => |windows| try buffer.print( \\ .windows = .{{ \\ .min = {f}, \\ .max = {f}, \\ }}}}, \\ , .{ windows.min, windows.max }), } try buffer.appendSlice( \\}; \\pub const target: std.Target = .{ \\ .cpu = cpu, \\ .os = os, \\ .abi = abi, \\ .ofmt = object_format, \\ ); if (target.dynamic_linker.get()) |dl| { try buffer.print( \\ .dynamic_linker = .init("{s}"), \\}}; \\ , .{dl}); } else { try buffer.appendSlice( \\ .dynamic_linker = .none, \\}; \\ ); } // This is so that compiler_rt and libc.zig libraries know whether they // will eventually be linked with libc. They make different decisions // about what to export depending on whether another libc will be linked // in. For example, compiler_rt will not export the __chkstk symbol if it // knows libc will provide it, and likewise c.zig will not export memcpy. const link_libc = opts.link_libc; try buffer.print( \\pub const object_format: std.Target.ObjectFormat = .{f}; \\pub const mode: std.builtin.OptimizeMode = .{f}; \\pub const link_libc = {}; \\pub const link_libcpp = {}; \\pub const have_error_return_tracing = {}; \\pub const valgrind_support = {}; \\pub const sanitize_thread = {}; \\pub const fuzz = {}; \\pub const position_independent_code = {}; \\pub const position_independent_executable = {}; \\pub const strip_debug_info = {}; \\pub const code_model: std.builtin.CodeModel = .{f}; \\pub const omit_frame_pointer = {}; \\ , .{ std.zig.fmtIdPU(@tagName(target.ofmt)), std.zig.fmtIdPU(@tagName(opts.optimize_mode)), link_libc, opts.link_libcpp, opts.error_tracing, opts.valgrind, opts.sanitize_thread, opts.fuzz, opts.pic, opts.pie, opts.strip, std.zig.fmtIdPU(@tagName(opts.code_model)), opts.omit_frame_pointer, }); if (target.os.tag == .wasi) { try buffer.print( \\pub const wasi_exec_model: std.builtin.WasiExecModel = .{f}; \\ , .{std.zig.fmtIdPU(@tagName(opts.wasi_exec_model))}); } if (opts.is_test) { try buffer.appendSlice( \\pub var test_functions: []const std.builtin.TestFn = &.{}; // overwritten later \\ ); } } /// This essentially takes the place of `Zcu.PerThread.updateFile`, but for 'builtin' modules. /// Instead of reading the file from disk, its contents are generated in-memory. pub fn populateFile(opts: @This(), gpa: Allocator, file: *File) Allocator.Error!void { assert(file.is_builtin); assert(file.status == .never_loaded); assert(file.source == null); assert(file.tree == null); assert(file.zir == null); file.source = try opts.generate(gpa); log.debug("parsing and generating 'builtin.zig'", .{}); file.tree = try std.zig.Ast.parse(gpa, file.source.?, .zig); assert(file.tree.?.errors.len == 0); // builtin.zig must parse file.zir = try AstGen.generate(gpa, file.tree.?); assert(!file.zir.?.hasCompileErrors()); // builtin.zig must not have astgen errors file.status = .success; } /// After `populateFile` succeeds, call this function to write the generated file out to disk /// if necessary. This is useful for external tooling such as debuggers. /// Assumes that `file.mod` is correctly set to the builtin module. pub fn updateFileOnDisk(file: *File, comp: *Compilation) !void { assert(file.is_builtin); assert(file.status == .success); assert(file.source != null); const root_dir, const sub_path = file.path.openInfo(comp.dirs); if (root_dir.statFile(sub_path)) |stat| { if (stat.size != file.source.?.len) { std.log.warn( "the cached file '{f}' had the wrong size. Expected {d}, found {d}. " ++ "Overwriting with correct file contents now", .{ file.path.fmt(comp), file.source.?.len, stat.size }, ); } else { file.stat = .{ .size = stat.size, .inode = stat.inode, .mtime = stat.mtime, }; return; } } else |err| switch (err) { error.FileNotFound => {}, error.WouldBlock => unreachable, // not asking for non-blocking I/O error.BadPathName => unreachable, // it's always "o/digest/builtin.zig" error.NameTooLong => unreachable, // it's always "o/digest/builtin.zig" // We don't expect the file to be a pipe, but can't mark `error.PipeBusy` as `unreachable`, // because the user could always replace the file on disk. else => |e| return e, } // `make_path` matters because the dir hasn't actually been created yet. var af = try root_dir.atomicFile(sub_path, .{ .make_path = true, .write_buffer = &.{} }); defer af.deinit(); try af.file_writer.interface.writeAll(file.source.?); af.finish() catch |err| switch (err) { error.AccessDenied => switch (builtin.os.tag) { .windows => { // Very likely happened due to another process or thread // simultaneously creating the same, correct builtin.zig file. // This is not a problem; ignore it. }, else => return err, }, else => return err, }; file.stat = .{ .size = file.source.?.len, .inode = 0, // dummy value .mtime = .zero, // dummy value }; } const builtin = @import("builtin"); const std = @import("std"); const Allocator = std.mem.Allocator; const Cache = std.Build.Cache; const build_options = @import("build_options"); const Module = @import("Package/Module.zig"); const assert = std.debug.assert; const AstGen = std.zig.AstGen; const File = @import("Zcu.zig").File; const Compilation = @import("Compilation.zig"); const log = std.log.scoped(.builtin);