mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
Previously, various doc comments heavily disagreed with the implementation on both what lives where on the filesystem at what time, and how that was represented in code. Notably, the combination of emit paths outside the cache and `disable_lld_caching` created a kind of ad-hoc "cache disable" mechanism -- which didn't actually *work* very well, 'most everything still ended up in this cache. There was also a long-standing issue where building using the LLVM backend would put a random object file in your cwd. This commit reworks how emit paths are specified in `Compilation.CreateOptions`, how they are represented internally, and how the cache usage is specified. There are now 3 options for `Compilation.CacheMode`: * `.none`: do not use the cache. The paths we have to emit to are relative to the compiler cwd (they're either user-specified, or defaults inferred from the root name). If we create any temporary files (e.g. the ZCU object when using the LLVM backend) they are emitted to a directory in `local_cache/tmp/`, which is deleted once the update finishes. * `.whole`: cache the compilation based on all inputs, including file contents. All emit paths are computed by the compiler (and will be stored as relative to the local cache directory); it is a CLI error to specify an explicit emit path. Artifacts (including temporary files) are written to a directory under `local_cache/tmp/`, which is later renamed to an appropriate `local_cache/o/`. The caller (who is using `--listen`; e.g. the build system) learns the name of this directory, and can get the artifacts from it. * `.incremental`: similar to `.whole`, but Zig source file contents, and anything else which incremental compilation can handle changes for, is not included in the cache manifest. We don't need to do the dance where the output directory is initially in `tmp/`, because our digest is computed entirely from CLI inputs. To be clear, the difference between `CacheMode.whole` and `CacheMode.incremental` is unchanged. `CacheMode.none` is new (previously it was sort of poorly imitated with `CacheMode.whole`). The defined behavior for temporary/intermediate files is new. `.none` is used for direct CLI invocations like `zig build-exe foo.zig`. The other cache modes are reserved for `--listen`, and the cache mode in use is currently just based on the presence of the `-fincremental` flag. There are two cases in which `CacheMode.whole` is used despite there being no `--listen` flag: `zig test` and `zig run`. Unless an explicit `-femit-bin=xxx` argument is passed on the CLI, these subcommands will use `CacheMode.whole`, so that they can put the output somewhere without polluting the cwd (plus, caching is potentially more useful for direct usage of these subcommands). Users of `--listen` (such as the build system) can now use `std.zig.EmitArtifact.cacheName` to find out what an output will be named. This avoids having to synchronize logic between the compiler and all users of `--listen`.
933 lines
35 KiB
Zig
933 lines
35 KiB
Zig
//! Builds of the Zig compiler are distributed partly in source form. That
|
|
//! source lives here. These APIs are provided as-is and have absolutely no API
|
|
//! guarantees whatsoever.
|
|
|
|
pub const ErrorBundle = @import("zig/ErrorBundle.zig");
|
|
pub const Server = @import("zig/Server.zig");
|
|
pub const Client = @import("zig/Client.zig");
|
|
pub const Token = tokenizer.Token;
|
|
pub const Tokenizer = tokenizer.Tokenizer;
|
|
pub const string_literal = @import("zig/string_literal.zig");
|
|
pub const number_literal = @import("zig/number_literal.zig");
|
|
pub const primitives = @import("zig/primitives.zig");
|
|
pub const isPrimitive = primitives.isPrimitive;
|
|
pub const Ast = @import("zig/Ast.zig");
|
|
pub const AstGen = @import("zig/AstGen.zig");
|
|
pub const Zir = @import("zig/Zir.zig");
|
|
pub const Zoir = @import("zig/Zoir.zig");
|
|
pub const ZonGen = @import("zig/ZonGen.zig");
|
|
pub const system = @import("zig/system.zig");
|
|
pub const CrossTarget = @compileError("deprecated; use std.Target.Query");
|
|
pub const BuiltinFn = @import("zig/BuiltinFn.zig");
|
|
pub const AstRlAnnotate = @import("zig/AstRlAnnotate.zig");
|
|
pub const LibCInstallation = @import("zig/LibCInstallation.zig");
|
|
pub const WindowsSdk = @import("zig/WindowsSdk.zig");
|
|
pub const LibCDirs = @import("zig/LibCDirs.zig");
|
|
pub const target = @import("zig/target.zig");
|
|
pub const llvm = @import("zig/llvm.zig");
|
|
|
|
// Character literal parsing
|
|
pub const ParsedCharLiteral = string_literal.ParsedCharLiteral;
|
|
pub const parseCharLiteral = string_literal.parseCharLiteral;
|
|
pub const parseNumberLiteral = number_literal.parseNumberLiteral;
|
|
|
|
// Files needed by translate-c.
|
|
pub const c_builtins = @import("zig/c_builtins.zig");
|
|
pub const c_translation = @import("zig/c_translation.zig");
|
|
|
|
pub const SrcHasher = std.crypto.hash.Blake3;
|
|
pub const SrcHash = [16]u8;
|
|
|
|
pub const Color = enum {
|
|
/// Determine whether stderr is a terminal or not automatically.
|
|
auto,
|
|
/// Assume stderr is not a terminal.
|
|
off,
|
|
/// Assume stderr is a terminal.
|
|
on,
|
|
|
|
pub fn get_tty_conf(color: Color) std.io.tty.Config {
|
|
return switch (color) {
|
|
.auto => std.io.tty.detectConfig(std.io.getStdErr()),
|
|
.on => .escape_codes,
|
|
.off => .no_color,
|
|
};
|
|
}
|
|
|
|
pub fn renderOptions(color: Color) std.zig.ErrorBundle.RenderOptions {
|
|
return .{
|
|
.ttyconf = get_tty_conf(color),
|
|
};
|
|
}
|
|
};
|
|
|
|
/// There are many assumptions in the entire codebase that Zig source files can
|
|
/// be byte-indexed with a u32 integer.
|
|
pub const max_src_size = std.math.maxInt(u32);
|
|
|
|
pub fn hashSrc(src: []const u8) SrcHash {
|
|
var out: SrcHash = undefined;
|
|
SrcHasher.hash(src, &out, .{});
|
|
return out;
|
|
}
|
|
|
|
pub fn srcHashEql(a: SrcHash, b: SrcHash) bool {
|
|
return @as(u128, @bitCast(a)) == @as(u128, @bitCast(b));
|
|
}
|
|
|
|
pub fn hashName(parent_hash: SrcHash, sep: []const u8, name: []const u8) SrcHash {
|
|
var out: SrcHash = undefined;
|
|
var hasher = SrcHasher.init(.{});
|
|
hasher.update(&parent_hash);
|
|
hasher.update(sep);
|
|
hasher.update(name);
|
|
hasher.final(&out);
|
|
return out;
|
|
}
|
|
|
|
pub const Loc = struct {
|
|
line: usize,
|
|
column: usize,
|
|
/// Does not include the trailing newline.
|
|
source_line: []const u8,
|
|
|
|
pub fn eql(a: Loc, b: Loc) bool {
|
|
return a.line == b.line and a.column == b.column and std.mem.eql(u8, a.source_line, b.source_line);
|
|
}
|
|
};
|
|
|
|
pub fn findLineColumn(source: []const u8, byte_offset: usize) Loc {
|
|
var line: usize = 0;
|
|
var column: usize = 0;
|
|
var line_start: usize = 0;
|
|
var i: usize = 0;
|
|
while (i < byte_offset) : (i += 1) {
|
|
switch (source[i]) {
|
|
'\n' => {
|
|
line += 1;
|
|
column = 0;
|
|
line_start = i + 1;
|
|
},
|
|
else => {
|
|
column += 1;
|
|
},
|
|
}
|
|
}
|
|
while (i < source.len and source[i] != '\n') {
|
|
i += 1;
|
|
}
|
|
return .{
|
|
.line = line,
|
|
.column = column,
|
|
.source_line = source[line_start..i],
|
|
};
|
|
}
|
|
|
|
pub fn lineDelta(source: []const u8, start: usize, end: usize) isize {
|
|
var line: isize = 0;
|
|
if (end >= start) {
|
|
for (source[start..end]) |byte| switch (byte) {
|
|
'\n' => line += 1,
|
|
else => continue,
|
|
};
|
|
} else {
|
|
for (source[end..start]) |byte| switch (byte) {
|
|
'\n' => line -= 1,
|
|
else => continue,
|
|
};
|
|
}
|
|
return line;
|
|
}
|
|
|
|
pub const BinNameOptions = struct {
|
|
root_name: []const u8,
|
|
target: std.Target,
|
|
output_mode: std.builtin.OutputMode,
|
|
link_mode: ?std.builtin.LinkMode = null,
|
|
version: ?std.SemanticVersion = null,
|
|
};
|
|
|
|
/// Returns the standard file system basename of a binary generated by the Zig compiler.
|
|
pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMemory}![]u8 {
|
|
const root_name = options.root_name;
|
|
const t = options.target;
|
|
switch (t.ofmt) {
|
|
.coff => switch (options.output_mode) {
|
|
.Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, t.exeFileExt() }),
|
|
.Lib => {
|
|
const suffix = switch (options.link_mode orelse .static) {
|
|
.static => ".lib",
|
|
.dynamic => ".dll",
|
|
};
|
|
return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, suffix });
|
|
},
|
|
.Obj => return std.fmt.allocPrint(allocator, "{s}.obj", .{root_name}),
|
|
},
|
|
.elf, .goff, .xcoff => switch (options.output_mode) {
|
|
.Exe => return allocator.dupe(u8, root_name),
|
|
.Lib => {
|
|
switch (options.link_mode orelse .static) {
|
|
.static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{
|
|
t.libPrefix(), root_name,
|
|
}),
|
|
.dynamic => {
|
|
if (options.version) |ver| {
|
|
return std.fmt.allocPrint(allocator, "{s}{s}.so.{d}.{d}.{d}", .{
|
|
t.libPrefix(), root_name, ver.major, ver.minor, ver.patch,
|
|
});
|
|
} else {
|
|
return std.fmt.allocPrint(allocator, "{s}{s}.so", .{
|
|
t.libPrefix(), root_name,
|
|
});
|
|
}
|
|
},
|
|
}
|
|
},
|
|
.Obj => return std.fmt.allocPrint(allocator, "{s}.o", .{root_name}),
|
|
},
|
|
.macho => switch (options.output_mode) {
|
|
.Exe => return allocator.dupe(u8, root_name),
|
|
.Lib => {
|
|
switch (options.link_mode orelse .static) {
|
|
.static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{
|
|
t.libPrefix(), root_name,
|
|
}),
|
|
.dynamic => {
|
|
if (options.version) |ver| {
|
|
return std.fmt.allocPrint(allocator, "{s}{s}.{d}.{d}.{d}.dylib", .{
|
|
t.libPrefix(), root_name, ver.major, ver.minor, ver.patch,
|
|
});
|
|
} else {
|
|
return std.fmt.allocPrint(allocator, "{s}{s}.dylib", .{
|
|
t.libPrefix(), root_name,
|
|
});
|
|
}
|
|
},
|
|
}
|
|
},
|
|
.Obj => return std.fmt.allocPrint(allocator, "{s}.o", .{root_name}),
|
|
},
|
|
.wasm => switch (options.output_mode) {
|
|
.Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, t.exeFileExt() }),
|
|
.Lib => {
|
|
switch (options.link_mode orelse .static) {
|
|
.static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{
|
|
t.libPrefix(), root_name,
|
|
}),
|
|
.dynamic => return std.fmt.allocPrint(allocator, "{s}.wasm", .{root_name}),
|
|
}
|
|
},
|
|
.Obj => return std.fmt.allocPrint(allocator, "{s}.o", .{root_name}),
|
|
},
|
|
.c => return std.fmt.allocPrint(allocator, "{s}.c", .{root_name}),
|
|
.spirv => return std.fmt.allocPrint(allocator, "{s}.spv", .{root_name}),
|
|
.hex => return std.fmt.allocPrint(allocator, "{s}.ihex", .{root_name}),
|
|
.raw => return std.fmt.allocPrint(allocator, "{s}.bin", .{root_name}),
|
|
.plan9 => switch (options.output_mode) {
|
|
.Exe => return allocator.dupe(u8, root_name),
|
|
.Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{
|
|
root_name, t.ofmt.fileExt(t.cpu.arch),
|
|
}),
|
|
.Lib => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{
|
|
t.libPrefix(), root_name,
|
|
}),
|
|
},
|
|
}
|
|
}
|
|
|
|
pub const SanitizeC = enum {
|
|
off,
|
|
trap,
|
|
full,
|
|
};
|
|
|
|
pub const BuildId = union(enum) {
|
|
none,
|
|
fast,
|
|
uuid,
|
|
sha1,
|
|
md5,
|
|
hexstring: HexString,
|
|
|
|
pub fn eql(a: BuildId, b: BuildId) bool {
|
|
const Tag = @typeInfo(BuildId).@"union".tag_type.?;
|
|
const a_tag: Tag = a;
|
|
const b_tag: Tag = b;
|
|
if (a_tag != b_tag) return false;
|
|
return switch (a) {
|
|
.none, .fast, .uuid, .sha1, .md5 => true,
|
|
.hexstring => |a_hexstring| std.mem.eql(u8, a_hexstring.toSlice(), b.hexstring.toSlice()),
|
|
};
|
|
}
|
|
|
|
pub const HexString = struct {
|
|
bytes: [32]u8,
|
|
len: u8,
|
|
|
|
/// Result is byte values, *not* hex-encoded.
|
|
pub fn toSlice(hs: *const HexString) []const u8 {
|
|
return hs.bytes[0..hs.len];
|
|
}
|
|
};
|
|
|
|
/// Input is byte values, *not* hex-encoded.
|
|
/// Asserts `bytes` fits inside `HexString`
|
|
pub fn initHexString(bytes: []const u8) BuildId {
|
|
var result: BuildId = .{ .hexstring = .{
|
|
.bytes = undefined,
|
|
.len = @intCast(bytes.len),
|
|
} };
|
|
@memcpy(result.hexstring.bytes[0..bytes.len], bytes);
|
|
return result;
|
|
}
|
|
|
|
/// Converts UTF-8 text to a `BuildId`.
|
|
pub fn parse(text: []const u8) !BuildId {
|
|
if (std.mem.eql(u8, text, "none")) {
|
|
return .none;
|
|
} else if (std.mem.eql(u8, text, "fast")) {
|
|
return .fast;
|
|
} else if (std.mem.eql(u8, text, "uuid")) {
|
|
return .uuid;
|
|
} else if (std.mem.eql(u8, text, "sha1") or std.mem.eql(u8, text, "tree")) {
|
|
return .sha1;
|
|
} else if (std.mem.eql(u8, text, "md5")) {
|
|
return .md5;
|
|
} else if (std.mem.startsWith(u8, text, "0x")) {
|
|
var result: BuildId = .{ .hexstring = undefined };
|
|
const slice = try std.fmt.hexToBytes(&result.hexstring.bytes, text[2..]);
|
|
result.hexstring.len = @as(u8, @intCast(slice.len));
|
|
return result;
|
|
}
|
|
return error.InvalidBuildIdStyle;
|
|
}
|
|
|
|
test parse {
|
|
try std.testing.expectEqual(BuildId.md5, try parse("md5"));
|
|
try std.testing.expectEqual(BuildId.none, try parse("none"));
|
|
try std.testing.expectEqual(BuildId.fast, try parse("fast"));
|
|
try std.testing.expectEqual(BuildId.uuid, try parse("uuid"));
|
|
try std.testing.expectEqual(BuildId.sha1, try parse("sha1"));
|
|
try std.testing.expectEqual(BuildId.sha1, try parse("tree"));
|
|
|
|
try std.testing.expect(BuildId.initHexString("").eql(try parse("0x")));
|
|
try std.testing.expect(BuildId.initHexString("\x12\x34\x56").eql(try parse("0x123456")));
|
|
try std.testing.expectError(error.InvalidLength, parse("0x12-34"));
|
|
try std.testing.expectError(error.InvalidCharacter, parse("0xfoobbb"));
|
|
try std.testing.expectError(error.InvalidBuildIdStyle, parse("yaddaxxx"));
|
|
}
|
|
};
|
|
|
|
pub const LtoMode = enum { none, full, thin };
|
|
|
|
/// Renders a `std.Target.Cpu` value into a textual representation that can be parsed
|
|
/// via the `-mcpu` flag passed to the Zig compiler.
|
|
/// Appends the result to `buffer`.
|
|
pub fn serializeCpu(buffer: *std.ArrayList(u8), cpu: std.Target.Cpu) Allocator.Error!void {
|
|
const all_features = cpu.arch.allFeaturesList();
|
|
var populated_cpu_features = cpu.model.features;
|
|
populated_cpu_features.populateDependencies(all_features);
|
|
|
|
try buffer.appendSlice(cpu.model.name);
|
|
|
|
if (populated_cpu_features.eql(cpu.features)) {
|
|
// The CPU name alone is sufficient.
|
|
return;
|
|
}
|
|
|
|
for (all_features, 0..) |feature, i_usize| {
|
|
const i: std.Target.Cpu.Feature.Set.Index = @intCast(i_usize);
|
|
const in_cpu_set = populated_cpu_features.isEnabled(i);
|
|
const in_actual_set = cpu.features.isEnabled(i);
|
|
try buffer.ensureUnusedCapacity(feature.name.len + 1);
|
|
if (in_cpu_set and !in_actual_set) {
|
|
buffer.appendAssumeCapacity('-');
|
|
buffer.appendSliceAssumeCapacity(feature.name);
|
|
} else if (!in_cpu_set and in_actual_set) {
|
|
buffer.appendAssumeCapacity('+');
|
|
buffer.appendSliceAssumeCapacity(feature.name);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn serializeCpuAlloc(ally: Allocator, cpu: std.Target.Cpu) Allocator.Error![]u8 {
|
|
var buffer = std.ArrayList(u8).init(ally);
|
|
try serializeCpu(&buffer, cpu);
|
|
return buffer.toOwnedSlice();
|
|
}
|
|
|
|
const std = @import("std.zig");
|
|
const tokenizer = @import("zig/tokenizer.zig");
|
|
const assert = std.debug.assert;
|
|
const Allocator = std.mem.Allocator;
|
|
|
|
/// Return a Formatter for a Zig identifier, escaping it with `@""` syntax if needed.
|
|
///
|
|
/// - An empty `{}` format specifier escapes invalid identifiers, identifiers that shadow primitives
|
|
/// and the reserved `_` identifier.
|
|
/// - Add `p` to the specifier to render identifiers that shadow primitives unescaped.
|
|
/// - Add `_` to the specifier to render the reserved `_` identifier unescaped.
|
|
/// - `p` and `_` can be combined, e.g. `{p_}`.
|
|
///
|
|
pub fn fmtId(bytes: []const u8) std.fmt.Formatter(formatId) {
|
|
return .{ .data = bytes };
|
|
}
|
|
|
|
test fmtId {
|
|
const expectFmt = std.testing.expectFmt;
|
|
try expectFmt("@\"while\"", "{}", .{fmtId("while")});
|
|
try expectFmt("@\"while\"", "{p}", .{fmtId("while")});
|
|
try expectFmt("@\"while\"", "{_}", .{fmtId("while")});
|
|
try expectFmt("@\"while\"", "{p_}", .{fmtId("while")});
|
|
try expectFmt("@\"while\"", "{_p}", .{fmtId("while")});
|
|
|
|
try expectFmt("hello", "{}", .{fmtId("hello")});
|
|
try expectFmt("hello", "{p}", .{fmtId("hello")});
|
|
try expectFmt("hello", "{_}", .{fmtId("hello")});
|
|
try expectFmt("hello", "{p_}", .{fmtId("hello")});
|
|
try expectFmt("hello", "{_p}", .{fmtId("hello")});
|
|
|
|
try expectFmt("@\"type\"", "{}", .{fmtId("type")});
|
|
try expectFmt("type", "{p}", .{fmtId("type")});
|
|
try expectFmt("@\"type\"", "{_}", .{fmtId("type")});
|
|
try expectFmt("type", "{p_}", .{fmtId("type")});
|
|
try expectFmt("type", "{_p}", .{fmtId("type")});
|
|
|
|
try expectFmt("@\"_\"", "{}", .{fmtId("_")});
|
|
try expectFmt("@\"_\"", "{p}", .{fmtId("_")});
|
|
try expectFmt("_", "{_}", .{fmtId("_")});
|
|
try expectFmt("_", "{p_}", .{fmtId("_")});
|
|
try expectFmt("_", "{_p}", .{fmtId("_")});
|
|
|
|
try expectFmt("@\"i123\"", "{}", .{fmtId("i123")});
|
|
try expectFmt("i123", "{p}", .{fmtId("i123")});
|
|
try expectFmt("@\"4four\"", "{}", .{fmtId("4four")});
|
|
try expectFmt("_underscore", "{}", .{fmtId("_underscore")});
|
|
try expectFmt("@\"11\\\"23\"", "{}", .{fmtId("11\"23")});
|
|
try expectFmt("@\"11\\x0f23\"", "{}", .{fmtId("11\x0F23")});
|
|
|
|
// These are technically not currently legal in Zig.
|
|
try expectFmt("@\"\"", "{}", .{fmtId("")});
|
|
try expectFmt("@\"\\x00\"", "{}", .{fmtId("\x00")});
|
|
}
|
|
|
|
/// Print the string as a Zig identifier, escaping it with `@""` syntax if needed.
|
|
fn formatId(
|
|
bytes: []const u8,
|
|
comptime fmt: []const u8,
|
|
options: std.fmt.FormatOptions,
|
|
writer: anytype,
|
|
) !void {
|
|
const allow_primitive, const allow_underscore = comptime parse_fmt: {
|
|
var allow_primitive = false;
|
|
var allow_underscore = false;
|
|
for (fmt) |char| {
|
|
switch (char) {
|
|
'p' => if (!allow_primitive) {
|
|
allow_primitive = true;
|
|
continue;
|
|
},
|
|
'_' => if (!allow_underscore) {
|
|
allow_underscore = true;
|
|
continue;
|
|
},
|
|
else => {},
|
|
}
|
|
@compileError("expected {}, {p}, {_}, {p_} or {_p}, found {" ++ fmt ++ "}");
|
|
}
|
|
break :parse_fmt .{ allow_primitive, allow_underscore };
|
|
};
|
|
|
|
if (isValidId(bytes) and
|
|
(allow_primitive or !std.zig.isPrimitive(bytes)) and
|
|
(allow_underscore or !isUnderscore(bytes)))
|
|
{
|
|
return writer.writeAll(bytes);
|
|
}
|
|
try writer.writeAll("@\"");
|
|
try stringEscape(bytes, "", options, writer);
|
|
try writer.writeByte('"');
|
|
}
|
|
|
|
/// Return a Formatter for Zig Escapes of a double quoted string.
|
|
/// The format specifier must be one of:
|
|
/// * `{}` treats contents as a double-quoted string.
|
|
/// * `{'}` treats contents as a single-quoted string.
|
|
pub fn fmtEscapes(bytes: []const u8) std.fmt.Formatter(stringEscape) {
|
|
return .{ .data = bytes };
|
|
}
|
|
|
|
test fmtEscapes {
|
|
const expectFmt = std.testing.expectFmt;
|
|
try expectFmt("\\x0f", "{}", .{fmtEscapes("\x0f")});
|
|
try expectFmt(
|
|
\\" \\ hi \x07 \x11 " derp \'"
|
|
, "\"{'}\"", .{fmtEscapes(" \\ hi \x07 \x11 \" derp '")});
|
|
try expectFmt(
|
|
\\" \\ hi \x07 \x11 \" derp '"
|
|
, "\"{}\"", .{fmtEscapes(" \\ hi \x07 \x11 \" derp '")});
|
|
}
|
|
|
|
/// Print the string as escaped contents of a double quoted or single-quoted string.
|
|
/// Format `{}` treats contents as a double-quoted string.
|
|
/// Format `{'}` treats contents as a single-quoted string.
|
|
pub fn stringEscape(
|
|
bytes: []const u8,
|
|
comptime f: []const u8,
|
|
options: std.fmt.FormatOptions,
|
|
writer: anytype,
|
|
) !void {
|
|
_ = options;
|
|
for (bytes) |byte| switch (byte) {
|
|
'\n' => try writer.writeAll("\\n"),
|
|
'\r' => try writer.writeAll("\\r"),
|
|
'\t' => try writer.writeAll("\\t"),
|
|
'\\' => try writer.writeAll("\\\\"),
|
|
'"' => {
|
|
if (f.len == 1 and f[0] == '\'') {
|
|
try writer.writeByte('"');
|
|
} else if (f.len == 0) {
|
|
try writer.writeAll("\\\"");
|
|
} else {
|
|
@compileError("expected {} or {'}, found {" ++ f ++ "}");
|
|
}
|
|
},
|
|
'\'' => {
|
|
if (f.len == 1 and f[0] == '\'') {
|
|
try writer.writeAll("\\'");
|
|
} else if (f.len == 0) {
|
|
try writer.writeByte('\'');
|
|
} else {
|
|
@compileError("expected {} or {'}, found {" ++ f ++ "}");
|
|
}
|
|
},
|
|
' ', '!', '#'...'&', '('...'[', ']'...'~' => try writer.writeByte(byte),
|
|
// Use hex escapes for rest any unprintable characters.
|
|
else => {
|
|
try writer.writeAll("\\x");
|
|
try std.fmt.formatInt(byte, 16, .lower, .{ .width = 2, .fill = '0' }, writer);
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn isValidId(bytes: []const u8) bool {
|
|
if (bytes.len == 0) return false;
|
|
for (bytes, 0..) |c, i| {
|
|
switch (c) {
|
|
'_', 'a'...'z', 'A'...'Z' => {},
|
|
'0'...'9' => if (i == 0) return false,
|
|
else => return false,
|
|
}
|
|
}
|
|
return std.zig.Token.getKeyword(bytes) == null;
|
|
}
|
|
|
|
test isValidId {
|
|
try std.testing.expect(!isValidId(""));
|
|
try std.testing.expect(isValidId("foobar"));
|
|
try std.testing.expect(!isValidId("a b c"));
|
|
try std.testing.expect(!isValidId("3d"));
|
|
try std.testing.expect(!isValidId("enum"));
|
|
try std.testing.expect(isValidId("i386"));
|
|
}
|
|
|
|
pub fn isUnderscore(bytes: []const u8) bool {
|
|
return bytes.len == 1 and bytes[0] == '_';
|
|
}
|
|
|
|
test isUnderscore {
|
|
try std.testing.expect(isUnderscore("_"));
|
|
try std.testing.expect(!isUnderscore("__"));
|
|
try std.testing.expect(!isUnderscore("_foo"));
|
|
try std.testing.expect(isUnderscore("\x5f"));
|
|
try std.testing.expect(!isUnderscore("\\x5f"));
|
|
}
|
|
|
|
pub fn readSourceFileToEndAlloc(gpa: Allocator, input: std.fs.File, size_hint: ?usize) ![:0]u8 {
|
|
const source_code = input.readToEndAllocOptions(
|
|
gpa,
|
|
max_src_size,
|
|
size_hint,
|
|
.of(u8),
|
|
0,
|
|
) catch |err| switch (err) {
|
|
error.ConnectionResetByPeer => unreachable,
|
|
error.ConnectionTimedOut => unreachable,
|
|
error.NotOpenForReading => unreachable,
|
|
else => |e| return e,
|
|
};
|
|
errdefer gpa.free(source_code);
|
|
|
|
// Detect unsupported file types with their Byte Order Mark
|
|
const unsupported_boms = [_][]const u8{
|
|
"\xff\xfe\x00\x00", // UTF-32 little endian
|
|
"\xfe\xff\x00\x00", // UTF-32 big endian
|
|
"\xfe\xff", // UTF-16 big endian
|
|
};
|
|
for (unsupported_boms) |bom| {
|
|
if (std.mem.startsWith(u8, source_code, bom)) {
|
|
return error.UnsupportedEncoding;
|
|
}
|
|
}
|
|
|
|
// If the file starts with a UTF-16 little endian BOM, translate it to UTF-8
|
|
if (std.mem.startsWith(u8, source_code, "\xff\xfe")) {
|
|
if (source_code.len % 2 != 0) return error.InvalidEncoding;
|
|
// TODO: after wrangle-writer-buffering branch is merged,
|
|
// avoid this unnecessary allocation
|
|
const aligned_copy = try gpa.alloc(u16, source_code.len / 2);
|
|
defer gpa.free(aligned_copy);
|
|
@memcpy(std.mem.sliceAsBytes(aligned_copy), source_code);
|
|
const source_code_utf8 = std.unicode.utf16LeToUtf8AllocZ(gpa, aligned_copy) catch |err| switch (err) {
|
|
error.DanglingSurrogateHalf => error.UnsupportedEncoding,
|
|
error.ExpectedSecondSurrogateHalf => error.UnsupportedEncoding,
|
|
error.UnexpectedSecondSurrogateHalf => error.UnsupportedEncoding,
|
|
else => |e| return e,
|
|
};
|
|
gpa.free(source_code);
|
|
return source_code_utf8;
|
|
}
|
|
|
|
return source_code;
|
|
}
|
|
|
|
pub fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color: Color) !void {
|
|
var wip_errors: std.zig.ErrorBundle.Wip = undefined;
|
|
try wip_errors.init(gpa);
|
|
defer wip_errors.deinit();
|
|
|
|
try putAstErrorsIntoBundle(gpa, tree, path, &wip_errors);
|
|
|
|
var error_bundle = try wip_errors.toOwnedBundle("");
|
|
defer error_bundle.deinit(gpa);
|
|
error_bundle.renderToStdErr(color.renderOptions());
|
|
}
|
|
|
|
pub fn putAstErrorsIntoBundle(
|
|
gpa: Allocator,
|
|
tree: Ast,
|
|
path: []const u8,
|
|
wip_errors: *std.zig.ErrorBundle.Wip,
|
|
) Allocator.Error!void {
|
|
var zir = try AstGen.generate(gpa, tree);
|
|
defer zir.deinit(gpa);
|
|
|
|
try wip_errors.addZirErrorMessages(zir, tree, tree.source, path);
|
|
}
|
|
|
|
pub fn resolveTargetQueryOrFatal(target_query: std.Target.Query) std.Target {
|
|
return std.zig.system.resolveTargetQuery(target_query) catch |err|
|
|
fatal("unable to resolve target: {s}", .{@errorName(err)});
|
|
}
|
|
|
|
pub fn parseTargetQueryOrReportFatalError(
|
|
allocator: Allocator,
|
|
opts: std.Target.Query.ParseOptions,
|
|
) std.Target.Query {
|
|
var opts_with_diags = opts;
|
|
var diags: std.Target.Query.ParseOptions.Diagnostics = .{};
|
|
if (opts_with_diags.diagnostics == null) {
|
|
opts_with_diags.diagnostics = &diags;
|
|
}
|
|
return std.Target.Query.parse(opts_with_diags) catch |err| switch (err) {
|
|
error.UnknownCpuModel => {
|
|
help: {
|
|
var help_text = std.ArrayList(u8).init(allocator);
|
|
defer help_text.deinit();
|
|
for (diags.arch.?.allCpuModels()) |cpu| {
|
|
help_text.writer().print(" {s}\n", .{cpu.name}) catch break :help;
|
|
}
|
|
std.log.info("available CPUs for architecture '{s}':\n{s}", .{
|
|
@tagName(diags.arch.?), help_text.items,
|
|
});
|
|
}
|
|
fatal("unknown CPU: '{s}'", .{diags.cpu_name.?});
|
|
},
|
|
error.UnknownCpuFeature => {
|
|
help: {
|
|
var help_text = std.ArrayList(u8).init(allocator);
|
|
defer help_text.deinit();
|
|
for (diags.arch.?.allFeaturesList()) |feature| {
|
|
help_text.writer().print(" {s}: {s}\n", .{ feature.name, feature.description }) catch break :help;
|
|
}
|
|
std.log.info("available CPU features for architecture '{s}':\n{s}", .{
|
|
@tagName(diags.arch.?), help_text.items,
|
|
});
|
|
}
|
|
fatal("unknown CPU feature: '{s}'", .{diags.unknown_feature_name.?});
|
|
},
|
|
error.UnknownObjectFormat => {
|
|
help: {
|
|
var help_text = std.ArrayList(u8).init(allocator);
|
|
defer help_text.deinit();
|
|
inline for (@typeInfo(std.Target.ObjectFormat).@"enum".fields) |field| {
|
|
help_text.writer().print(" {s}\n", .{field.name}) catch break :help;
|
|
}
|
|
std.log.info("available object formats:\n{s}", .{help_text.items});
|
|
}
|
|
fatal("unknown object format: '{s}'", .{opts.object_format.?});
|
|
},
|
|
error.UnknownArchitecture => {
|
|
help: {
|
|
var help_text = std.ArrayList(u8).init(allocator);
|
|
defer help_text.deinit();
|
|
inline for (@typeInfo(std.Target.Cpu.Arch).@"enum".fields) |field| {
|
|
help_text.writer().print(" {s}\n", .{field.name}) catch break :help;
|
|
}
|
|
std.log.info("available architectures:\n{s} native\n", .{help_text.items});
|
|
}
|
|
fatal("unknown architecture: '{s}'", .{diags.unknown_architecture_name.?});
|
|
},
|
|
else => |e| fatal("unable to parse target query '{s}': {s}", .{
|
|
opts.arch_os_abi, @errorName(e),
|
|
}),
|
|
};
|
|
}
|
|
|
|
/// Deprecated; see `std.process.fatal`.
|
|
pub const fatal = std.process.fatal;
|
|
|
|
/// Collects all the environment variables that Zig could possibly inspect, so
|
|
/// that we can do reflection on this and print them with `zig env`.
|
|
pub const EnvVar = enum {
|
|
ZIG_GLOBAL_CACHE_DIR,
|
|
ZIG_LOCAL_CACHE_DIR,
|
|
ZIG_LIB_DIR,
|
|
ZIG_LIBC,
|
|
ZIG_BUILD_RUNNER,
|
|
ZIG_VERBOSE_LINK,
|
|
ZIG_VERBOSE_CC,
|
|
ZIG_BTRFS_WORKAROUND,
|
|
ZIG_DEBUG_CMD,
|
|
CC,
|
|
NO_COLOR,
|
|
CLICOLOR_FORCE,
|
|
XDG_CACHE_HOME,
|
|
HOME,
|
|
|
|
pub fn isSet(comptime ev: EnvVar) bool {
|
|
return std.process.hasNonEmptyEnvVarConstant(@tagName(ev));
|
|
}
|
|
|
|
pub fn get(ev: EnvVar, arena: std.mem.Allocator) !?[]u8 {
|
|
if (std.process.getEnvVarOwned(arena, @tagName(ev))) |value| {
|
|
return value;
|
|
} else |err| switch (err) {
|
|
error.EnvironmentVariableNotFound => return null,
|
|
else => |e| return e,
|
|
}
|
|
}
|
|
|
|
pub fn getPosix(comptime ev: EnvVar) ?[:0]const u8 {
|
|
return std.posix.getenvZ(@tagName(ev));
|
|
}
|
|
};
|
|
|
|
pub const SimpleComptimeReason = enum(u32) {
|
|
// Evaluating at comptime because a builtin operand must be comptime-known.
|
|
// These messages all mention a specific builtin.
|
|
operand_Type,
|
|
operand_setEvalBranchQuota,
|
|
operand_setFloatMode,
|
|
operand_branchHint,
|
|
operand_setRuntimeSafety,
|
|
operand_embedFile,
|
|
operand_cImport,
|
|
operand_cDefine_macro_name,
|
|
operand_cDefine_macro_value,
|
|
operand_cInclude_file_name,
|
|
operand_cUndef_macro_name,
|
|
operand_shuffle_mask,
|
|
operand_atomicRmw_operation,
|
|
operand_reduce_operation,
|
|
|
|
// Evaluating at comptime because an operand must be comptime-known.
|
|
// These messages do not mention a specific builtin (and may not be about a builtin at all).
|
|
export_target,
|
|
export_options,
|
|
extern_options,
|
|
prefetch_options,
|
|
call_modifier,
|
|
compile_error_string,
|
|
inline_assembly_code,
|
|
atomic_order,
|
|
array_mul_factor,
|
|
slice_cat_operand,
|
|
inline_call_target,
|
|
generic_call_target,
|
|
wasm_memory_index,
|
|
work_group_dim_index,
|
|
|
|
// Evaluating at comptime because types must be comptime-known.
|
|
// Reasons other than `.type` are just more specific messages.
|
|
type,
|
|
array_sentinel,
|
|
pointer_sentinel,
|
|
slice_sentinel,
|
|
array_length,
|
|
vector_length,
|
|
error_set_contents,
|
|
struct_fields,
|
|
enum_fields,
|
|
union_fields,
|
|
function_ret_ty,
|
|
function_parameters,
|
|
|
|
// Evaluating at comptime because decl/field name must be comptime-known.
|
|
decl_name,
|
|
field_name,
|
|
struct_field_name,
|
|
enum_field_name,
|
|
union_field_name,
|
|
tuple_field_name,
|
|
tuple_field_index,
|
|
|
|
// Evaluating at comptime because it is an attribute of a global declaration.
|
|
container_var_init,
|
|
@"callconv",
|
|
@"align",
|
|
@"addrspace",
|
|
@"linksection",
|
|
|
|
// Miscellaneous reasons.
|
|
comptime_keyword,
|
|
comptime_call_modifier,
|
|
inline_loop_operand,
|
|
switch_item,
|
|
tuple_field_default_value,
|
|
struct_field_default_value,
|
|
enum_field_tag_value,
|
|
slice_single_item_ptr_bounds,
|
|
stored_to_comptime_field,
|
|
stored_to_comptime_var,
|
|
casted_to_comptime_enum,
|
|
casted_to_comptime_int,
|
|
casted_to_comptime_float,
|
|
panic_handler,
|
|
|
|
pub fn message(r: SimpleComptimeReason) []const u8 {
|
|
return switch (r) {
|
|
// zig fmt: off
|
|
.operand_Type => "operand to '@Type' must be comptime-known",
|
|
.operand_setEvalBranchQuota => "operand to '@setEvalBranchQuota' must be comptime-known",
|
|
.operand_setFloatMode => "operand to '@setFloatMode' must be comptime-known",
|
|
.operand_branchHint => "operand to '@branchHint' must be comptime-known",
|
|
.operand_setRuntimeSafety => "operand to '@setRuntimeSafety' must be comptime-known",
|
|
.operand_embedFile => "operand to '@embedFile' must be comptime-known",
|
|
.operand_cImport => "operand to '@cImport' is evaluated at comptime",
|
|
.operand_cDefine_macro_name => "'@cDefine' macro name must be comptime-known",
|
|
.operand_cDefine_macro_value => "'@cDefine' macro value must be comptime-known",
|
|
.operand_cInclude_file_name => "'@cInclude' file name must be comptime-known",
|
|
.operand_cUndef_macro_name => "'@cUndef' macro name must be comptime-known",
|
|
.operand_shuffle_mask => "'@shuffle' mask must be comptime-known",
|
|
.operand_atomicRmw_operation => "'@atomicRmw' operation must be comptime-known",
|
|
.operand_reduce_operation => "'@reduce' operation must be comptime-known",
|
|
|
|
.export_target => "export target must be comptime-known",
|
|
.export_options => "export options must be comptime-known",
|
|
.extern_options => "extern options must be comptime-known",
|
|
.prefetch_options => "prefetch options must be comptime-known",
|
|
.call_modifier => "call modifier must be comptime-known",
|
|
.compile_error_string => "compile error string must be comptime-known",
|
|
.inline_assembly_code => "inline assembly code must be comptime-known",
|
|
.atomic_order => "atomic order must be comptime-known",
|
|
.array_mul_factor => "array multiplication factor must be comptime-known",
|
|
.slice_cat_operand => "slice being concatenated must be comptime-known",
|
|
.inline_call_target => "function being called inline must be comptime-known",
|
|
.generic_call_target => "generic function being called must be comptime-known",
|
|
.wasm_memory_index => "wasm memory index must be comptime-known",
|
|
.work_group_dim_index => "work group dimension index must be comptime-known",
|
|
|
|
.type => "types must be comptime-known",
|
|
.array_sentinel => "array sentinel value must be comptime-known",
|
|
.pointer_sentinel => "pointer sentinel value must be comptime-known",
|
|
.slice_sentinel => "slice sentinel value must be comptime-known",
|
|
.array_length => "array length must be comptime-known",
|
|
.vector_length => "vector length must be comptime-known",
|
|
.error_set_contents => "error set contents must be comptime-known",
|
|
.struct_fields => "struct fields must be comptime-known",
|
|
.enum_fields => "enum fields must be comptime-known",
|
|
.union_fields => "union fields must be comptime-known",
|
|
.function_ret_ty => "function return type must be comptime-known",
|
|
.function_parameters => "function parameters must be comptime-known",
|
|
|
|
.decl_name => "declaration name must be comptime-known",
|
|
.field_name => "field name must be comptime-known",
|
|
.struct_field_name => "struct field name must be comptime-known",
|
|
.enum_field_name => "enum field name must be comptime-known",
|
|
.union_field_name => "union field name must be comptime-known",
|
|
.tuple_field_name => "tuple field name must be comptime-known",
|
|
.tuple_field_index => "tuple field index must be comptime-known",
|
|
|
|
.container_var_init => "initializer of container-level variable must be comptime-known",
|
|
.@"callconv" => "calling convention must be comptime-known",
|
|
.@"align" => "alignment must be comptime-known",
|
|
.@"addrspace" => "address space must be comptime-known",
|
|
.@"linksection" => "linksection must be comptime-known",
|
|
|
|
.comptime_keyword => "'comptime' keyword forces comptime evaluation",
|
|
.comptime_call_modifier => "'.compile_time' call modifier forces comptime evaluation",
|
|
.inline_loop_operand => "inline loop condition must be comptime-known",
|
|
.switch_item => "switch prong values must be comptime-known",
|
|
.tuple_field_default_value => "tuple field default value must be comptime-known",
|
|
.struct_field_default_value => "struct field default value must be comptime-known",
|
|
.enum_field_tag_value => "enum field tag value must be comptime-known",
|
|
.slice_single_item_ptr_bounds => "slice of single-item pointer must have comptime-known bounds",
|
|
.stored_to_comptime_field => "value stored to a comptime field must be comptime-known",
|
|
.stored_to_comptime_var => "value stored to a comptime variable must be comptime-known",
|
|
.casted_to_comptime_enum => "value casted to enum with 'comptime_int' tag type must be comptime-known",
|
|
.casted_to_comptime_int => "value casted to 'comptime_int' must be comptime-known",
|
|
.casted_to_comptime_float => "value casted to 'comptime_float' must be comptime-known",
|
|
.panic_handler => "panic handler must be comptime-known",
|
|
// zig fmt: on
|
|
};
|
|
}
|
|
};
|
|
|
|
/// Every kind of artifact which the compiler can emit.
|
|
pub const EmitArtifact = enum {
|
|
bin,
|
|
@"asm",
|
|
implib,
|
|
llvm_ir,
|
|
llvm_bc,
|
|
docs,
|
|
pdb,
|
|
h,
|
|
|
|
/// If using `Server` to communicate with the compiler, it will place requested artifacts in
|
|
/// paths under the output directory, where those paths are named according to this function.
|
|
/// Returned string is allocated with `gpa` and owned by the caller.
|
|
pub fn cacheName(ea: EmitArtifact, gpa: Allocator, opts: BinNameOptions) Allocator.Error![]const u8 {
|
|
const suffix: []const u8 = switch (ea) {
|
|
.bin => return binNameAlloc(gpa, opts),
|
|
.@"asm" => ".s",
|
|
.implib => ".lib",
|
|
.llvm_ir => ".ll",
|
|
.llvm_bc => ".bc",
|
|
.docs => "-docs",
|
|
.pdb => ".pdb",
|
|
.h => ".h",
|
|
};
|
|
return std.fmt.allocPrint(gpa, "{s}{s}", .{ opts.root_name, suffix });
|
|
}
|
|
};
|
|
|
|
test {
|
|
_ = Ast;
|
|
_ = AstRlAnnotate;
|
|
_ = BuiltinFn;
|
|
_ = Client;
|
|
_ = ErrorBundle;
|
|
_ = LibCDirs;
|
|
_ = LibCInstallation;
|
|
_ = Server;
|
|
_ = WindowsSdk;
|
|
_ = number_literal;
|
|
_ = primitives;
|
|
_ = string_literal;
|
|
_ = system;
|
|
_ = target;
|
|
_ = c_translation;
|
|
}
|