aboutsummaryrefslogtreecommitdiff
path: root/src/Builtin.zig
blob: b0077f2276c56fa37c9337ec7b4c4e04cbfef7b4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
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);