diff --git a/build.zig b/build.zig index 5f55726d05..7290d770db 100644 --- a/build.zig +++ b/build.zig @@ -305,7 +305,7 @@ fn configureStage2(b: *Builder, exe: var, ctx: Context) !void { } dependOnLib(b, exe, ctx.llvm); - if (exe.target.getOs() == .linux) { + if (exe.target.getOsTag() == .linux) { try addCxxKnownPath(b, ctx, exe, "libstdc++.a", \\Unable to determine path to libstdc++.a \\On Fedora, install libstdc++-static and try again. diff --git a/doc/docgen.zig b/doc/docgen.zig index b429c93e65..5d7f2b7b38 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -1,5 +1,5 @@ -const builtin = @import("builtin"); const std = @import("std"); +const builtin = std.builtin; const io = std.io; const fs = std.fs; const process = std.process; @@ -10,8 +10,8 @@ const testing = std.testing; const max_doc_file_size = 10 * 1024 * 1024; -const exe_ext = @as(std.build.Target, std.build.Target.Native).exeFileExt(); -const obj_ext = @as(std.build.Target, std.build.Target.Native).oFileExt(); +const exe_ext = @as(std.zig.CrossTarget, .{}).exeFileExt(); +const obj_ext = @as(std.zig.CrossTarget, .{}).oFileExt(); const tmp_dir_name = "docgen_tmp"; const test_out_path = tmp_dir_name ++ fs.path.sep_str ++ "test" ++ exe_ext; @@ -521,7 +521,7 @@ fn genToc(allocator: *mem.Allocator, tokenizer: *Tokenizer) !Toc { return parseError(tokenizer, code_kind_tok, "unrecognized code kind: {}", .{code_kind_str}); } - var mode = builtin.Mode.Debug; + var mode: builtin.Mode = .Debug; var link_objects = std.ArrayList([]const u8).init(allocator); defer link_objects.deinit(); var target_str: ?[]const u8 = null; @@ -533,9 +533,9 @@ fn genToc(allocator: *mem.Allocator, tokenizer: *Tokenizer) !Toc { const end_code_tag = try eatToken(tokenizer, Token.Id.TagContent); const end_tag_name = tokenizer.buffer[end_code_tag.start..end_code_tag.end]; if (mem.eql(u8, end_tag_name, "code_release_fast")) { - mode = builtin.Mode.ReleaseFast; + mode = .ReleaseFast; } else if (mem.eql(u8, end_tag_name, "code_release_safe")) { - mode = builtin.Mode.ReleaseSafe; + mode = .ReleaseSafe; } else if (mem.eql(u8, end_tag_name, "code_link_object")) { _ = try eatToken(tokenizer, Token.Id.Separator); const obj_tok = try eatToken(tokenizer, Token.Id.TagContent); @@ -1001,30 +1001,30 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var for (toc.nodes) |node| { switch (node) { - Node.Content => |data| { + .Content => |data| { try out.write(data); }, - Node.Link => |info| { + .Link => |info| { if (!toc.urls.contains(info.url)) { return parseError(tokenizer, info.token, "url not found: {}", .{info.url}); } try out.print("{}", .{ info.url, info.name }); }, - Node.Nav => { + .Nav => { try out.write(toc.toc); }, - Node.Builtin => |tok| { + .Builtin => |tok| { try out.write("
");
                 try tokenizeAndPrintRaw(tokenizer, out, tok, builtin_code);
                 try out.write("
"); }, - Node.HeaderOpen => |info| { + .HeaderOpen => |info| { try out.print( "{} ยง\n", .{ info.n, info.url, info.url, info.name, info.url, info.n }, ); }, - Node.SeeAlso => |items| { + .SeeAlso => |items| { try out.write("

See also:

\n"); }, - Node.Syntax => |content_tok| { + .Syntax => |content_tok| { try tokenizeAndPrint(tokenizer, out, content_tok); }, - Node.Code => |code| { + .Code => |code| { code_progress_index += 1; warn("docgen example code {}/{}...", .{ code_progress_index, tokenizer.code_node_count }); @@ -1075,16 +1075,16 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var }); try out.print("
$ zig build-exe {}.zig", .{code.name});
                         switch (code.mode) {
-                            builtin.Mode.Debug => {},
-                            builtin.Mode.ReleaseSafe => {
+                            .Debug => {},
+                            .ReleaseSafe => {
                                 try build_args.append("--release-safe");
                                 try out.print(" --release-safe", .{});
                             },
-                            builtin.Mode.ReleaseFast => {
+                            .ReleaseFast => {
                                 try build_args.append("--release-fast");
                                 try out.print(" --release-fast", .{});
                             },
-                            builtin.Mode.ReleaseSmall => {
+                            .ReleaseSmall => {
                                 try build_args.append("--release-small");
                                 try out.print(" --release-small", .{});
                             },
@@ -1142,13 +1142,14 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var
                             try out.print("\n{}
\n", .{colored_stderr}); break :code_block; } - const exec_result = exec(allocator, &env_map, build_args.toSliceConst()) catch return parseError(tokenizer, code.source_token, "example failed to compile", .{}); + const exec_result = exec(allocator, &env_map, build_args.toSliceConst()) catch + return parseError(tokenizer, code.source_token, "example failed to compile", .{}); if (code.target_str) |triple| { if (mem.startsWith(u8, triple, "wasm32") or mem.startsWith(u8, triple, "riscv64-linux") or - mem.startsWith(u8, triple, "x86_64-linux") and - (builtin.os != .linux or builtin.arch != .x86_64)) + (mem.startsWith(u8, triple, "x86_64-linux") and + std.Target.current.os.tag != .linux or std.Target.current.cpu.arch != .x86_64)) { // skip execution try out.print("\n", .{}); @@ -1207,16 +1208,16 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var }); try out.print("
$ zig test {}.zig", .{code.name});
                         switch (code.mode) {
-                            builtin.Mode.Debug => {},
-                            builtin.Mode.ReleaseSafe => {
+                            .Debug => {},
+                            .ReleaseSafe => {
                                 try test_args.append("--release-safe");
                                 try out.print(" --release-safe", .{});
                             },
-                            builtin.Mode.ReleaseFast => {
+                            .ReleaseFast => {
                                 try test_args.append("--release-fast");
                                 try out.print(" --release-fast", .{});
                             },
-                            builtin.Mode.ReleaseSmall => {
+                            .ReleaseSmall => {
                                 try test_args.append("--release-small");
                                 try out.print(" --release-small", .{});
                             },
@@ -1249,16 +1250,16 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var
                         });
                         try out.print("
$ zig test {}.zig", .{code.name});
                         switch (code.mode) {
-                            builtin.Mode.Debug => {},
-                            builtin.Mode.ReleaseSafe => {
+                            .Debug => {},
+                            .ReleaseSafe => {
                                 try test_args.append("--release-safe");
                                 try out.print(" --release-safe", .{});
                             },
-                            builtin.Mode.ReleaseFast => {
+                            .ReleaseFast => {
                                 try test_args.append("--release-fast");
                                 try out.print(" --release-fast", .{});
                             },
-                            builtin.Mode.ReleaseSmall => {
+                            .ReleaseSmall => {
                                 try test_args.append("--release-small");
                                 try out.print(" --release-small", .{});
                             },
@@ -1306,16 +1307,16 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var
                         });
                         var mode_arg: []const u8 = "";
                         switch (code.mode) {
-                            builtin.Mode.Debug => {},
-                            builtin.Mode.ReleaseSafe => {
+                            .Debug => {},
+                            .ReleaseSafe => {
                                 try test_args.append("--release-safe");
                                 mode_arg = " --release-safe";
                             },
-                            builtin.Mode.ReleaseFast => {
+                            .ReleaseFast => {
                                 try test_args.append("--release-fast");
                                 mode_arg = " --release-fast";
                             },
-                            builtin.Mode.ReleaseSmall => {
+                            .ReleaseSmall => {
                                 try test_args.append("--release-small");
                                 mode_arg = " --release-small";
                             },
@@ -1386,20 +1387,20 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var
                         }
 
                         switch (code.mode) {
-                            builtin.Mode.Debug => {},
-                            builtin.Mode.ReleaseSafe => {
+                            .Debug => {},
+                            .ReleaseSafe => {
                                 try build_args.append("--release-safe");
                                 if (!code.is_inline) {
                                     try out.print(" --release-safe", .{});
                                 }
                             },
-                            builtin.Mode.ReleaseFast => {
+                            .ReleaseFast => {
                                 try build_args.append("--release-fast");
                                 if (!code.is_inline) {
                                     try out.print(" --release-fast", .{});
                                 }
                             },
-                            builtin.Mode.ReleaseSmall => {
+                            .ReleaseSmall => {
                                 try build_args.append("--release-small");
                                 if (!code.is_inline) {
                                     try out.print(" --release-small", .{});
@@ -1461,16 +1462,16 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var
                         });
                         try out.print("
$ zig build-lib {}.zig", .{code.name});
                         switch (code.mode) {
-                            builtin.Mode.Debug => {},
-                            builtin.Mode.ReleaseSafe => {
+                            .Debug => {},
+                            .ReleaseSafe => {
                                 try test_args.append("--release-safe");
                                 try out.print(" --release-safe", .{});
                             },
-                            builtin.Mode.ReleaseFast => {
+                            .ReleaseFast => {
                                 try test_args.append("--release-fast");
                                 try out.print(" --release-fast", .{});
                             },
-                            builtin.Mode.ReleaseSmall => {
+                            .ReleaseSmall => {
                                 try test_args.append("--release-small");
                                 try out.print(" --release-small", .{});
                             },
diff --git a/doc/langref.html.in b/doc/langref.html.in
index e963e39af7..31def074d7 100644
--- a/doc/langref.html.in
+++ b/doc/langref.html.in
@@ -965,7 +965,8 @@ const nan = std.math.nan(f128);
           but you can switch to {#syntax#}Optimized{#endsyntax#} mode on a per-block basis:

{#code_begin|obj|foo#} {#code_release_fast#} -const builtin = @import("builtin"); +const std = @import("std"); +const builtin = std.builtin; const big = @as(f64, 1 << 40); export fn foo_strict(x: f64) f64 { @@ -2063,15 +2064,15 @@ test "pointer child type" { alignment of the underlying type, it can be omitted from the type:

{#code_begin|test#} -const assert = @import("std").debug.assert; -const builtin = @import("builtin"); +const std = @import("std"); +const assert = std.debug.assert; test "variable alignment" { var x: i32 = 1234; const align_of_i32 = @alignOf(@TypeOf(x)); assert(@TypeOf(&x) == *i32); assert(*i32 == *align(align_of_i32) i32); - if (builtin.arch == builtin.Arch.x86_64) { + if (std.Target.current.cpu.arch == .x86_64) { assert((*i32).alignment == 4); } } @@ -2474,7 +2475,7 @@ test "default struct initialization fields" {

{#code_begin|test#} const std = @import("std"); -const builtin = @import("builtin"); +const builtin = std.builtin; const assert = std.debug.assert; const Full = packed struct { @@ -3204,8 +3205,8 @@ test "separate scopes" { {#header_open|switch#} {#code_begin|test|switch#} -const assert = @import("std").debug.assert; -const builtin = @import("builtin"); +const std = @import("std"); +const assert = std.debug.assert; test "switch simple" { const a: u64 = 10; @@ -3249,16 +3250,16 @@ test "switch simple" { } // Switch expressions can be used outside a function: -const os_msg = switch (builtin.os) { - builtin.Os.linux => "we found a linux user", +const os_msg = switch (std.Target.current.os.tag) { + .linux => "we found a linux user", else => "not a linux user", }; // Inside a function, switch statements implicitly are compile-time // evaluated if the target expression is compile-time known. test "switch inside function" { - switch (builtin.os) { - builtin.Os.fuchsia => { + switch (std.Target.current.os.tag) { + .fuchsia => { // On an OS other than fuchsia, block is not even analyzed, // so this compile error is not triggered. // On fuchsia this compile error would be triggered. @@ -7330,8 +7331,6 @@ test "main" { the {#syntax#}export{#endsyntax#} keyword used on a function:

{#code_begin|obj#} -const builtin = @import("builtin"); - comptime { @export(internalName, .{ .name = "foo", .linkage = .Strong }); } @@ -9363,7 +9362,7 @@ const separator = if (builtin.os == builtin.Os.windows) '\\' else '/';

{#code_begin|test|detect_test#} const std = @import("std"); -const builtin = @import("builtin"); +const builtin = std.builtin; const assert = std.debug.assert; test "builtin.is_test" { @@ -9681,7 +9680,8 @@ WebAssembly.instantiate(typedArray, {
$ node test.js
 The result is 3
{#header_open|WASI#} -

Zig's support for WebAssembly System Interface (WASI) is under active development. Example of using the standard library and reading command line arguments:

+

Zig's support for WebAssembly System Interface (WASI) is under active development. + Example of using the standard library and reading command line arguments:

{#code_begin|exe|wasi#} {#target_wasi#} const std = @import("std"); diff --git a/lib/std/build.zig b/lib/std/build.zig index 29837d56d9..ecf3930551 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -1,5 +1,5 @@ const std = @import("std.zig"); -const builtin = @import("builtin"); +const builtin = std.builtin; const io = std.io; const fs = std.fs; const mem = std.mem; @@ -15,6 +15,7 @@ const BufSet = std.BufSet; const BufMap = std.BufMap; const fmt_lib = std.fmt; const File = std.fs.File; +const CrossTarget = std.zig.CrossTarget; pub const FmtStep = @import("build/fmt.zig").FmtStep; pub const TranslateCStep = @import("build/translate_c.zig").TranslateCStep; @@ -521,24 +522,91 @@ pub const Builder = struct { return mode; } - /// Exposes standard `zig build` options for choosing a target. Pass `null` to support all targets. - pub fn standardTargetOptions(self: *Builder, supported_targets: ?[]const Target) Target { - if (supported_targets) |target_list| { - // TODO detect multiple args and emit an error message - // there's probably a better way to collect the target - for (target_list) |targ| { - const targ_str = targ.zigTriple(self.allocator) catch unreachable; - const targ_desc = targ.allocDescription(self.allocator) catch unreachable; - const this_targ_opt = self.option(bool, targ_str, targ_desc) orelse false; - if (this_targ_opt) { - return targ; + pub const StandardTargetOptionsArgs = struct { + whitelist: ?[]const CrossTarget = null, + + default_target: CrossTarget = CrossTarget{}, + }; + + /// Exposes standard `zig build` options for choosing a target. + pub fn standardTargetOptions(self: *Builder, args: StandardTargetOptionsArgs) CrossTarget { + const triple = self.option( + []const u8, + "target", + "The CPU architecture, OS, and ABI to build for.", + ) orelse return args.default_target; + + // TODO add cpu and features as part of the target triple + + var diags: CrossTarget.ParseOptions.Diagnostics = .{}; + const selected_target = CrossTarget.parse(.{ + .arch_os_abi = triple, + .diagnostics = &diags, + }) catch |err| switch (err) { + error.UnknownCpuModel => { + std.debug.warn("Unknown CPU: '{}'\nAvailable CPUs for architecture '{}':\n", .{ + diags.cpu_name.?, + @tagName(diags.arch.?), + }); + for (diags.arch.?.allCpuModels()) |cpu| { + std.debug.warn(" {}\n", .{cpu.name}); + } + process.exit(1); + }, + error.UnknownCpuFeature => { + std.debug.warn( + \\Unknown CPU feature: '{}' + \\Available CPU features for architecture '{}': + \\ + , .{ + diags.unknown_feature_name, + @tagName(diags.arch.?), + }); + for (diags.arch.?.allFeaturesList()) |feature| { + std.debug.warn(" {}: {}\n", .{ feature.name, feature.description }); + } + process.exit(1); + }, + error.UnknownOperatingSystem => { + std.debug.warn( + \\Unknown OS: '{}' + \\Available operating systems: + \\ + , .{diags.os_name}); + inline for (std.meta.fields(std.Target.Os.Tag)) |field| { + std.debug.warn(" {}\n", .{field.name}); + } + process.exit(1); + }, + else => |e| { + std.debug.warn("Unable to parse target '{}': {}\n", .{ triple, @errorName(e) }); + process.exit(1); + }, + }; + + const selected_canonicalized_triple = selected_target.zigTriple(self.allocator) catch unreachable; + + if (args.whitelist) |list| whitelist_check: { + // Make sure it's a match of one of the list. + for (list) |t| { + const t_triple = t.zigTriple(self.allocator) catch unreachable; + if (mem.eql(u8, t_triple, selected_canonicalized_triple)) { + break :whitelist_check; } } - return Target.Native; - } else { - const target_str = self.option([]const u8, "target", "the target to build for") orelse return Target.Native; - return Target.parse(.{ .arch_os_abi = target_str }) catch unreachable; // TODO better error message for bad target + std.debug.warn("Chosen target '{}' does not match one of the supported targets:\n", .{ + selected_canonicalized_triple, + }); + for (list) |t| { + const t_triple = t.zigTriple(self.allocator) catch unreachable; + std.debug.warn(" {}\n", .{t_triple}); + } + // TODO instead of process exit, return error and have a zig build flag implemented by + // the build runner that turns process exits into error return traces + process.exit(1); } + + return selected_target; } pub fn addUserInputOption(self: *Builder, name: []const u8, value: []const u8) !bool { @@ -796,7 +864,7 @@ pub const Builder = struct { pub fn findProgram(self: *Builder, names: []const []const u8, paths: []const []const u8) ![]const u8 { // TODO report error for ambiguous situations - const exe_extension = (Target{ .Native = {} }).exeFileExt(); + const exe_extension = @as(CrossTarget, .{}).exeFileExt(); for (self.search_prefixes.toSliceConst()) |search_prefix| { for (names) |name| { if (fs.path.isAbsolute(name)) { @@ -971,21 +1039,19 @@ pub const Builder = struct { }; test "builder.findProgram compiles" { - // TODO: uncomment and fix the leak - // const builder = try Builder.create(std.testing.allocator, "zig", "zig-cache", "zig-cache"); - const builder = try Builder.create(std.heap.page_allocator, "zig", "zig-cache", "zig-cache"); + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + + const builder = try Builder.create(&arena.allocator, "zig", "zig-cache", "zig-cache"); defer builder.destroy(); _ = builder.findProgram(&[_][]const u8{}, &[_][]const u8{}) catch null; } -/// Deprecated. Use `builtin.Version`. +/// Deprecated. Use `std.builtin.Version`. pub const Version = builtin.Version; -/// Deprecated. Use `std.Target.Cross`. -pub const CrossTarget = std.Target.Cross; - -/// Deprecated. Use `std.Target`. -pub const Target = std.Target; +/// Deprecated. Use `std.zig.CrossTarget`. +pub const Target = std.zig.CrossTarget; pub const Pkg = struct { name: []const u8, @@ -1038,7 +1104,7 @@ pub const LibExeObjStep = struct { step: Step, builder: *Builder, name: []const u8, - target: Target, + target: CrossTarget = CrossTarget{}, linker_script: ?[]const u8 = null, version_script: ?[]const u8 = null, out_filename: []const u8, @@ -1076,7 +1142,7 @@ pub const LibExeObjStep = struct { out_pdb_filename: []const u8, packages: ArrayList(Pkg), build_options_contents: std.Buffer, - system_linker_hack: bool, + system_linker_hack: bool = false, object_src: []const u8, @@ -1091,7 +1157,6 @@ pub const LibExeObjStep = struct { install_step: ?*InstallArtifactStep, libc_file: ?[]const u8 = null, - target_glibc: ?Version = null, valgrind_support: ?bool = null, @@ -1112,8 +1177,6 @@ pub const LibExeObjStep = struct { /// that contains the path `aarch64-linux-gnu/lib/ld-linux-aarch64.so.1`. glibc_multi_install_dir: ?[]const u8 = null, - dynamic_linker: ?[]const u8 = null, - /// Position Independent Code force_pic: ?bool = null, @@ -1191,7 +1254,6 @@ pub const LibExeObjStep = struct { .kind = kind, .root_src = root_src, .name = name, - .target = Target.Native, .frameworks = BufSet.init(builder.allocator), .step = Step.init(name, builder.allocator, make), .version = ver, @@ -1210,7 +1272,6 @@ pub const LibExeObjStep = struct { .object_src = undefined, .build_options_contents = std.Buffer.initSize(builder.allocator, 0) catch unreachable, .c_std = Builder.CStd.C99, - .system_linker_hack = false, .override_lib_dir = null, .main_pkg_path = null, .exec_cmd_args = null, @@ -1282,36 +1343,11 @@ pub const LibExeObjStep = struct { } } - /// Deprecated. Use `setTheTarget`. - pub fn setTarget( - self: *LibExeObjStep, - target_arch: builtin.Arch, - target_os: builtin.Os, - target_abi: builtin.Abi, - ) void { - return self.setTheTarget(Target{ - .Cross = CrossTarget{ - .arch = target_arch, - .os = target_os, - .abi = target_abi, - .cpu_features = target_arch.getBaselineCpuFeatures(), - }, - }); - } - - pub fn setTheTarget(self: *LibExeObjStep, target: Target) void { + pub fn setTarget(self: *LibExeObjStep, target: CrossTarget) void { self.target = target; self.computeOutFileNames(); } - pub fn setTargetGLibC(self: *LibExeObjStep, major: u32, minor: u32, patch: u32) void { - self.target_glibc = Version{ - .major = major, - .minor = minor, - .patch = patch, - }; - } - pub fn setOutputDir(self: *LibExeObjStep, dir: []const u8) void { self.output_dir = self.builder.dupePath(dir); } @@ -1692,7 +1728,7 @@ pub const LibExeObjStep = struct { .NotFound => return error.VcpkgNotFound, .Found => |root| { const allocator = self.builder.allocator; - const triplet = try Target.vcpkgTriplet(allocator, self.target, linkage); + const triplet = try self.target.vcpkgTriplet(allocator, linkage); defer self.builder.allocator.free(triplet); const include_path = try fs.path.join(allocator, &[_][]const u8{ root, "installed", triplet, "include" }); @@ -1862,10 +1898,10 @@ pub const LibExeObjStep = struct { } switch (self.build_mode) { - builtin.Mode.Debug => {}, - builtin.Mode.ReleaseSafe => zig_args.append("--release-safe") catch unreachable, - builtin.Mode.ReleaseFast => zig_args.append("--release-fast") catch unreachable, - builtin.Mode.ReleaseSmall => zig_args.append("--release-small") catch unreachable, + .Debug => {}, + .ReleaseSafe => zig_args.append("--release-safe") catch unreachable, + .ReleaseFast => zig_args.append("--release-fast") catch unreachable, + .ReleaseSmall => zig_args.append("--release-small") catch unreachable, } try zig_args.append("--cache-dir"); @@ -1905,47 +1941,46 @@ pub const LibExeObjStep = struct { try zig_args.append(@tagName(self.code_model)); } - switch (self.target) { - .Native => {}, - .Cross => |cross| { - try zig_args.append("-target"); - try zig_args.append(self.target.zigTriple(builder.allocator) catch unreachable); + if (!self.target.isNative()) { + try zig_args.append("-target"); + try zig_args.append(try self.target.zigTriple(builder.allocator)); - const all_features = self.target.getArch().allFeaturesList(); - var populated_cpu_features = cross.cpu.model.features; - populated_cpu_features.populateDependencies(all_features); + // TODO this logic can disappear if cpu model + features becomes part of the target triple + const cross = self.target.toTarget(); + const all_features = cross.cpu.arch.allFeaturesList(); + var populated_cpu_features = cross.cpu.model.features; + populated_cpu_features.populateDependencies(all_features); - if (populated_cpu_features.eql(cross.cpu.features)) { - // The CPU name alone is sufficient. - // If it is the baseline CPU, no command line args are required. - if (cross.cpu.model != Target.Cpu.baseline(self.target.getArch()).model) { - try zig_args.append("-mcpu"); - try zig_args.append(cross.cpu.model.name); - } - } else { - var mcpu_buffer = try std.Buffer.init(builder.allocator, "-mcpu="); - try mcpu_buffer.append(cross.cpu.model.name); - - for (all_features) |feature, i_usize| { - const i = @intCast(Target.Cpu.Feature.Set.Index, i_usize); - const in_cpu_set = populated_cpu_features.isEnabled(i); - const in_actual_set = cross.cpu.features.isEnabled(i); - if (in_cpu_set and !in_actual_set) { - try mcpu_buffer.appendByte('-'); - try mcpu_buffer.append(feature.name); - } else if (!in_cpu_set and in_actual_set) { - try mcpu_buffer.appendByte('+'); - try mcpu_buffer.append(feature.name); - } - } - try zig_args.append(mcpu_buffer.toSliceConst()); + if (populated_cpu_features.eql(cross.cpu.features)) { + // The CPU name alone is sufficient. + // If it is the baseline CPU, no command line args are required. + if (cross.cpu.model != std.Target.Cpu.baseline(cross.cpu.arch).model) { + try zig_args.append("-mcpu"); + try zig_args.append(cross.cpu.model.name); } - }, - } + } else { + var mcpu_buffer = try std.Buffer.init(builder.allocator, "-mcpu="); + try mcpu_buffer.append(cross.cpu.model.name); - if (self.target_glibc) |ver| { - try zig_args.append("-target-glibc"); - try zig_args.append(builder.fmt("{}.{}.{}", .{ ver.major, ver.minor, ver.patch })); + for (all_features) |feature, i_usize| { + const i = @intCast(std.Target.Cpu.Feature.Set.Index, i_usize); + const in_cpu_set = populated_cpu_features.isEnabled(i); + const in_actual_set = cross.cpu.features.isEnabled(i); + if (in_cpu_set and !in_actual_set) { + try mcpu_buffer.appendByte('-'); + try mcpu_buffer.append(feature.name); + } else if (!in_cpu_set and in_actual_set) { + try mcpu_buffer.appendByte('+'); + try mcpu_buffer.append(feature.name); + } + } + try zig_args.append(mcpu_buffer.toSliceConst()); + } + + if (self.target.dynamic_linker.get()) |dynamic_linker| { + try zig_args.append("--dynamic-linker"); + try zig_args.append(dynamic_linker); + } } if (self.linker_script) |linker_script| { @@ -1953,11 +1988,6 @@ pub const LibExeObjStep = struct { zig_args.append(builder.pathFromRoot(linker_script)) catch unreachable; } - if (self.dynamic_linker) |dynamic_linker| { - try zig_args.append("--dynamic-linker"); - try zig_args.append(dynamic_linker); - } - if (self.version_script) |version_script| { try zig_args.append("--version-script"); try zig_args.append(builder.pathFromRoot(version_script)); @@ -1975,7 +2005,7 @@ pub const LibExeObjStep = struct { } else switch (self.target.getExternalExecutor()) { .native, .unavailable => {}, .qemu => |bin_name| if (self.enable_qemu) qemu: { - const need_cross_glibc = self.target.isGnu() and self.target.isLinux() and self.is_linking_libc; + const need_cross_glibc = self.target.isGnuLibC() and self.is_linking_libc; const glibc_dir_arg = if (need_cross_glibc) self.glibc_multi_install_dir orelse break :qemu else @@ -2420,10 +2450,7 @@ const VcpkgRootStatus = enum { Found, }; -pub const VcpkgLinkage = enum { - Static, - Dynamic, -}; +pub const VcpkgLinkage = std.builtin.LinkMode; pub const InstallDir = enum { Prefix, diff --git a/lib/std/build/run.zig b/lib/std/build/run.zig index 75809bde03..eacf408ba9 100644 --- a/lib/std/build/run.zig +++ b/lib/std/build/run.zig @@ -82,7 +82,7 @@ pub const RunStep = struct { var key: []const u8 = undefined; var prev_path: ?[]const u8 = undefined; - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { key = "Path"; prev_path = env_map.get(key); if (prev_path == null) { diff --git a/lib/std/build/translate_c.zig b/lib/std/build/translate_c.zig index 3d1bdad677..e9e61b190f 100644 --- a/lib/std/build/translate_c.zig +++ b/lib/std/build/translate_c.zig @@ -7,6 +7,7 @@ const LibExeObjStep = build.LibExeObjStep; const CheckFileStep = build.CheckFileStep; const fs = std.fs; const mem = std.mem; +const CrossTarget = std.zig.CrossTarget; pub const TranslateCStep = struct { step: Step, @@ -14,7 +15,7 @@ pub const TranslateCStep = struct { source: build.FileSource, output_dir: ?[]const u8, out_basename: []const u8, - target: std.Target = .Native, + target: CrossTarget = CrossTarget{}, pub fn create(builder: *Builder, source: build.FileSource) *TranslateCStep { const self = builder.allocator.create(TranslateCStep) catch unreachable; @@ -39,7 +40,7 @@ pub const TranslateCStep = struct { ) catch unreachable; } - pub fn setTarget(self: *TranslateCStep, target: std.Target) void { + pub fn setTarget(self: *TranslateCStep, target: CrossTarget) void { self.target = target; } @@ -63,12 +64,9 @@ pub const TranslateCStep = struct { try argv_list.append("--cache"); try argv_list.append("on"); - switch (self.target) { - .Native => {}, - .Cross => { - try argv_list.append("-target"); - try argv_list.append(try self.target.zigTriple(self.builder.allocator)); - }, + if (!self.target.isNative()) { + try argv_list.append("-target"); + try argv_list.append(try self.target.zigTriple(self.builder.allocator)); } try argv_list.append(self.source.getPath(self.builder)); diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index f57998c567..08fc85871b 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -185,6 +185,7 @@ pub const TypeInfo = union(enum) { child: type, is_allowzero: bool, + /// This field is an optional type. /// The type of the sentinel is the element type of the pointer, which is /// the value of the `child` field in this struct. However there is no way /// to refer to that type here, so we use `var`. @@ -206,6 +207,7 @@ pub const TypeInfo = union(enum) { len: comptime_int, child: type, + /// This field is an optional type. /// The type of the sentinel is the element type of the array, which is /// the value of the `child` field in this struct. However there is no way /// to refer to that type here, so we use `var`. @@ -398,7 +400,60 @@ pub const LinkMode = enum { pub const Version = struct { major: u32, minor: u32, - patch: u32, + patch: u32 = 0, + + pub const Range = struct { + min: Version, + max: Version, + + pub fn includesVersion(self: LinuxVersionRange, ver: Version) bool { + if (self.min.compare(ver) == .gt) return false; + if (self.max.compare(ver) == .lt) return false; + return true; + } + }; + + pub fn order(lhs: Version, rhs: Version) std.math.Order { + if (lhs.major < rhs.major) return .lt; + if (lhs.major > rhs.major) return .gt; + if (lhs.minor < rhs.minor) return .lt; + if (lhs.minor > rhs.minor) return .gt; + if (lhs.patch < rhs.patch) return .lt; + if (lhs.patch > rhs.patch) return .gt; + return .eq; + } + + pub fn parse(text: []const u8) !Version { + var it = std.mem.separate(text, "."); + return Version{ + .major = try std.fmt.parseInt(u32, it.next() orelse return error.InvalidVersion, 10), + .minor = try std.fmt.parseInt(u32, it.next() orelse "0", 10), + .patch = try std.fmt.parseInt(u32, it.next() orelse "0", 10), + }; + } + + pub fn format( + self: Version, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + context: var, + comptime Error: type, + comptime output: fn (@TypeOf(context), []const u8) Error!void, + ) Error!void { + if (fmt.len == 0) { + if (self.patch == 0) { + if (self.minor == 0) { + return std.fmt.format(context, Error, output, "{}", .{self.major}); + } else { + return std.fmt.format(context, Error, output, "{}.{}", .{ self.major, self.minor }); + } + } else { + return std.fmt.format(context, Error, output, "{}.{}.{}", .{ self.major, self.minor, self.patch }); + } + } else { + @compileError("Unknown format string: '" ++ fmt ++ "'"); + } + } }; /// This data structure is used by the Zig language code generation and @@ -476,7 +531,7 @@ pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace) noreturn root.os.panic(msg, error_return_trace); unreachable; } - switch (os) { + switch (os.tag) { .freestanding => { while (true) { @breakpoint(); diff --git a/lib/std/c.zig b/lib/std/c.zig index f6c0e07dbd..48a3039f51 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -1,5 +1,5 @@ -const builtin = @import("builtin"); const std = @import("std"); +const builtin = std.builtin; const page_size = std.mem.page_size; pub const tokenizer = @import("c/tokenizer.zig"); @@ -10,7 +10,7 @@ pub const ast = @import("c/ast.zig"); pub usingnamespace @import("os/bits.zig"); -pub usingnamespace switch (builtin.os) { +pub usingnamespace switch (std.Target.current.os.tag) { .linux => @import("c/linux.zig"), .windows => @import("c/windows.zig"), .macosx, .ios, .tvos, .watchos => @import("c/darwin.zig"), @@ -46,17 +46,16 @@ pub fn versionCheck(glibc_version: builtin.Version) type { return struct { pub const ok = blk: { if (!builtin.link_libc) break :blk false; - switch (builtin.abi) { - .musl, .musleabi, .musleabihf => break :blk true, - .gnu, .gnuabin32, .gnuabi64, .gnueabi, .gnueabihf, .gnux32 => { - const ver = builtin.glibc_version orelse break :blk false; - if (ver.major < glibc_version.major) break :blk false; - if (ver.major > glibc_version.major) break :blk true; - if (ver.minor < glibc_version.minor) break :blk false; - if (ver.minor > glibc_version.minor) break :blk true; - break :blk ver.patch >= glibc_version.patch; - }, - else => break :blk false, + if (std.Target.current.abi.isMusl()) break :blk true; + if (std.Target.current.isGnuLibC()) { + const ver = std.Target.current.os.version_range.linux.glibc; + const order = ver.order(glibc_version); + break :blk switch (order) { + .gt, .eq => true, + .lt => false, + }; + } else { + break :blk false; } }; }; @@ -109,6 +108,7 @@ pub extern "c" fn execve(path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8 pub extern "c" fn dup(fd: fd_t) c_int; pub extern "c" fn dup2(old_fd: fd_t, new_fd: fd_t) c_int; pub extern "c" fn readlink(noalias path: [*:0]const u8, noalias buf: [*]u8, bufsize: usize) isize; +pub extern "c" fn readlinkat(dirfd: fd_t, noalias path: [*:0]const u8, noalias buf: [*]u8, bufsize: usize) isize; pub extern "c" fn realpath(noalias file_name: [*:0]const u8, noalias resolved_name: [*]u8) ?[*:0]u8; pub extern "c" fn sigprocmask(how: c_int, noalias set: ?*const sigset_t, noalias oset: ?*sigset_t) c_int; pub extern "c" fn gettimeofday(noalias tv: ?*timeval, noalias tz: ?*timezone) c_int; @@ -125,6 +125,7 @@ pub extern "c" fn sysctlnametomib(name: [*:0]const u8, mibp: ?*c_int, sizep: ?*u pub extern "c" fn tcgetattr(fd: fd_t, termios_p: *termios) c_int; pub extern "c" fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) c_int; pub extern "c" fn fcntl(fd: fd_t, cmd: c_int, ...) c_int; +pub extern "c" fn uname(buf: *utsname) c_int; pub extern "c" fn gethostname(name: [*]u8, len: usize) c_int; pub extern "c" fn bind(socket: fd_t, address: ?*const sockaddr, address_len: socklen_t) c_int; diff --git a/lib/std/c/linux.zig b/lib/std/c/linux.zig index 0f7abaaaa0..be32536d6f 100644 --- a/lib/std/c/linux.zig +++ b/lib/std/c/linux.zig @@ -94,7 +94,7 @@ pub const pthread_cond_t = extern struct { size: [__SIZEOF_PTHREAD_COND_T]u8 align(@alignOf(usize)) = [_]u8{0} ** __SIZEOF_PTHREAD_COND_T, }; const __SIZEOF_PTHREAD_COND_T = 48; -const __SIZEOF_PTHREAD_MUTEX_T = if (builtin.os == .fuchsia) 40 else switch (builtin.abi) { +const __SIZEOF_PTHREAD_MUTEX_T = if (builtin.os.tag == .fuchsia) 40 else switch (builtin.abi) { .musl, .musleabi, .musleabihf => if (@sizeOf(usize) == 8) 40 else 24, .gnu, .gnuabin32, .gnuabi64, .gnueabi, .gnueabihf, .gnux32 => switch (builtin.arch) { .aarch64 => 48, diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index bb8ed2e8a0..d5e914b286 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -17,9 +17,9 @@ const TailQueue = std.TailQueue; const maxInt = std.math.maxInt; pub const ChildProcess = struct { - pid: if (builtin.os == .windows) void else i32, - handle: if (builtin.os == .windows) windows.HANDLE else void, - thread_handle: if (builtin.os == .windows) windows.HANDLE else void, + pid: if (builtin.os.tag == .windows) void else i32, + handle: if (builtin.os.tag == .windows) windows.HANDLE else void, + thread_handle: if (builtin.os.tag == .windows) windows.HANDLE else void, allocator: *mem.Allocator, @@ -39,15 +39,15 @@ pub const ChildProcess = struct { stderr_behavior: StdIo, /// Set to change the user id when spawning the child process. - uid: if (builtin.os == .windows) void else ?u32, + uid: if (builtin.os.tag == .windows) void else ?u32, /// Set to change the group id when spawning the child process. - gid: if (builtin.os == .windows) void else ?u32, + gid: if (builtin.os.tag == .windows) void else ?u32, /// Set to change the current working directory when spawning the child process. cwd: ?[]const u8, - err_pipe: if (builtin.os == .windows) void else [2]os.fd_t, + err_pipe: if (builtin.os.tag == .windows) void else [2]os.fd_t, expand_arg0: Arg0Expand, @@ -96,8 +96,8 @@ pub const ChildProcess = struct { .term = null, .env_map = null, .cwd = null, - .uid = if (builtin.os == .windows) {} else null, - .gid = if (builtin.os == .windows) {} else null, + .uid = if (builtin.os.tag == .windows) {} else null, + .gid = if (builtin.os.tag == .windows) {} else null, .stdin = null, .stdout = null, .stderr = null, @@ -118,7 +118,7 @@ pub const ChildProcess = struct { /// On success must call `kill` or `wait`. pub fn spawn(self: *ChildProcess) SpawnError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return self.spawnWindows(); } else { return self.spawnPosix(); @@ -132,7 +132,7 @@ pub const ChildProcess = struct { /// Forcibly terminates child process and then cleans up all resources. pub fn kill(self: *ChildProcess) !Term { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return self.killWindows(1); } else { return self.killPosix(); @@ -162,7 +162,7 @@ pub const ChildProcess = struct { /// Blocks until child process terminates and then cleans up all resources. pub fn wait(self: *ChildProcess) !Term { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return self.waitWindows(); } else { return self.waitPosix(); @@ -307,7 +307,7 @@ pub const ChildProcess = struct { fn cleanupAfterWait(self: *ChildProcess, status: u32) !Term { defer destroyPipe(self.err_pipe); - if (builtin.os == .linux) { + if (builtin.os.tag == .linux) { var fd = [1]std.os.pollfd{std.os.pollfd{ .fd = self.err_pipe[0], .events = std.os.POLLIN, @@ -402,7 +402,7 @@ pub const ChildProcess = struct { // This pipe is used to communicate errors between the time of fork // and execve from the child process to the parent process. const err_pipe = blk: { - if (builtin.os == .linux) { + if (builtin.os.tag == .linux) { const fd = try os.eventfd(0, 0); // There's no distinction between the readable and the writeable // end with eventfd diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index 44326bfd97..0cc2c1d3ad 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -120,7 +120,7 @@ fn usage() void { } fn mode(comptime x: comptime_int) comptime_int { - return if (builtin.mode == builtin.Mode.Debug) x / 64 else x; + return if (builtin.mode == .Debug) x / 64 else x; } // TODO(#1358): Replace with builtin formatted padding when available. diff --git a/lib/std/cstr.zig b/lib/std/cstr.zig index 765e0a45cf..9cb16b0ed3 100644 --- a/lib/std/cstr.zig +++ b/lib/std/cstr.zig @@ -4,8 +4,8 @@ const debug = std.debug; const mem = std.mem; const testing = std.testing; -pub const line_sep = switch (builtin.os) { - builtin.Os.windows => "\r\n", +pub const line_sep = switch (builtin.os.tag) { + .windows => "\r\n", else => "\n", }; @@ -28,7 +28,7 @@ test "cstr fns" { fn testCStrFnsImpl() void { testing.expect(cmp("aoeu", "aoez") == -1); - testing.expect(mem.len(u8, "123456789") == 9); + testing.expect(mem.len("123456789") == 9); } /// Returns a mutable, null-terminated slice with the same length as `slice`. diff --git a/lib/std/debug.zig b/lib/std/debug.zig index efe4f1fa76..558b7e0513 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1,4 +1,5 @@ const std = @import("std.zig"); +const builtin = std.builtin; const math = std.math; const mem = std.mem; const io = std.io; @@ -11,7 +12,6 @@ const macho = std.macho; const coff = std.coff; const pdb = std.pdb; const ArrayList = std.ArrayList; -const builtin = @import("builtin"); const root = @import("root"); const maxInt = std.math.maxInt; const File = std.fs.File; @@ -38,6 +38,18 @@ const Module = struct { checksum_offset: ?usize, }; +pub const LineInfo = struct { + line: u64, + column: u64, + file_name: []const u8, + allocator: ?*mem.Allocator, + + fn deinit(self: LineInfo) void { + const allocator = self.allocator orelse return; + allocator.free(self.file_name); + } +}; + /// Tries to write to stderr, unbuffered, and ignores any error returned. /// Does not append a newline. var stderr_file: File = undefined; @@ -89,7 +101,7 @@ pub fn detectTTYConfig() TTY.Config { } else |_| { if (stderr_file.supportsAnsiEscapeCodes()) { return .escape_codes; - } else if (builtin.os == .windows and stderr_file.isTty()) { + } else if (builtin.os.tag == .windows and stderr_file.isTty()) { return .windows_api; } else { return .no_color; @@ -143,7 +155,7 @@ pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void { /// chopping off the irrelevant frames and shifting so that the returned addresses pointer /// equals the passed in addresses pointer. pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace) void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const addrs = stack_trace.instruction_addresses; const u32_addrs_len = @intCast(u32, addrs.len); const first_addr = first_address orelse { @@ -219,7 +231,7 @@ pub fn assert(ok: bool) void { pub fn panic(comptime format: []const u8, args: var) noreturn { @setCold(true); // TODO: remove conditional once wasi / LLVM defines __builtin_return_address - const first_trace_addr = if (builtin.os == .wasi) null else @returnAddress(); + const first_trace_addr = if (builtin.os.tag == .wasi) null else @returnAddress(); panicExtra(null, first_trace_addr, format, args); } @@ -349,7 +361,7 @@ pub fn writeCurrentStackTrace( tty_config: TTY.Config, start_addr: ?usize, ) !void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return writeCurrentStackTraceWindows(out_stream, debug_info, tty_config, start_addr); } var it = StackIterator.init(start_addr, null); @@ -378,175 +390,6 @@ pub fn writeCurrentStackTraceWindows( } } -/// TODO once https://github.com/ziglang/zig/issues/3157 is fully implemented, -/// make this `noasync fn` and remove the individual noasync calls. -pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: var, address: usize, tty_config: TTY.Config) !void { - if (builtin.os == .windows) { - return noasync printSourceAtAddressWindows(debug_info, out_stream, address, tty_config); - } - if (comptime std.Target.current.isDarwin()) { - return noasync printSourceAtAddressMacOs(debug_info, out_stream, address, tty_config); - } - return noasync printSourceAtAddressPosix(debug_info, out_stream, address, tty_config); -} - -/// TODO resources https://github.com/ziglang/zig/issues/4353 -fn printSourceAtAddressWindows( - di: *DebugInfo, - out_stream: var, - relocated_address: usize, - tty_config: TTY.Config, -) !void { - const allocator = getDebugInfoAllocator(); - const base_address = process.getBaseAddress(); - const relative_address = relocated_address - base_address; - - var coff_section: *coff.Section = undefined; - const mod_index = for (di.sect_contribs) |sect_contrib| { - if (sect_contrib.Section > di.coff.sections.len) continue; - // Remember that SectionContribEntry.Section is 1-based. - coff_section = &di.coff.sections.toSlice()[sect_contrib.Section - 1]; - - const vaddr_start = coff_section.header.virtual_address + sect_contrib.Offset; - const vaddr_end = vaddr_start + sect_contrib.Size; - if (relative_address >= vaddr_start and relative_address < vaddr_end) { - break sect_contrib.ModuleIndex; - } - } else { - // we have no information to add to the address - return printLineInfo(out_stream, null, relocated_address, "???", "???", tty_config, printLineFromFileAnyOs); - }; - - const mod = &di.modules[mod_index]; - try populateModule(di, mod); - const obj_basename = fs.path.basename(mod.obj_file_name); - - var symbol_i: usize = 0; - const symbol_name = if (!mod.populated) "???" else while (symbol_i != mod.symbols.len) { - const prefix = @ptrCast(*pdb.RecordPrefix, &mod.symbols[symbol_i]); - if (prefix.RecordLen < 2) - return error.InvalidDebugInfo; - switch (prefix.RecordKind) { - .S_LPROC32, .S_GPROC32 => { - const proc_sym = @ptrCast(*pdb.ProcSym, &mod.symbols[symbol_i + @sizeOf(pdb.RecordPrefix)]); - const vaddr_start = coff_section.header.virtual_address + proc_sym.CodeOffset; - const vaddr_end = vaddr_start + proc_sym.CodeSize; - if (relative_address >= vaddr_start and relative_address < vaddr_end) { - break mem.toSliceConst(u8, @ptrCast([*:0]u8, proc_sym) + @sizeOf(pdb.ProcSym)); - } - }, - else => {}, - } - symbol_i += prefix.RecordLen + @sizeOf(u16); - if (symbol_i > mod.symbols.len) - return error.InvalidDebugInfo; - } else "???"; - - const subsect_info = mod.subsect_info; - - var sect_offset: usize = 0; - var skip_len: usize = undefined; - const opt_line_info = subsections: { - const checksum_offset = mod.checksum_offset orelse break :subsections null; - while (sect_offset != subsect_info.len) : (sect_offset += skip_len) { - const subsect_hdr = @ptrCast(*pdb.DebugSubsectionHeader, &subsect_info[sect_offset]); - skip_len = subsect_hdr.Length; - sect_offset += @sizeOf(pdb.DebugSubsectionHeader); - - switch (subsect_hdr.Kind) { - pdb.DebugSubsectionKind.Lines => { - var line_index = sect_offset; - - const line_hdr = @ptrCast(*pdb.LineFragmentHeader, &subsect_info[line_index]); - if (line_hdr.RelocSegment == 0) return error.MissingDebugInfo; - line_index += @sizeOf(pdb.LineFragmentHeader); - const frag_vaddr_start = coff_section.header.virtual_address + line_hdr.RelocOffset; - const frag_vaddr_end = frag_vaddr_start + line_hdr.CodeSize; - - if (relative_address >= frag_vaddr_start and relative_address < frag_vaddr_end) { - // There is an unknown number of LineBlockFragmentHeaders (and their accompanying line and column records) - // from now on. We will iterate through them, and eventually find a LineInfo that we're interested in, - // breaking out to :subsections. If not, we will make sure to not read anything outside of this subsection. - const subsection_end_index = sect_offset + subsect_hdr.Length; - - while (line_index < subsection_end_index) { - const block_hdr = @ptrCast(*pdb.LineBlockFragmentHeader, &subsect_info[line_index]); - line_index += @sizeOf(pdb.LineBlockFragmentHeader); - const start_line_index = line_index; - - const has_column = line_hdr.Flags.LF_HaveColumns; - - // All line entries are stored inside their line block by ascending start address. - // Heuristic: we want to find the last line entry that has a vaddr_start <= relative_address. - // This is done with a simple linear search. - var line_i: u32 = 0; - while (line_i < block_hdr.NumLines) : (line_i += 1) { - const line_num_entry = @ptrCast(*pdb.LineNumberEntry, &subsect_info[line_index]); - line_index += @sizeOf(pdb.LineNumberEntry); - - const vaddr_start = frag_vaddr_start + line_num_entry.Offset; - if (relative_address < vaddr_start) { - break; - } - } - - // line_i == 0 would mean that no matching LineNumberEntry was found. - if (line_i > 0) { - const subsect_index = checksum_offset + block_hdr.NameIndex; - const chksum_hdr = @ptrCast(*pdb.FileChecksumEntryHeader, &mod.subsect_info[subsect_index]); - const strtab_offset = @sizeOf(pdb.PDBStringTableHeader) + chksum_hdr.FileNameOffset; - try di.pdb.string_table.seekTo(strtab_offset); - const source_file_name = try di.pdb.string_table.readNullTermString(allocator); - - const line_entry_idx = line_i - 1; - - const column = if (has_column) blk: { - const start_col_index = start_line_index + @sizeOf(pdb.LineNumberEntry) * block_hdr.NumLines; - const col_index = start_col_index + @sizeOf(pdb.ColumnNumberEntry) * line_entry_idx; - const col_num_entry = @ptrCast(*pdb.ColumnNumberEntry, &subsect_info[col_index]); - break :blk col_num_entry.StartColumn; - } else 0; - - const found_line_index = start_line_index + line_entry_idx * @sizeOf(pdb.LineNumberEntry); - const line_num_entry = @ptrCast(*pdb.LineNumberEntry, &subsect_info[found_line_index]); - const flags = @ptrCast(*pdb.LineNumberEntry.Flags, &line_num_entry.Flags); - - break :subsections LineInfo{ - .allocator = allocator, - .file_name = source_file_name, - .line = flags.Start, - .column = column, - }; - } - } - - // Checking that we are not reading garbage after the (possibly) multiple block fragments. - if (line_index != subsection_end_index) { - return error.InvalidDebugInfo; - } - } - }, - else => {}, - } - - if (sect_offset > subsect_info.len) - return error.InvalidDebugInfo; - } else { - break :subsections null; - } - }; - - try printLineInfo( - out_stream, - opt_line_info, - relocated_address, - symbol_name, - obj_basename, - tty_config, - printLineFromFileAnyOs, - ); -} - pub const TTY = struct { pub const Color = enum { Red, @@ -575,7 +418,7 @@ pub const TTY = struct { .Dim => noasync out_stream.write(DIM) catch return, .Reset => noasync out_stream.write(RESET) catch return, }, - .windows_api => if (builtin.os == .windows) { + .windows_api => if (builtin.os.tag == .windows) { const S = struct { var attrs: windows.WORD = undefined; var init_attrs = false; @@ -618,7 +461,7 @@ pub const TTY = struct { }; /// TODO resources https://github.com/ziglang/zig/issues/4353 -fn populateModule(di: *DebugInfo, mod: *Module) !void { +fn populateModule(di: *ModuleDebugInfo, mod: *Module) !void { if (mod.populated) return; const allocator = getDebugInfoAllocator(); @@ -650,7 +493,7 @@ fn populateModule(di: *DebugInfo, mod: *Module) !void { sect_offset += @sizeOf(pdb.DebugSubsectionHeader); switch (subsect_hdr.Kind) { - pdb.DebugSubsectionKind.FileChecksums => { + .FileChecksums => { mod.checksum_offset = sect_offset; break; }, @@ -682,41 +525,37 @@ fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const Mach return null; } -fn printSourceAtAddressMacOs(di: *DebugInfo, out_stream: var, address: usize, tty_config: TTY.Config) !void { - const base_addr = process.getBaseAddress(); - const adjusted_addr = 0x100000000 + (address - base_addr); - - const symbol = machoSearchSymbols(di.symbols, adjusted_addr) orelse { - return printLineInfo(out_stream, null, address, "???", "???", tty_config, printLineFromFileAnyOs); - }; - - const symbol_name = mem.toSliceConst(u8, @ptrCast([*:0]const u8, di.strings.ptr + symbol.nlist.n_strx)); - const compile_unit_name = if (symbol.ofile) |ofile| blk: { - const ofile_path = mem.toSliceConst(u8, @ptrCast([*:0]const u8, di.strings.ptr + ofile.n_strx)); - break :blk fs.path.basename(ofile_path); - } else "???"; - - const line_info = getLineNumberInfoMacOs(di, symbol.*, adjusted_addr) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => null, +/// TODO resources https://github.com/ziglang/zig/issues/4353 +pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: var, address: usize, tty_config: TTY.Config) !void { + const module = debug_info.getModuleForAddress(address) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => { + return printLineInfo( + out_stream, + null, + address, + "???", + "???", + tty_config, + printLineFromFileAnyOs, + ); + }, else => return err, }; - defer if (line_info) |li| li.deinit(); - try printLineInfo( + const symbol_info = try module.getSymbolAtAddress(address); + defer symbol_info.deinit(); + + return printLineInfo( out_stream, - line_info, + symbol_info.line_info, address, - symbol_name, - compile_unit_name, + symbol_info.symbol_name, + symbol_info.compile_unit_name, tty_config, printLineFromFileAnyOs, ); } -pub fn printSourceAtAddressPosix(debug_info: *DebugInfo, out_stream: var, address: usize, tty_config: TTY.Config) !void { - return debug_info.printSourceAtAddress(out_stream, address, tty_config, printLineFromFileAnyOs); -} - fn printLineInfo( out_stream: var, line_info: ?LineInfo, @@ -772,29 +611,32 @@ pub const OpenSelfDebugInfoError = error{ /// TODO resources https://github.com/ziglang/zig/issues/4353 /// TODO once https://github.com/ziglang/zig/issues/3157 is fully implemented, /// make this `noasync fn` and remove the individual noasync calls. -pub fn openSelfDebugInfo(allocator: *mem.Allocator) !DebugInfo { +pub fn openSelfDebugInfo(allocator: *mem.Allocator) anyerror!DebugInfo { if (builtin.strip_debug_info) return error.MissingDebugInfo; if (@hasDecl(root, "os") and @hasDecl(root.os, "debug") and @hasDecl(root.os.debug, "openSelfDebugInfo")) { return noasync root.os.debug.openSelfDebugInfo(allocator); } - if (builtin.os == .windows) { - return noasync openSelfDebugInfoWindows(allocator); + switch (builtin.os.tag) { + .linux, + .freebsd, + .macosx, + .windows, + => return DebugInfo.init(allocator), + else => @compileError("openSelfDebugInfo unsupported for this platform"), } - if (comptime std.Target.current.isDarwin()) { - return noasync openSelfDebugInfoMacOs(allocator); - } - return noasync openSelfDebugInfoPosix(allocator); } -fn openSelfDebugInfoWindows(allocator: *mem.Allocator) !DebugInfo { - const self_file = try fs.openSelfExe(); - defer self_file.close(); +/// TODO resources https://github.com/ziglang/zig/issues/4353 +fn openCoffDebugInfo(allocator: *mem.Allocator, coff_file_path: [:0]const u16) !ModuleDebugInfo { + const coff_file = try std.fs.openFileAbsoluteW(coff_file_path.ptr, .{}); + errdefer coff_file.close(); const coff_obj = try allocator.create(coff.Coff); - coff_obj.* = coff.Coff.init(allocator, self_file); + coff_obj.* = coff.Coff.init(allocator, coff_file); - var di = DebugInfo{ + var di = ModuleDebugInfo{ + .base_address = undefined, .coff = coff_obj, .pdb = undefined, .sect_contribs = undefined, @@ -958,109 +800,85 @@ fn readSparseBitVector(stream: var, allocator: *mem.Allocator) ![]usize { return list.toOwnedSlice(); } -fn findDwarfSectionFromElf(elf_file: *elf.Elf, name: []const u8) !?DwarfInfo.Section { - const elf_header = (try elf_file.findSection(name)) orelse return null; - return DwarfInfo.Section{ - .offset = elf_header.sh_offset, - .size = elf_header.sh_size, - }; -} - -/// Initialize DWARF info. The caller has the responsibility to initialize most -/// the DwarfInfo fields before calling. These fields can be left undefined: -/// * abbrev_table_list -/// * compile_unit_list -pub fn openDwarfDebugInfo(di: *DwarfInfo, allocator: *mem.Allocator) !void { - di.abbrev_table_list = ArrayList(AbbrevTableHeader).init(allocator); - di.compile_unit_list = ArrayList(CompileUnit).init(allocator); - di.func_list = ArrayList(Func).init(allocator); - try di.scanAllFunctions(); - try di.scanAllCompileUnits(); +fn chopSlice(ptr: []const u8, offset: u64, size: u64) ![]const u8 { + const start = try math.cast(usize, offset); + const end = start + try math.cast(usize, size); + return ptr[start..end]; } /// TODO resources https://github.com/ziglang/zig/issues/4353 -pub fn openElfDebugInfo( - allocator: *mem.Allocator, - data: []u8, -) !DwarfInfo { - var seekable_stream = io.SliceSeekableInStream.init(data); - var efile = try elf.Elf.openStream( +pub fn openElfDebugInfo(allocator: *mem.Allocator, elf_file_path: []const u8) !ModuleDebugInfo { + const mapped_mem = try mapWholeFile(elf_file_path); + + var seekable_stream = io.SliceSeekableInStream.init(mapped_mem); + var efile = try noasync elf.Elf.openStream( allocator, - @ptrCast(*DwarfSeekableStream, &seekable_stream.seekable_stream), - @ptrCast(*DwarfInStream, &seekable_stream.stream), + @ptrCast(*DW.DwarfSeekableStream, &seekable_stream.seekable_stream), + @ptrCast(*DW.DwarfInStream, &seekable_stream.stream), ); - defer efile.close(); + defer noasync efile.close(); - const debug_info = (try efile.findSection(".debug_info")) orelse + const debug_info = (try noasync efile.findSection(".debug_info")) orelse return error.MissingDebugInfo; - const debug_abbrev = (try efile.findSection(".debug_abbrev")) orelse + const debug_abbrev = (try noasync efile.findSection(".debug_abbrev")) orelse return error.MissingDebugInfo; - const debug_str = (try efile.findSection(".debug_str")) orelse + const debug_str = (try noasync efile.findSection(".debug_str")) orelse return error.MissingDebugInfo; - const debug_line = (try efile.findSection(".debug_line")) orelse + const debug_line = (try noasync efile.findSection(".debug_line")) orelse return error.MissingDebugInfo; - const opt_debug_ranges = try efile.findSection(".debug_ranges"); + const opt_debug_ranges = try noasync efile.findSection(".debug_ranges"); - var di = DwarfInfo{ + var di = DW.DwarfInfo{ .endian = efile.endian, - .debug_info = (data[@intCast(usize, debug_info.sh_offset)..@intCast(usize, debug_info.sh_offset + debug_info.sh_size)]), - .debug_abbrev = (data[@intCast(usize, debug_abbrev.sh_offset)..@intCast(usize, debug_abbrev.sh_offset + debug_abbrev.sh_size)]), - .debug_str = (data[@intCast(usize, debug_str.sh_offset)..@intCast(usize, debug_str.sh_offset + debug_str.sh_size)]), - .debug_line = (data[@intCast(usize, debug_line.sh_offset)..@intCast(usize, debug_line.sh_offset + debug_line.sh_size)]), + .debug_info = try chopSlice(mapped_mem, debug_info.sh_offset, debug_info.sh_size), + .debug_abbrev = try chopSlice(mapped_mem, debug_abbrev.sh_offset, debug_abbrev.sh_size), + .debug_str = try chopSlice(mapped_mem, debug_str.sh_offset, debug_str.sh_size), + .debug_line = try chopSlice(mapped_mem, debug_line.sh_offset, debug_line.sh_size), .debug_ranges = if (opt_debug_ranges) |debug_ranges| - data[@intCast(usize, debug_ranges.sh_offset)..@intCast(usize, debug_ranges.sh_offset + debug_ranges.sh_size)] + try chopSlice(mapped_mem, debug_ranges.sh_offset, debug_ranges.sh_size) else null, }; - try openDwarfDebugInfo(&di, allocator); - return di; + try noasync DW.openDwarfDebugInfo(&di, allocator); + + return ModuleDebugInfo{ + .base_address = undefined, + .dwarf = di, + .mapped_memory = mapped_mem, + }; } /// TODO resources https://github.com/ziglang/zig/issues/4353 -fn openSelfDebugInfoPosix(allocator: *mem.Allocator) !DwarfInfo { - var exe_file = try fs.openSelfExe(); - errdefer exe_file.close(); +fn openMachODebugInfo(allocator: *mem.Allocator, macho_file_path: []const u8) !ModuleDebugInfo { + const mapped_mem = try mapWholeFile(macho_file_path); - const exe_len = math.cast(usize, try exe_file.getEndPos()) catch - return error.DebugInfoTooLarge; - const exe_mmap = try os.mmap( - null, - exe_len, - os.PROT_READ, - os.MAP_SHARED, - exe_file.handle, - 0, + const hdr = @ptrCast( + *const macho.mach_header_64, + @alignCast(@alignOf(macho.mach_header_64), mapped_mem.ptr), ); - errdefer os.munmap(exe_mmap); + if (hdr.magic != macho.MH_MAGIC_64) + return error.InvalidDebugInfo; - return openElfDebugInfo(allocator, exe_mmap); -} - -/// TODO resources https://github.com/ziglang/zig/issues/4353 -fn openSelfDebugInfoMacOs(allocator: *mem.Allocator) !DebugInfo { - const hdr = &std.c._mh_execute_header; - assert(hdr.magic == std.macho.MH_MAGIC_64); - - const hdr_base = @ptrCast([*]u8, hdr); + const hdr_base = @ptrCast([*]const u8, hdr); var ptr = hdr_base + @sizeOf(macho.mach_header_64); var ncmd: u32 = hdr.ncmds; const symtab = while (ncmd != 0) : (ncmd -= 1) { - const lc = @ptrCast(*std.macho.load_command, ptr); + const lc = @ptrCast(*const std.macho.load_command, ptr); switch (lc.cmd) { - std.macho.LC_SYMTAB => break @ptrCast(*std.macho.symtab_command, ptr), + std.macho.LC_SYMTAB => break @ptrCast(*const std.macho.symtab_command, ptr), else => {}, } ptr = @alignCast(@alignOf(std.macho.load_command), ptr + lc.cmdsize); } else { return error.MissingDebugInfo; }; - const syms = @ptrCast([*]macho.nlist_64, @alignCast(@alignOf(macho.nlist_64), hdr_base + symtab.symoff))[0..symtab.nsyms]; - const strings = @ptrCast([*]u8, hdr_base + symtab.stroff)[0..symtab.strsize]; + const syms = @ptrCast([*]const macho.nlist_64, @alignCast(@alignOf(macho.nlist_64), hdr_base + symtab.symoff))[0..symtab.nsyms]; + const strings = @ptrCast([*]const u8, hdr_base + symtab.stroff)[0..symtab.strsize :0]; const symbols_buf = try allocator.alloc(MachoSymbol, syms.len); - var ofile: ?*macho.nlist_64 = null; + var ofile: ?*const macho.nlist_64 = null; var reloc: u64 = 0; var symbol_index: usize = 0; var last_len: u64 = 0; @@ -1108,8 +926,10 @@ fn openSelfDebugInfoMacOs(allocator: *mem.Allocator) !DebugInfo { // This sort is so that we can binary search later. std.sort.sort(MachoSymbol, symbols, MachoSymbol.addressLessThan); - return DebugInfo{ - .ofiles = DebugInfo.OFileTable.init(allocator), + return ModuleDebugInfo{ + .base_address = undefined, + .mapped_memory = mapped_mem, + .ofiles = ModuleDebugInfo.OFileTable.init(allocator), .symbols = symbols, .strings = strings, }; @@ -1148,8 +968,8 @@ fn printLineFromFileAnyOs(out_stream: var, line_info: LineInfo) !void { } const MachoSymbol = struct { - nlist: *macho.nlist_64, - ofile: ?*macho.nlist_64, + nlist: *const macho.nlist_64, + ofile: ?*const macho.nlist_64, reloc: u64, /// Returns the address from the macho file @@ -1162,1057 +982,614 @@ const MachoSymbol = struct { } }; -pub const DwarfSeekableStream = io.SeekableStream(anyerror, anyerror); -pub const DwarfInStream = io.InStream(anyerror); +fn mapWholeFile(path: []const u8) ![]const u8 { + const file = try noasync fs.openFileAbsolute(path, .{ .always_blocking = true }); + defer noasync file.close(); -pub const DwarfInfo = struct { - endian: builtin.Endian, - // No memory is owned by the DwarfInfo - debug_info: []u8, - debug_abbrev: []u8, - debug_str: []u8, - debug_line: []u8, - debug_ranges: ?[]u8, - // Filled later by the initializer - abbrev_table_list: ArrayList(AbbrevTableHeader) = undefined, - compile_unit_list: ArrayList(CompileUnit) = undefined, - func_list: ArrayList(Func) = undefined, + const file_len = try math.cast(usize, try file.getEndPos()); + const mapped_mem = try os.mmap( + null, + file_len, + os.PROT_READ, + os.MAP_SHARED, + file.handle, + 0, + ); + errdefer os.munmap(mapped_mem); - pub fn allocator(self: DwarfInfo) *mem.Allocator { - return self.abbrev_table_list.allocator; - } + return mapped_mem; +} - /// This function works in freestanding mode. - /// fn printLineFromFile(out_stream: var, line_info: LineInfo) !void - pub fn printSourceAtAddress( - self: *DwarfInfo, - out_stream: var, - address: usize, - tty_config: TTY.Config, - comptime printLineFromFile: var, - ) !void { - const compile_unit = self.findCompileUnit(address) catch { - return printLineInfo(out_stream, null, address, "???", "???", tty_config, printLineFromFile); +pub const DebugInfo = struct { + allocator: *mem.Allocator, + address_map: std.AutoHashMap(usize, *ModuleDebugInfo), + + pub fn init(allocator: *mem.Allocator) DebugInfo { + return DebugInfo{ + .allocator = allocator, + .address_map = std.AutoHashMap(usize, *ModuleDebugInfo).init(allocator), }; - - const compile_unit_name = try compile_unit.die.getAttrString(self, DW.AT_name); - const symbol_name = self.getSymbolName(address) orelse "???"; - const line_info = self.getLineNumberInfo(compile_unit.*, address) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => null, - else => return err, - }; - defer if (line_info) |li| li.deinit(); - - try printLineInfo( - out_stream, - line_info, - address, - symbol_name, - compile_unit_name, - tty_config, - printLineFromFile, - ); } - fn getSymbolName(di: *DwarfInfo, address: u64) ?[]const u8 { - for (di.func_list.toSliceConst()) |*func| { - if (func.pc_range) |range| { - if (address >= range.start and address < range.end) { - return func.name; - } - } - } - - return null; + pub fn deinit(self: *DebugInfo) void { + // TODO: resources https://github.com/ziglang/zig/issues/4353 + self.address_map.deinit(); } - fn scanAllFunctions(di: *DwarfInfo) !void { - var s = io.SliceSeekableInStream.init(di.debug_info); - var this_unit_offset: u64 = 0; - - while (true) { - s.seekable_stream.seekTo(this_unit_offset) catch |err| switch (err) { - error.EndOfStream => return, - else => return err, - }; - - var is_64: bool = undefined; - const unit_length = try readInitialLength(@TypeOf(s.stream.readFn).ReturnType.ErrorSet, &s.stream, &is_64); - if (unit_length == 0) return; - const next_offset = unit_length + (if (is_64) @as(usize, 12) else @as(usize, 4)); - - const version = try s.stream.readInt(u16, di.endian); - if (version < 2 or version > 5) return error.InvalidDebugInfo; - - const debug_abbrev_offset = if (is_64) try s.stream.readInt(u64, di.endian) else try s.stream.readInt(u32, di.endian); - - const address_size = try s.stream.readByte(); - if (address_size != @sizeOf(usize)) return error.InvalidDebugInfo; - - const compile_unit_pos = try s.seekable_stream.getPos(); - const abbrev_table = try di.getAbbrevTable(debug_abbrev_offset); - - try s.seekable_stream.seekTo(compile_unit_pos); - - const next_unit_pos = this_unit_offset + next_offset; - - while ((try s.seekable_stream.getPos()) < next_unit_pos) { - const die_obj = (try di.parseDie(&s.stream, abbrev_table, is_64)) orelse continue; - const after_die_offset = try s.seekable_stream.getPos(); - - switch (die_obj.tag_id) { - DW.TAG_subprogram, DW.TAG_inlined_subroutine, DW.TAG_subroutine, DW.TAG_entry_point => { - const fn_name = x: { - var depth: i32 = 3; - var this_die_obj = die_obj; - // Prenvent endless loops - while (depth > 0) : (depth -= 1) { - if (this_die_obj.getAttr(DW.AT_name)) |_| { - const name = try this_die_obj.getAttrString(di, DW.AT_name); - break :x name; - } else if (this_die_obj.getAttr(DW.AT_abstract_origin)) |ref| { - // Follow the DIE it points to and repeat - const ref_offset = try this_die_obj.getAttrRef(DW.AT_abstract_origin); - if (ref_offset > next_offset) return error.InvalidDebugInfo; - try s.seekable_stream.seekTo(this_unit_offset + ref_offset); - this_die_obj = (try di.parseDie(&s.stream, abbrev_table, is_64)) orelse return error.InvalidDebugInfo; - } else if (this_die_obj.getAttr(DW.AT_specification)) |ref| { - // Follow the DIE it points to and repeat - const ref_offset = try this_die_obj.getAttrRef(DW.AT_specification); - if (ref_offset > next_offset) return error.InvalidDebugInfo; - try s.seekable_stream.seekTo(this_unit_offset + ref_offset); - this_die_obj = (try di.parseDie(&s.stream, abbrev_table, is_64)) orelse return error.InvalidDebugInfo; - } else { - break :x null; - } - } - - break :x null; - }; - - const pc_range = x: { - if (die_obj.getAttrAddr(DW.AT_low_pc)) |low_pc| { - if (die_obj.getAttr(DW.AT_high_pc)) |high_pc_value| { - const pc_end = switch (high_pc_value.*) { - FormValue.Address => |value| value, - FormValue.Const => |value| b: { - const offset = try value.asUnsignedLe(); - break :b (low_pc + offset); - }, - else => return error.InvalidDebugInfo, - }; - break :x PcRange{ - .start = low_pc, - .end = pc_end, - }; - } else { - break :x null; - } - } else |err| { - if (err != error.MissingDebugInfo) return err; - break :x null; - } - }; - - try di.func_list.append(Func{ - .name = fn_name, - .pc_range = pc_range, - }); - }, - else => {}, - } - - try s.seekable_stream.seekTo(after_die_offset); - } - - this_unit_offset += next_offset; - } + pub fn getModuleForAddress(self: *DebugInfo, address: usize) !*ModuleDebugInfo { + if (comptime std.Target.current.isDarwin()) + return self.lookupModuleDyld(address) + else if (builtin.os.tag == .windows) + return self.lookupModuleWin32(address) + else + return self.lookupModuleDl(address); } - fn scanAllCompileUnits(di: *DwarfInfo) !void { - var s = io.SliceSeekableInStream.init(di.debug_info); - var this_unit_offset: u64 = 0; + fn lookupModuleDyld(self: *DebugInfo, address: usize) !*ModuleDebugInfo { + const image_count = std.c._dyld_image_count(); - while (true) { - s.seekable_stream.seekTo(this_unit_offset) catch |err| switch (err) { - error.EndOfStream => return, - else => return err, - }; + var i: u32 = 0; + while (i < image_count) : (i += 1) { + const base_address = std.c._dyld_get_image_vmaddr_slide(i); - var is_64: bool = undefined; - const unit_length = try readInitialLength(@TypeOf(s.stream.readFn).ReturnType.ErrorSet, &s.stream, &is_64); - if (unit_length == 0) return; - const next_offset = unit_length + (if (is_64) @as(usize, 12) else @as(usize, 4)); + if (address < base_address) continue; - const version = try s.stream.readInt(u16, di.endian); - if (version < 2 or version > 5) return error.InvalidDebugInfo; + const header = std.c._dyld_get_image_header(i) orelse continue; + // The array of load commands is right after the header + var cmd_ptr = @intToPtr([*]u8, @ptrToInt(header) + @sizeOf(macho.mach_header_64)); - const debug_abbrev_offset = if (is_64) try s.stream.readInt(u64, di.endian) else try s.stream.readInt(u32, di.endian); + var cmds = header.ncmds; + while (cmds != 0) : (cmds -= 1) { + const lc = @ptrCast( + *macho.load_command, + @alignCast(@alignOf(macho.load_command), cmd_ptr), + ); + cmd_ptr += lc.cmdsize; + if (lc.cmd != macho.LC_SEGMENT_64) continue; - const address_size = try s.stream.readByte(); - if (address_size != @sizeOf(usize)) return error.InvalidDebugInfo; + const segment_cmd = @ptrCast( + *const std.macho.segment_command_64, + @alignCast(@alignOf(std.macho.segment_command_64), lc), + ); - const compile_unit_pos = try s.seekable_stream.getPos(); - const abbrev_table = try di.getAbbrevTable(debug_abbrev_offset); + const rebased_address = address - base_address; + const seg_start = segment_cmd.vmaddr; + const seg_end = seg_start + segment_cmd.vmsize; - try s.seekable_stream.seekTo(compile_unit_pos); - - const compile_unit_die = try di.allocator().create(Die); - compile_unit_die.* = (try di.parseDie(&s.stream, abbrev_table, is_64)) orelse return error.InvalidDebugInfo; - - if (compile_unit_die.tag_id != DW.TAG_compile_unit) return error.InvalidDebugInfo; - - const pc_range = x: { - if (compile_unit_die.getAttrAddr(DW.AT_low_pc)) |low_pc| { - if (compile_unit_die.getAttr(DW.AT_high_pc)) |high_pc_value| { - const pc_end = switch (high_pc_value.*) { - FormValue.Address => |value| value, - FormValue.Const => |value| b: { - const offset = try value.asUnsignedLe(); - break :b (low_pc + offset); - }, - else => return error.InvalidDebugInfo, - }; - break :x PcRange{ - .start = low_pc, - .end = pc_end, - }; - } else { - break :x null; + if (rebased_address >= seg_start and rebased_address < seg_end) { + if (self.address_map.getValue(base_address)) |obj_di| { + return obj_di; } - } else |err| { - if (err != error.MissingDebugInfo) return err; - break :x null; - } - }; - try di.compile_unit_list.append(CompileUnit{ - .version = version, - .is_64 = is_64, - .pc_range = pc_range, - .die = compile_unit_die, - }); + const obj_di = try self.allocator.create(ModuleDebugInfo); + errdefer self.allocator.destroy(obj_di); - this_unit_offset += next_offset; - } - } - - fn findCompileUnit(di: *DwarfInfo, target_address: u64) !*const CompileUnit { - for (di.compile_unit_list.toSlice()) |*compile_unit| { - if (compile_unit.pc_range) |range| { - if (target_address >= range.start and target_address < range.end) return compile_unit; - } - if (di.debug_ranges) |debug_ranges| { - if (compile_unit.die.getAttrSecOffset(DW.AT_ranges)) |ranges_offset| { - var s = io.SliceSeekableInStream.init(debug_ranges); - - // All the addresses in the list are relative to the value - // specified by DW_AT_low_pc or to some other value encoded - // in the list itself. - // If no starting value is specified use zero. - var base_address = compile_unit.die.getAttrAddr(DW.AT_low_pc) catch |err| switch (err) { - error.MissingDebugInfo => 0, + const macho_path = mem.toSliceConst(u8, std.c._dyld_get_image_name(i)); + obj_di.* = openMachODebugInfo(self.allocator, macho_path) catch |err| switch (err) { + error.FileNotFound => return error.MissingDebugInfo, else => return err, }; + obj_di.base_address = base_address; - try s.seekable_stream.seekTo(ranges_offset); + try self.address_map.putNoClobber(base_address, obj_di); - while (true) { - const begin_addr = try s.stream.readIntLittle(usize); - const end_addr = try s.stream.readIntLittle(usize); - if (begin_addr == 0 and end_addr == 0) { - break; - } - // This entry selects a new value for the base address - if (begin_addr == maxInt(usize)) { - base_address = end_addr; - continue; - } - if (target_address >= base_address + begin_addr and target_address < base_address + end_addr) { - return compile_unit; - } - } - } else |err| { - if (err != error.MissingDebugInfo) return err; - continue; + return obj_di; } } } + return error.MissingDebugInfo; } - /// Gets an already existing AbbrevTable given the abbrev_offset, or if not found, - /// seeks in the stream and parses it. - fn getAbbrevTable(di: *DwarfInfo, abbrev_offset: u64) !*const AbbrevTable { - for (di.abbrev_table_list.toSlice()) |*header| { - if (header.offset == abbrev_offset) { - return &header.table; - } - } - try di.abbrev_table_list.append(AbbrevTableHeader{ - .offset = abbrev_offset, - .table = try di.parseAbbrevTable(abbrev_offset), - }); - return &di.abbrev_table_list.items[di.abbrev_table_list.len - 1].table; - } + fn lookupModuleWin32(self: *DebugInfo, address: usize) !*ModuleDebugInfo { + const process_handle = windows.kernel32.GetCurrentProcess(); - fn parseAbbrevTable(di: *DwarfInfo, offset: u64) !AbbrevTable { - var s = io.SliceSeekableInStream.init(di.debug_abbrev); - - try s.seekable_stream.seekTo(offset); - var result = AbbrevTable.init(di.allocator()); - errdefer result.deinit(); - while (true) { - const abbrev_code = try leb.readULEB128(u64, &s.stream); - if (abbrev_code == 0) return result; - try result.append(AbbrevTableEntry{ - .abbrev_code = abbrev_code, - .tag_id = try leb.readULEB128(u64, &s.stream), - .has_children = (try s.stream.readByte()) == DW.CHILDREN_yes, - .attrs = ArrayList(AbbrevAttr).init(di.allocator()), - }); - const attrs = &result.items[result.len - 1].attrs; - - while (true) { - const attr_id = try leb.readULEB128(u64, &s.stream); - const form_id = try leb.readULEB128(u64, &s.stream); - if (attr_id == 0 and form_id == 0) break; - try attrs.append(AbbrevAttr{ - .attr_id = attr_id, - .form_id = form_id, - }); - } - } - } - - fn parseDie(di: *DwarfInfo, in_stream: var, abbrev_table: *const AbbrevTable, is_64: bool) !?Die { - const abbrev_code = try leb.readULEB128(u64, in_stream); - if (abbrev_code == 0) return null; - const table_entry = getAbbrevTableEntry(abbrev_table, abbrev_code) orelse return error.InvalidDebugInfo; - - var result = Die{ - .tag_id = table_entry.tag_id, - .has_children = table_entry.has_children, - .attrs = ArrayList(Die.Attr).init(di.allocator()), - }; - try result.attrs.resize(table_entry.attrs.len); - for (table_entry.attrs.toSliceConst()) |attr, i| { - result.attrs.items[i] = Die.Attr{ - .id = attr.attr_id, - .value = try parseFormValue(di.allocator(), in_stream, attr.form_id, is_64), - }; - } - return result; - } - - fn getLineNumberInfo(di: *DwarfInfo, compile_unit: CompileUnit, target_address: usize) !LineInfo { - var s = io.SliceSeekableInStream.init(di.debug_line); - - const compile_unit_cwd = try compile_unit.die.getAttrString(di, DW.AT_comp_dir); - const line_info_offset = try compile_unit.die.getAttrSecOffset(DW.AT_stmt_list); - - try s.seekable_stream.seekTo(line_info_offset); - - var is_64: bool = undefined; - const unit_length = try readInitialLength(@TypeOf(s.stream.readFn).ReturnType.ErrorSet, &s.stream, &is_64); - if (unit_length == 0) { + // Find how many modules are actually loaded + var dummy: windows.HMODULE = undefined; + var bytes_needed: windows.DWORD = undefined; + if (windows.kernel32.K32EnumProcessModules( + process_handle, + @ptrCast([*]windows.HMODULE, &dummy), + 0, + &bytes_needed, + ) == 0) return error.MissingDebugInfo; - } - const next_offset = unit_length + (if (is_64) @as(usize, 12) else @as(usize, 4)); - const version = try s.stream.readInt(u16, di.endian); - // TODO support 3 and 5 - if (version != 2 and version != 4) return error.InvalidDebugInfo; + const needed_modules = bytes_needed / @sizeOf(windows.HMODULE); - const prologue_length = if (is_64) try s.stream.readInt(u64, di.endian) else try s.stream.readInt(u32, di.endian); - const prog_start_offset = (try s.seekable_stream.getPos()) + prologue_length; + // Fetch the complete module list + var modules = try self.allocator.alloc(windows.HMODULE, needed_modules); + defer self.allocator.free(modules); + if (windows.kernel32.K32EnumProcessModules( + process_handle, + modules.ptr, + try math.cast(windows.DWORD, modules.len * @sizeOf(windows.HMODULE)), + &bytes_needed, + ) == 0) + return error.MissingDebugInfo; - const minimum_instruction_length = try s.stream.readByte(); - if (minimum_instruction_length == 0) return error.InvalidDebugInfo; + // There's an unavoidable TOCTOU problem here, the module list may have + // changed between the two EnumProcessModules call. + // Pick the smallest amount of elements to avoid processing garbage. + const needed_modules_after = bytes_needed / @sizeOf(windows.HMODULE); + const loaded_modules = math.min(needed_modules, needed_modules_after); - if (version >= 4) { - // maximum_operations_per_instruction - _ = try s.stream.readByte(); - } + for (modules[0..loaded_modules]) |module| { + var info: windows.MODULEINFO = undefined; + if (windows.kernel32.K32GetModuleInformation( + process_handle, + module, + &info, + @sizeOf(@TypeOf(info)), + ) == 0) + return error.MissingDebugInfo; - const default_is_stmt = (try s.stream.readByte()) != 0; - const line_base = try s.stream.readByteSigned(); + const seg_start = @ptrToInt(info.lpBaseOfDll); + const seg_end = seg_start + info.SizeOfImage; - const line_range = try s.stream.readByte(); - if (line_range == 0) return error.InvalidDebugInfo; - - const opcode_base = try s.stream.readByte(); - - const standard_opcode_lengths = try di.allocator().alloc(u8, opcode_base - 1); - - { - var i: usize = 0; - while (i < opcode_base - 1) : (i += 1) { - standard_opcode_lengths[i] = try s.stream.readByte(); - } - } - - var include_directories = ArrayList([]u8).init(di.allocator()); - try include_directories.append(compile_unit_cwd); - while (true) { - const dir = try readStringRaw(di.allocator(), &s.stream); - if (dir.len == 0) break; - try include_directories.append(dir); - } - - var file_entries = ArrayList(FileEntry).init(di.allocator()); - var prog = LineNumberProgram.init(default_is_stmt, include_directories.toSliceConst(), &file_entries, target_address); - - while (true) { - const file_name = try readStringRaw(di.allocator(), &s.stream); - if (file_name.len == 0) break; - const dir_index = try leb.readULEB128(usize, &s.stream); - const mtime = try leb.readULEB128(usize, &s.stream); - const len_bytes = try leb.readULEB128(usize, &s.stream); - try file_entries.append(FileEntry{ - .file_name = file_name, - .dir_index = dir_index, - .mtime = mtime, - .len_bytes = len_bytes, - }); - } - - try s.seekable_stream.seekTo(prog_start_offset); - - const next_unit_pos = line_info_offset + next_offset; - - while ((try s.seekable_stream.getPos()) < next_unit_pos) { - const opcode = try s.stream.readByte(); - - if (opcode == DW.LNS_extended_op) { - const op_size = try leb.readULEB128(u64, &s.stream); - if (op_size < 1) return error.InvalidDebugInfo; - var sub_op = try s.stream.readByte(); - switch (sub_op) { - DW.LNE_end_sequence => { - prog.end_sequence = true; - if (try prog.checkLineMatch()) |info| return info; - prog.reset(); - }, - DW.LNE_set_address => { - const addr = try s.stream.readInt(usize, di.endian); - prog.address = addr; - }, - DW.LNE_define_file => { - const file_name = try readStringRaw(di.allocator(), &s.stream); - const dir_index = try leb.readULEB128(usize, &s.stream); - const mtime = try leb.readULEB128(usize, &s.stream); - const len_bytes = try leb.readULEB128(usize, &s.stream); - try file_entries.append(FileEntry{ - .file_name = file_name, - .dir_index = dir_index, - .mtime = mtime, - .len_bytes = len_bytes, - }); - }, - else => { - const fwd_amt = math.cast(isize, op_size - 1) catch return error.InvalidDebugInfo; - try s.seekable_stream.seekBy(fwd_amt); - }, - } - } else if (opcode >= opcode_base) { - // special opcodes - const adjusted_opcode = opcode - opcode_base; - const inc_addr = minimum_instruction_length * (adjusted_opcode / line_range); - const inc_line = @as(i32, line_base) + @as(i32, adjusted_opcode % line_range); - prog.line += inc_line; - prog.address += inc_addr; - if (try prog.checkLineMatch()) |info| return info; - prog.basic_block = false; - } else { - switch (opcode) { - DW.LNS_copy => { - if (try prog.checkLineMatch()) |info| return info; - prog.basic_block = false; - }, - DW.LNS_advance_pc => { - const arg = try leb.readULEB128(usize, &s.stream); - prog.address += arg * minimum_instruction_length; - }, - DW.LNS_advance_line => { - const arg = try leb.readILEB128(i64, &s.stream); - prog.line += arg; - }, - DW.LNS_set_file => { - const arg = try leb.readULEB128(usize, &s.stream); - prog.file = arg; - }, - DW.LNS_set_column => { - const arg = try leb.readULEB128(u64, &s.stream); - prog.column = arg; - }, - DW.LNS_negate_stmt => { - prog.is_stmt = !prog.is_stmt; - }, - DW.LNS_set_basic_block => { - prog.basic_block = true; - }, - DW.LNS_const_add_pc => { - const inc_addr = minimum_instruction_length * ((255 - opcode_base) / line_range); - prog.address += inc_addr; - }, - DW.LNS_fixed_advance_pc => { - const arg = try s.stream.readInt(u16, di.endian); - prog.address += arg; - }, - DW.LNS_set_prologue_end => {}, - else => { - if (opcode - 1 >= standard_opcode_lengths.len) return error.InvalidDebugInfo; - const len_bytes = standard_opcode_lengths[opcode - 1]; - try s.seekable_stream.seekBy(len_bytes); - }, + if (address >= seg_start and address < seg_end) { + if (self.address_map.getValue(seg_start)) |obj_di| { + return obj_di; } + + var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined; + // openFileAbsoluteW requires the prefix to be present + mem.copy(u16, name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' }); + const len = windows.kernel32.K32GetModuleFileNameExW( + process_handle, + module, + @ptrCast(windows.LPWSTR, &name_buffer[4]), + windows.PATH_MAX_WIDE, + ); + assert(len > 0); + + const obj_di = try self.allocator.create(ModuleDebugInfo); + errdefer self.allocator.destroy(obj_di); + + obj_di.* = openCoffDebugInfo(self.allocator, name_buffer[0..:0]) catch |err| switch (err) { + error.FileNotFound => return error.MissingDebugInfo, + else => return err, + }; + obj_di.base_address = seg_start; + + try self.address_map.putNoClobber(seg_start, obj_di); + + return obj_di; } } return error.MissingDebugInfo; } - fn getString(di: *DwarfInfo, offset: u64) ![]u8 { - if (offset > di.debug_str.len) - return error.InvalidDebugInfo; - const casted_offset = math.cast(usize, offset) catch - return error.InvalidDebugInfo; + fn lookupModuleDl(self: *DebugInfo, address: usize) !*ModuleDebugInfo { + var ctx: struct { + // Input + address: usize, + // Output + base_address: usize = undefined, + name: []const u8 = undefined, + } = .{ .address = address }; + const CtxTy = @TypeOf(ctx); - // Valid strings always have a terminating zero byte - if (mem.indexOfScalarPos(u8, di.debug_str, casted_offset, 0)) |last| { - return di.debug_str[casted_offset..last]; + if (os.dl_iterate_phdr(&ctx, anyerror, struct { + fn callback(info: *os.dl_phdr_info, size: usize, context: *CtxTy) !void { + // The base address is too high + if (context.address < info.dlpi_addr) + return; + + const phdrs = info.dlpi_phdr[0..info.dlpi_phnum]; + for (phdrs) |*phdr| { + if (phdr.p_type != elf.PT_LOAD) continue; + + const seg_start = info.dlpi_addr + phdr.p_vaddr; + const seg_end = seg_start + phdr.p_memsz; + + if (context.address >= seg_start and context.address < seg_end) { + // Android libc uses NULL instead of an empty string to mark the + // main program + context.name = if (info.dlpi_name) |dlpi_name| + mem.toSliceConst(u8, dlpi_name) + else + ""; + context.base_address = info.dlpi_addr; + // Stop the iteration + return error.Found; + } + } + } + }.callback)) { + return error.MissingDebugInfo; + } else |err| switch (err) { + error.Found => {}, + else => return error.MissingDebugInfo, } - return error.InvalidDebugInfo; + if (self.address_map.getValue(ctx.base_address)) |obj_di| { + return obj_di; + } + + const elf_path = if (ctx.name.len > 0) + ctx.name + else blk: { + var buf: [fs.MAX_PATH_BYTES]u8 = undefined; + break :blk try fs.selfExePath(&buf); + }; + + const obj_di = try self.allocator.create(ModuleDebugInfo); + errdefer self.allocator.destroy(obj_di); + + obj_di.* = openElfDebugInfo(self.allocator, elf_path) catch |err| switch (err) { + error.FileNotFound => return error.MissingDebugInfo, + else => return err, + }; + obj_di.base_address = ctx.base_address; + + try self.address_map.putNoClobber(ctx.base_address, obj_di); + + return obj_di; } }; -pub const DebugInfo = switch (builtin.os) { +const SymbolInfo = struct { + symbol_name: []const u8 = "???", + compile_unit_name: []const u8 = "???", + line_info: ?LineInfo = null, + + fn deinit(self: @This()) void { + if (self.line_info) |li| { + li.deinit(); + } + } +}; + +pub const ModuleDebugInfo = switch (builtin.os.tag) { .macosx, .ios, .watchos, .tvos => struct { + base_address: usize, + mapped_memory: []const u8, symbols: []const MachoSymbol, - strings: []const u8, + strings: [:0]const u8, ofiles: OFileTable, - const OFileTable = std.HashMap( - *macho.nlist_64, - DwarfInfo, - std.hash_map.getHashPtrAddrFn(*macho.nlist_64), - std.hash_map.getTrivialEqlFn(*macho.nlist_64), - ); + const OFileTable = std.StringHashMap(DW.DwarfInfo); - pub fn allocator(self: DebugInfo) *mem.Allocator { + pub fn allocator(self: @This()) *mem.Allocator { return self.ofiles.allocator; } + + fn loadOFile(self: *@This(), o_file_path: []const u8) !DW.DwarfInfo { + const mapped_mem = try mapWholeFile(o_file_path); + + const hdr = @ptrCast( + *const macho.mach_header_64, + @alignCast(@alignOf(macho.mach_header_64), mapped_mem.ptr), + ); + if (hdr.magic != std.macho.MH_MAGIC_64) + return error.InvalidDebugInfo; + + const hdr_base = @ptrCast([*]const u8, hdr); + var ptr = hdr_base + @sizeOf(macho.mach_header_64); + var ncmd: u32 = hdr.ncmds; + const segcmd = while (ncmd != 0) : (ncmd -= 1) { + const lc = @ptrCast(*const std.macho.load_command, ptr); + switch (lc.cmd) { + std.macho.LC_SEGMENT_64 => { + break @ptrCast( + *const std.macho.segment_command_64, + @alignCast(@alignOf(std.macho.segment_command_64), ptr), + ); + }, + else => {}, + } + ptr = @alignCast(@alignOf(std.macho.load_command), ptr + lc.cmdsize); + } else { + return error.MissingDebugInfo; + }; + + var opt_debug_line: ?*const macho.section_64 = null; + var opt_debug_info: ?*const macho.section_64 = null; + var opt_debug_abbrev: ?*const macho.section_64 = null; + var opt_debug_str: ?*const macho.section_64 = null; + var opt_debug_ranges: ?*const macho.section_64 = null; + + const sections = @ptrCast( + [*]const macho.section_64, + @alignCast(@alignOf(macho.section_64), ptr + @sizeOf(std.macho.segment_command_64)), + )[0..segcmd.nsects]; + for (sections) |*sect| { + // The section name may not exceed 16 chars and a trailing null may + // not be present + const name = if (mem.indexOfScalar(u8, sect.sectname[0..], 0)) |last| + sect.sectname[0..last] + else + sect.sectname[0..]; + + if (mem.eql(u8, name, "__debug_line")) { + opt_debug_line = sect; + } else if (mem.eql(u8, name, "__debug_info")) { + opt_debug_info = sect; + } else if (mem.eql(u8, name, "__debug_abbrev")) { + opt_debug_abbrev = sect; + } else if (mem.eql(u8, name, "__debug_str")) { + opt_debug_str = sect; + } else if (mem.eql(u8, name, "__debug_ranges")) { + opt_debug_ranges = sect; + } + } + + const debug_line = opt_debug_line orelse + return error.MissingDebugInfo; + const debug_info = opt_debug_info orelse + return error.MissingDebugInfo; + const debug_str = opt_debug_str orelse + return error.MissingDebugInfo; + const debug_abbrev = opt_debug_abbrev orelse + return error.MissingDebugInfo; + + var di = DW.DwarfInfo{ + .endian = .Little, + .debug_info = try chopSlice(mapped_mem, debug_info.offset, debug_info.size), + .debug_abbrev = try chopSlice(mapped_mem, debug_abbrev.offset, debug_abbrev.size), + .debug_str = try chopSlice(mapped_mem, debug_str.offset, debug_str.size), + .debug_line = try chopSlice(mapped_mem, debug_line.offset, debug_line.size), + .debug_ranges = if (opt_debug_ranges) |debug_ranges| + try chopSlice(mapped_mem, debug_ranges.offset, debug_ranges.size) + else + null, + }; + + try DW.openDwarfDebugInfo(&di, self.allocator()); + + // Add the debug info to the cache + try self.ofiles.putNoClobber(o_file_path, di); + + return di; + } + + fn getSymbolAtAddress(self: *@This(), address: usize) !SymbolInfo { + // Translate the VA into an address into this object + const relocated_address = address - self.base_address; + assert(relocated_address >= 0x100000000); + + // Find the .o file where this symbol is defined + const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse + return SymbolInfo{}; + + // XXX: Return the symbol name + if (symbol.ofile == null) + return SymbolInfo{}; + + assert(symbol.ofile.?.n_strx < self.strings.len); + const o_file_path = mem.toSliceConst(u8, self.strings.ptr + symbol.ofile.?.n_strx); + + // Check if its debug infos are already in the cache + var o_file_di = self.ofiles.getValue(o_file_path) orelse + (self.loadOFile(o_file_path) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => { + // XXX: Return the symbol name + return SymbolInfo{}; + }, + else => return err, + }); + + // Translate again the address, this time into an address inside the + // .o file + const relocated_address_o = relocated_address - symbol.reloc; + + if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| { + return SymbolInfo{ + .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???", + .compile_unit_name = compile_unit.die.getAttrString(&o_file_di, DW.AT_name) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => "???", + else => return err, + }, + .line_info = o_file_di.getLineNumberInfo(compile_unit.*, relocated_address_o) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => null, + else => return err, + }, + }; + } else |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => { + return SymbolInfo{}; + }, + else => return err, + } + + unreachable; + } }, .uefi, .windows => struct { + base_address: usize, pdb: pdb.Pdb, coff: *coff.Coff, sect_contribs: []pdb.SectionContribEntry, modules: []Module, - }, - else => DwarfInfo, -}; -const PcRange = struct { - start: u64, - end: u64, -}; - -const CompileUnit = struct { - version: u16, - is_64: bool, - die: *Die, - pc_range: ?PcRange, -}; - -const AbbrevTable = ArrayList(AbbrevTableEntry); - -const AbbrevTableHeader = struct { - // offset from .debug_abbrev - offset: u64, - table: AbbrevTable, -}; - -const AbbrevTableEntry = struct { - has_children: bool, - abbrev_code: u64, - tag_id: u64, - attrs: ArrayList(AbbrevAttr), -}; - -const AbbrevAttr = struct { - attr_id: u64, - form_id: u64, -}; - -const FormValue = union(enum) { - Address: u64, - Block: []u8, - Const: Constant, - ExprLoc: []u8, - Flag: bool, - SecOffset: u64, - Ref: u64, - RefAddr: u64, - String: []u8, - StrPtr: u64, -}; - -const Constant = struct { - payload: u64, - signed: bool, - - fn asUnsignedLe(self: *const Constant) !u64 { - if (self.signed) return error.InvalidDebugInfo; - return self.payload; - } -}; - -const Die = struct { - tag_id: u64, - has_children: bool, - attrs: ArrayList(Attr), - - const Attr = struct { - id: u64, - value: FormValue, - }; - - fn getAttr(self: *const Die, id: u64) ?*const FormValue { - for (self.attrs.toSliceConst()) |*attr| { - if (attr.id == id) return &attr.value; + pub fn allocator(self: @This()) *mem.Allocator { + return self.coff.allocator; } - return null; - } - fn getAttrAddr(self: *const Die, id: u64) !u64 { - const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; - return switch (form_value.*) { - FormValue.Address => |value| value, - else => error.InvalidDebugInfo, - }; - } + fn getSymbolAtAddress(self: *@This(), address: usize) !SymbolInfo { + // Translate the VA into an address into this object + const relocated_address = address - self.base_address; - fn getAttrSecOffset(self: *const Die, id: u64) !u64 { - const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; - return switch (form_value.*) { - FormValue.Const => |value| value.asUnsignedLe(), - FormValue.SecOffset => |value| value, - else => error.InvalidDebugInfo, - }; - } + var coff_section: *coff.Section = undefined; + const mod_index = for (self.sect_contribs) |sect_contrib| { + if (sect_contrib.Section > self.coff.sections.len) continue; + // Remember that SectionContribEntry.Section is 1-based. + coff_section = &self.coff.sections.toSlice()[sect_contrib.Section - 1]; - fn getAttrUnsignedLe(self: *const Die, id: u64) !u64 { - const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; - return switch (form_value.*) { - FormValue.Const => |value| value.asUnsignedLe(), - else => error.InvalidDebugInfo, - }; - } + const vaddr_start = coff_section.header.virtual_address + sect_contrib.Offset; + const vaddr_end = vaddr_start + sect_contrib.Size; + if (relocated_address >= vaddr_start and relocated_address < vaddr_end) { + break sect_contrib.ModuleIndex; + } + } else { + // we have no information to add to the address + return SymbolInfo{}; + }; - fn getAttrRef(self: *const Die, id: u64) !u64 { - const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; - return switch (form_value.*) { - FormValue.Ref => |value| value, - else => error.InvalidDebugInfo, - }; - } + const mod = &self.modules[mod_index]; + try populateModule(self, mod); + const obj_basename = fs.path.basename(mod.obj_file_name); - fn getAttrString(self: *const Die, di: *DwarfInfo, id: u64) ![]u8 { - const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; - return switch (form_value.*) { - FormValue.String => |value| value, - FormValue.StrPtr => |offset| di.getString(offset), - else => error.InvalidDebugInfo, - }; - } -}; + var symbol_i: usize = 0; + const symbol_name = if (!mod.populated) "???" else while (symbol_i != mod.symbols.len) { + const prefix = @ptrCast(*pdb.RecordPrefix, &mod.symbols[symbol_i]); + if (prefix.RecordLen < 2) + return error.InvalidDebugInfo; + switch (prefix.RecordKind) { + .S_LPROC32, .S_GPROC32 => { + const proc_sym = @ptrCast(*pdb.ProcSym, &mod.symbols[symbol_i + @sizeOf(pdb.RecordPrefix)]); + const vaddr_start = coff_section.header.virtual_address + proc_sym.CodeOffset; + const vaddr_end = vaddr_start + proc_sym.CodeSize; + if (relocated_address >= vaddr_start and relocated_address < vaddr_end) { + break mem.toSliceConst(u8, @ptrCast([*:0]u8, proc_sym) + @sizeOf(pdb.ProcSym)); + } + }, + else => {}, + } + symbol_i += prefix.RecordLen + @sizeOf(u16); + if (symbol_i > mod.symbols.len) + return error.InvalidDebugInfo; + } else "???"; -const FileEntry = struct { - file_name: []const u8, - dir_index: usize, - mtime: usize, - len_bytes: usize, -}; + const subsect_info = mod.subsect_info; -pub const LineInfo = struct { - line: u64, - column: u64, - file_name: []const u8, - allocator: ?*mem.Allocator, + var sect_offset: usize = 0; + var skip_len: usize = undefined; + const opt_line_info = subsections: { + const checksum_offset = mod.checksum_offset orelse break :subsections null; + while (sect_offset != subsect_info.len) : (sect_offset += skip_len) { + const subsect_hdr = @ptrCast(*pdb.DebugSubsectionHeader, &subsect_info[sect_offset]); + skip_len = subsect_hdr.Length; + sect_offset += @sizeOf(pdb.DebugSubsectionHeader); - fn deinit(self: LineInfo) void { - const allocator = self.allocator orelse return; - allocator.free(self.file_name); - } -}; + switch (subsect_hdr.Kind) { + .Lines => { + var line_index = sect_offset; -const LineNumberProgram = struct { - address: usize, - file: usize, - line: i64, - column: u64, - is_stmt: bool, - basic_block: bool, - end_sequence: bool, + const line_hdr = @ptrCast(*pdb.LineFragmentHeader, &subsect_info[line_index]); + if (line_hdr.RelocSegment == 0) + return error.MissingDebugInfo; + line_index += @sizeOf(pdb.LineFragmentHeader); + const frag_vaddr_start = coff_section.header.virtual_address + line_hdr.RelocOffset; + const frag_vaddr_end = frag_vaddr_start + line_hdr.CodeSize; - default_is_stmt: bool, - target_address: usize, - include_dirs: []const []const u8, - file_entries: *ArrayList(FileEntry), + if (relocated_address >= frag_vaddr_start and relocated_address < frag_vaddr_end) { + // There is an unknown number of LineBlockFragmentHeaders (and their accompanying line and column records) + // from now on. We will iterate through them, and eventually find a LineInfo that we're interested in, + // breaking out to :subsections. If not, we will make sure to not read anything outside of this subsection. + const subsection_end_index = sect_offset + subsect_hdr.Length; - prev_address: usize, - prev_file: usize, - prev_line: i64, - prev_column: u64, - prev_is_stmt: bool, - prev_basic_block: bool, - prev_end_sequence: bool, + while (line_index < subsection_end_index) { + const block_hdr = @ptrCast(*pdb.LineBlockFragmentHeader, &subsect_info[line_index]); + line_index += @sizeOf(pdb.LineBlockFragmentHeader); + const start_line_index = line_index; - // Reset the state machine following the DWARF specification - pub fn reset(self: *LineNumberProgram) void { - self.address = 0; - self.file = 1; - self.line = 1; - self.column = 0; - self.is_stmt = self.default_is_stmt; - self.basic_block = false; - self.end_sequence = false; - // Invalidate all the remaining fields - self.prev_address = 0; - self.prev_file = undefined; - self.prev_line = undefined; - self.prev_column = undefined; - self.prev_is_stmt = undefined; - self.prev_basic_block = undefined; - self.prev_end_sequence = undefined; - } + const has_column = line_hdr.Flags.LF_HaveColumns; - pub fn init(is_stmt: bool, include_dirs: []const []const u8, file_entries: *ArrayList(FileEntry), target_address: usize) LineNumberProgram { - return LineNumberProgram{ - .address = 0, - .file = 1, - .line = 1, - .column = 0, - .is_stmt = is_stmt, - .basic_block = false, - .end_sequence = false, - .include_dirs = include_dirs, - .file_entries = file_entries, - .default_is_stmt = is_stmt, - .target_address = target_address, - .prev_address = 0, - .prev_file = undefined, - .prev_line = undefined, - .prev_column = undefined, - .prev_is_stmt = undefined, - .prev_basic_block = undefined, - .prev_end_sequence = undefined, - }; - } + // All line entries are stored inside their line block by ascending start address. + // Heuristic: we want to find the last line entry + // that has a vaddr_start <= relocated_address. + // This is done with a simple linear search. + var line_i: u32 = 0; + while (line_i < block_hdr.NumLines) : (line_i += 1) { + const line_num_entry = @ptrCast(*pdb.LineNumberEntry, &subsect_info[line_index]); + line_index += @sizeOf(pdb.LineNumberEntry); - pub fn checkLineMatch(self: *LineNumberProgram) !?LineInfo { - if (self.target_address >= self.prev_address and self.target_address < self.address) { - const file_entry = if (self.prev_file == 0) { - return error.MissingDebugInfo; - } else if (self.prev_file - 1 >= self.file_entries.len) { - return error.InvalidDebugInfo; - } else - &self.file_entries.items[self.prev_file - 1]; + const vaddr_start = frag_vaddr_start + line_num_entry.Offset; + if (relocated_address < vaddr_start) { + break; + } + } - const dir_name = if (file_entry.dir_index >= self.include_dirs.len) { - return error.InvalidDebugInfo; - } else - self.include_dirs[file_entry.dir_index]; - const file_name = try fs.path.join(self.file_entries.allocator, &[_][]const u8{ dir_name, file_entry.file_name }); - errdefer self.file_entries.allocator.free(file_name); - return LineInfo{ - .line = if (self.prev_line >= 0) @intCast(u64, self.prev_line) else 0, - .column = self.prev_column, - .file_name = file_name, - .allocator = self.file_entries.allocator, + // line_i == 0 would mean that no matching LineNumberEntry was found. + if (line_i > 0) { + const subsect_index = checksum_offset + block_hdr.NameIndex; + const chksum_hdr = @ptrCast(*pdb.FileChecksumEntryHeader, &mod.subsect_info[subsect_index]); + const strtab_offset = @sizeOf(pdb.PDBStringTableHeader) + chksum_hdr.FileNameOffset; + try self.pdb.string_table.seekTo(strtab_offset); + const source_file_name = try self.pdb.string_table.readNullTermString(self.allocator()); + + const line_entry_idx = line_i - 1; + + const column = if (has_column) blk: { + const start_col_index = start_line_index + @sizeOf(pdb.LineNumberEntry) * block_hdr.NumLines; + const col_index = start_col_index + @sizeOf(pdb.ColumnNumberEntry) * line_entry_idx; + const col_num_entry = @ptrCast(*pdb.ColumnNumberEntry, &subsect_info[col_index]); + break :blk col_num_entry.StartColumn; + } else 0; + + const found_line_index = start_line_index + line_entry_idx * @sizeOf(pdb.LineNumberEntry); + const line_num_entry = @ptrCast(*pdb.LineNumberEntry, &subsect_info[found_line_index]); + const flags = @ptrCast(*pdb.LineNumberEntry.Flags, &line_num_entry.Flags); + + break :subsections LineInfo{ + .allocator = self.allocator(), + .file_name = source_file_name, + .line = flags.Start, + .column = column, + }; + } + } + + // Checking that we are not reading garbage after the (possibly) multiple block fragments. + if (line_index != subsection_end_index) { + return error.InvalidDebugInfo; + } + } + }, + else => {}, + } + + if (sect_offset > subsect_info.len) + return error.InvalidDebugInfo; + } else { + break :subsections null; + } + }; + + return SymbolInfo{ + .symbol_name = symbol_name, + .compile_unit_name = obj_basename, + .line_info = opt_line_info, }; } + }, + .linux, .freebsd => struct { + base_address: usize, + dwarf: DW.DwarfInfo, + mapped_memory: []const u8, - self.prev_address = self.address; - self.prev_file = self.file; - self.prev_line = self.line; - self.prev_column = self.column; - self.prev_is_stmt = self.is_stmt; - self.prev_basic_block = self.basic_block; - self.prev_end_sequence = self.end_sequence; - return null; - } -}; + fn getSymbolAtAddress(self: *@This(), address: usize) !SymbolInfo { + // Translate the VA into an address into this object + const relocated_address = address - self.base_address; -// TODO the noasyncs here are workarounds -fn readStringRaw(allocator: *mem.Allocator, in_stream: var) ![]u8 { - var buf = ArrayList(u8).init(allocator); - while (true) { - const byte = try noasync in_stream.readByte(); - if (byte == 0) break; - try buf.append(byte); - } - return buf.toSlice(); -} - -// TODO the noasyncs here are workarounds -fn readAllocBytes(allocator: *mem.Allocator, in_stream: var, size: usize) ![]u8 { - const buf = try allocator.alloc(u8, size); - errdefer allocator.free(buf); - if ((try noasync in_stream.read(buf)) < size) return error.EndOfFile; - return buf; -} - -fn parseFormValueBlockLen(allocator: *mem.Allocator, in_stream: var, size: usize) !FormValue { - const buf = try readAllocBytes(allocator, in_stream, size); - return FormValue{ .Block = buf }; -} - -// TODO the noasyncs here are workarounds -fn parseFormValueBlock(allocator: *mem.Allocator, in_stream: var, size: usize) !FormValue { - const block_len = try noasync in_stream.readVarInt(usize, builtin.Endian.Little, size); - return parseFormValueBlockLen(allocator, in_stream, block_len); -} - -fn parseFormValueConstant(allocator: *mem.Allocator, in_stream: var, signed: bool, comptime size: i32) !FormValue { - // TODO: Please forgive me, I've worked around zig not properly spilling some intermediate values here. - // `noasync` should be removed from all the function calls once it is fixed. - return FormValue{ - .Const = Constant{ - .signed = signed, - .payload = switch (size) { - 1 => try noasync in_stream.readIntLittle(u8), - 2 => try noasync in_stream.readIntLittle(u16), - 4 => try noasync in_stream.readIntLittle(u32), - 8 => try noasync in_stream.readIntLittle(u64), - -1 => blk: { - if (signed) { - const x = try noasync leb.readILEB128(i64, in_stream); - break :blk @bitCast(u64, x); - } else { - const x = try noasync leb.readULEB128(u64, in_stream); - break :blk x; - } + if (noasync self.dwarf.findCompileUnit(relocated_address)) |compile_unit| { + return SymbolInfo{ + .symbol_name = noasync self.dwarf.getSymbolName(relocated_address) orelse "???", + .compile_unit_name = compile_unit.die.getAttrString(&self.dwarf, DW.AT_name) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => "???", + else => return err, + }, + .line_info = noasync self.dwarf.getLineNumberInfo(compile_unit.*, relocated_address) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => null, + else => return err, + }, + }; + } else |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => { + return SymbolInfo{}; }, - else => @compileError("Invalid size"), - }, - }, - }; -} - -// TODO the noasyncs here are workarounds -fn parseFormValueDwarfOffsetSize(in_stream: var, is_64: bool) !u64 { - return if (is_64) try noasync in_stream.readIntLittle(u64) else @as(u64, try noasync in_stream.readIntLittle(u32)); -} - -// TODO the noasyncs here are workarounds -fn parseFormValueTargetAddrSize(in_stream: var) !u64 { - if (@sizeOf(usize) == 4) { - // TODO this cast should not be needed - return @as(u64, try noasync in_stream.readIntLittle(u32)); - } else if (@sizeOf(usize) == 8) { - return noasync in_stream.readIntLittle(u64); - } else { - unreachable; - } -} - -// TODO the noasyncs here are workarounds -fn parseFormValueRef(allocator: *mem.Allocator, in_stream: var, size: i32) !FormValue { - return FormValue{ - .Ref = switch (size) { - 1 => try noasync in_stream.readIntLittle(u8), - 2 => try noasync in_stream.readIntLittle(u16), - 4 => try noasync in_stream.readIntLittle(u32), - 8 => try noasync in_stream.readIntLittle(u64), - -1 => try noasync leb.readULEB128(u64, in_stream), - else => unreachable, - }, - }; -} - -// TODO the noasyncs here are workarounds -fn parseFormValue(allocator: *mem.Allocator, in_stream: var, form_id: u64, is_64: bool) anyerror!FormValue { - return switch (form_id) { - DW.FORM_addr => FormValue{ .Address = try parseFormValueTargetAddrSize(in_stream) }, - DW.FORM_block1 => parseFormValueBlock(allocator, in_stream, 1), - DW.FORM_block2 => parseFormValueBlock(allocator, in_stream, 2), - DW.FORM_block4 => parseFormValueBlock(allocator, in_stream, 4), - DW.FORM_block => x: { - const block_len = try noasync leb.readULEB128(usize, in_stream); - return parseFormValueBlockLen(allocator, in_stream, block_len); - }, - DW.FORM_data1 => parseFormValueConstant(allocator, in_stream, false, 1), - DW.FORM_data2 => parseFormValueConstant(allocator, in_stream, false, 2), - DW.FORM_data4 => parseFormValueConstant(allocator, in_stream, false, 4), - DW.FORM_data8 => parseFormValueConstant(allocator, in_stream, false, 8), - DW.FORM_udata, DW.FORM_sdata => { - const signed = form_id == DW.FORM_sdata; - return parseFormValueConstant(allocator, in_stream, signed, -1); - }, - DW.FORM_exprloc => { - const size = try noasync leb.readULEB128(usize, in_stream); - const buf = try readAllocBytes(allocator, in_stream, size); - return FormValue{ .ExprLoc = buf }; - }, - DW.FORM_flag => FormValue{ .Flag = (try noasync in_stream.readByte()) != 0 }, - DW.FORM_flag_present => FormValue{ .Flag = true }, - DW.FORM_sec_offset => FormValue{ .SecOffset = try parseFormValueDwarfOffsetSize(in_stream, is_64) }, - - DW.FORM_ref1 => parseFormValueRef(allocator, in_stream, 1), - DW.FORM_ref2 => parseFormValueRef(allocator, in_stream, 2), - DW.FORM_ref4 => parseFormValueRef(allocator, in_stream, 4), - DW.FORM_ref8 => parseFormValueRef(allocator, in_stream, 8), - DW.FORM_ref_udata => parseFormValueRef(allocator, in_stream, -1), - - DW.FORM_ref_addr => FormValue{ .RefAddr = try parseFormValueDwarfOffsetSize(in_stream, is_64) }, - DW.FORM_ref_sig8 => FormValue{ .Ref = try noasync in_stream.readIntLittle(u64) }, - - DW.FORM_string => FormValue{ .String = try readStringRaw(allocator, in_stream) }, - DW.FORM_strp => FormValue{ .StrPtr = try parseFormValueDwarfOffsetSize(in_stream, is_64) }, - DW.FORM_indirect => { - const child_form_id = try noasync leb.readULEB128(u64, in_stream); - const F = @TypeOf(async parseFormValue(allocator, in_stream, child_form_id, is_64)); - var frame = try allocator.create(F); - defer allocator.destroy(frame); - return await @asyncCall(frame, {}, parseFormValue, allocator, in_stream, child_form_id, is_64); - }, - else => error.InvalidDebugInfo, - }; -} - -fn getAbbrevTableEntry(abbrev_table: *const AbbrevTable, abbrev_code: u64) ?*const AbbrevTableEntry { - for (abbrev_table.toSliceConst()) |*table_entry| { - if (table_entry.abbrev_code == abbrev_code) return table_entry; - } - return null; -} - -/// TODO resources https://github.com/ziglang/zig/issues/4353 -fn getLineNumberInfoMacOs(di: *DebugInfo, symbol: MachoSymbol, address: usize) !LineInfo { - const ofile = symbol.ofile orelse return error.MissingDebugInfo; - const gop = try di.ofiles.getOrPut(ofile); - const dwarf_info = if (gop.found_existing) &gop.kv.value else blk: { - errdefer _ = di.ofiles.remove(ofile); - const ofile_path = mem.toSliceConst(u8, @ptrCast([*:0]const u8, di.strings.ptr + ofile.n_strx)); - - var exe_file = try std.fs.openFileAbsoluteC(ofile_path, .{}); - errdefer exe_file.close(); - - const exe_len = math.cast(usize, try exe_file.getEndPos()) catch - return error.DebugInfoTooLarge; - const exe_mmap = try os.mmap( - null, - exe_len, - os.PROT_READ, - os.MAP_SHARED, - exe_file.handle, - 0, - ); - errdefer os.munmap(exe_mmap); - - const hdr = @ptrCast( - *const macho.mach_header_64, - @alignCast(@alignOf(macho.mach_header_64), exe_mmap.ptr), - ); - if (hdr.magic != std.macho.MH_MAGIC_64) return error.InvalidDebugInfo; - - const hdr_base = @ptrCast([*]const u8, hdr); - var ptr = hdr_base + @sizeOf(macho.mach_header_64); - var ncmd: u32 = hdr.ncmds; - const segcmd = while (ncmd != 0) : (ncmd -= 1) { - const lc = @ptrCast(*const std.macho.load_command, ptr); - switch (lc.cmd) { - std.macho.LC_SEGMENT_64 => { - break @ptrCast( - *const std.macho.segment_command_64, - @alignCast(@alignOf(std.macho.segment_command_64), ptr), - ); - }, - else => {}, + else => return err, } - ptr = @alignCast(@alignOf(std.macho.load_command), ptr + lc.cmdsize); - } else { - return error.MissingDebugInfo; - }; - var opt_debug_line: ?*const macho.section_64 = null; - var opt_debug_info: ?*const macho.section_64 = null; - var opt_debug_abbrev: ?*const macho.section_64 = null; - var opt_debug_str: ?*const macho.section_64 = null; - var opt_debug_ranges: ?*const macho.section_64 = null; - - const sections = @ptrCast([*]const macho.section_64, @alignCast(@alignOf(macho.section_64), ptr + @sizeOf(std.macho.segment_command_64)))[0..segcmd.nsects]; - for (sections) |*sect| { - // The section name may not exceed 16 chars and a trailing null may - // not be present - const name = if (mem.indexOfScalar(u8, sect.sectname[0..], 0)) |last| - sect.sectname[0..last] - else - sect.sectname[0..]; - - if (mem.eql(u8, name, "__debug_line")) { - opt_debug_line = sect; - } else if (mem.eql(u8, name, "__debug_info")) { - opt_debug_info = sect; - } else if (mem.eql(u8, name, "__debug_abbrev")) { - opt_debug_abbrev = sect; - } else if (mem.eql(u8, name, "__debug_str")) { - opt_debug_str = sect; - } else if (mem.eql(u8, name, "__debug_ranges")) { - opt_debug_ranges = sect; - } + unreachable; } - - var debug_line = opt_debug_line orelse - return error.MissingDebugInfo; - var debug_info = opt_debug_info orelse - return error.MissingDebugInfo; - var debug_str = opt_debug_str orelse - return error.MissingDebugInfo; - var debug_abbrev = opt_debug_abbrev orelse - return error.MissingDebugInfo; - - gop.kv.value = DwarfInfo{ - .endian = .Little, - .debug_info = exe_mmap[@intCast(usize, debug_info.offset)..@intCast(usize, debug_info.offset + debug_info.size)], - .debug_abbrev = exe_mmap[@intCast(usize, debug_abbrev.offset)..@intCast(usize, debug_abbrev.offset + debug_abbrev.size)], - .debug_str = exe_mmap[@intCast(usize, debug_str.offset)..@intCast(usize, debug_str.offset + debug_str.size)], - .debug_line = exe_mmap[@intCast(usize, debug_line.offset)..@intCast(usize, debug_line.offset + debug_line.size)], - .debug_ranges = if (opt_debug_ranges) |debug_ranges| - exe_mmap[@intCast(usize, debug_ranges.offset)..@intCast(usize, debug_ranges.offset + debug_ranges.size)] - else - null, - }; - try openDwarfDebugInfo(&gop.kv.value, di.allocator()); - - break :blk &gop.kv.value; - }; - - const o_file_address = address - symbol.reloc; - const compile_unit = try dwarf_info.findCompileUnit(o_file_address); - return dwarf_info.getLineNumberInfo(compile_unit.*, o_file_address); -} - -const Func = struct { - pc_range: ?PcRange, - name: ?[]u8, + }, + else => DW.DwarfInfo, }; -fn readInitialLength(comptime E: type, in_stream: *io.InStream(E), is_64: *bool) !u64 { - const first_32_bits = try in_stream.readIntLittle(u32); - is_64.* = (first_32_bits == 0xffffffff); - if (is_64.*) { - return in_stream.readIntLittle(u64); - } else { - if (first_32_bits >= 0xfffffff0) return error.InvalidDebugInfo; - // TODO this cast should not be needed - return @as(u64, first_32_bits); - } -} - /// TODO multithreaded awareness var debug_info_allocator: ?*mem.Allocator = null; var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined; @@ -2225,7 +1602,7 @@ fn getDebugInfoAllocator() *mem.Allocator { } /// Whether or not the current target can print useful debug information when a segfault occurs. -pub const have_segfault_handling_support = builtin.os == .linux or builtin.os == .windows; +pub const have_segfault_handling_support = builtin.os.tag == .linux or builtin.os.tag == .windows; pub const enable_segfault_handler: bool = if (@hasDecl(root, "enable_segfault_handler")) root.enable_segfault_handler else @@ -2244,7 +1621,7 @@ pub fn attachSegfaultHandler() void { if (!have_segfault_handling_support) { @compileError("segfault handler not supported for this target"); } - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { windows_segfault_handle = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows); return; } @@ -2260,7 +1637,7 @@ pub fn attachSegfaultHandler() void { } fn resetSegfaultHandler() void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { if (windows_segfault_handle) |handle| { assert(windows.kernel32.RemoveVectoredExceptionHandler(handle) != 0); windows_segfault_handle = null; diff --git a/lib/std/dwarf.zig b/lib/std/dwarf.zig index 2f3b29302d..32a49b68e0 100644 --- a/lib/std/dwarf.zig +++ b/lib/std/dwarf.zig @@ -1,682 +1,891 @@ -pub const TAG_padding = 0x00; -pub const TAG_array_type = 0x01; -pub const TAG_class_type = 0x02; -pub const TAG_entry_point = 0x03; -pub const TAG_enumeration_type = 0x04; -pub const TAG_formal_parameter = 0x05; -pub const TAG_imported_declaration = 0x08; -pub const TAG_label = 0x0a; -pub const TAG_lexical_block = 0x0b; -pub const TAG_member = 0x0d; -pub const TAG_pointer_type = 0x0f; -pub const TAG_reference_type = 0x10; -pub const TAG_compile_unit = 0x11; -pub const TAG_string_type = 0x12; -pub const TAG_structure_type = 0x13; -pub const TAG_subroutine = 0x14; -pub const TAG_subroutine_type = 0x15; -pub const TAG_typedef = 0x16; -pub const TAG_union_type = 0x17; -pub const TAG_unspecified_parameters = 0x18; -pub const TAG_variant = 0x19; -pub const TAG_common_block = 0x1a; -pub const TAG_common_inclusion = 0x1b; -pub const TAG_inheritance = 0x1c; -pub const TAG_inlined_subroutine = 0x1d; -pub const TAG_module = 0x1e; -pub const TAG_ptr_to_member_type = 0x1f; -pub const TAG_set_type = 0x20; -pub const TAG_subrange_type = 0x21; -pub const TAG_with_stmt = 0x22; -pub const TAG_access_declaration = 0x23; -pub const TAG_base_type = 0x24; -pub const TAG_catch_block = 0x25; -pub const TAG_const_type = 0x26; -pub const TAG_constant = 0x27; -pub const TAG_enumerator = 0x28; -pub const TAG_file_type = 0x29; -pub const TAG_friend = 0x2a; -pub const TAG_namelist = 0x2b; -pub const TAG_namelist_item = 0x2c; -pub const TAG_packed_type = 0x2d; -pub const TAG_subprogram = 0x2e; -pub const TAG_template_type_param = 0x2f; -pub const TAG_template_value_param = 0x30; -pub const TAG_thrown_type = 0x31; -pub const TAG_try_block = 0x32; -pub const TAG_variant_part = 0x33; -pub const TAG_variable = 0x34; -pub const TAG_volatile_type = 0x35; +const std = @import("std.zig"); +const builtin = @import("builtin"); +const debug = std.debug; +const fs = std.fs; +const io = std.io; +const mem = std.mem; +const math = std.math; +const leb = @import("debug/leb128.zig"); -// DWARF 3 -pub const TAG_dwarf_procedure = 0x36; -pub const TAG_restrict_type = 0x37; -pub const TAG_interface_type = 0x38; -pub const TAG_namespace = 0x39; -pub const TAG_imported_module = 0x3a; -pub const TAG_unspecified_type = 0x3b; -pub const TAG_partial_unit = 0x3c; -pub const TAG_imported_unit = 0x3d; -pub const TAG_condition = 0x3f; -pub const TAG_shared_type = 0x40; +const ArrayList = std.ArrayList; -// DWARF 4 -pub const TAG_type_unit = 0x41; -pub const TAG_rvalue_reference_type = 0x42; -pub const TAG_template_alias = 0x43; +usingnamespace @import("dwarf_bits.zig"); -pub const TAG_lo_user = 0x4080; -pub const TAG_hi_user = 0xffff; +pub const DwarfSeekableStream = io.SeekableStream(anyerror, anyerror); +pub const DwarfInStream = io.InStream(anyerror); -// SGI/MIPS Extensions. -pub const DW_TAG_MIPS_loop = 0x4081; +const PcRange = struct { + start: u64, + end: u64, +}; -// HP extensions. See: ftp://ftp.hp.com/pub/lang/tools/WDB/wdb-4.0.tar.gz . -pub const TAG_HP_array_descriptor = 0x4090; -pub const TAG_HP_Bliss_field = 0x4091; -pub const TAG_HP_Bliss_field_set = 0x4092; +const Func = struct { + pc_range: ?PcRange, + name: ?[]const u8, +}; -// GNU extensions. -pub const TAG_format_label = 0x4101; // For FORTRAN 77 and Fortran 90. -pub const TAG_function_template = 0x4102; // For C++. -pub const TAG_class_template = 0x4103; //For C++. -pub const TAG_GNU_BINCL = 0x4104; -pub const TAG_GNU_EINCL = 0x4105; +const CompileUnit = struct { + version: u16, + is_64: bool, + die: *Die, + pc_range: ?PcRange, +}; -// Template template parameter. -// See http://gcc.gnu.org/wiki/TemplateParmsDwarf . -pub const TAG_GNU_template_template_param = 0x4106; +const AbbrevTable = ArrayList(AbbrevTableEntry); -// Template parameter pack extension = specified at -// http://wiki.dwarfstd.org/index.php?title=C%2B%2B0x:_Variadic_templates -// The values of these two TAGS are in the DW_TAG_GNU_* space until the tags -// are properly part of DWARF 5. -pub const TAG_GNU_template_parameter_pack = 0x4107; -pub const TAG_GNU_formal_parameter_pack = 0x4108; -// The GNU call site extension = specified at -// http://www.dwarfstd.org/ShowIssue.php?issue=100909.2&type=open . -// The values of these two TAGS are in the DW_TAG_GNU_* space until the tags -// are properly part of DWARF 5. -pub const TAG_GNU_call_site = 0x4109; -pub const TAG_GNU_call_site_parameter = 0x410a; -// Extensions for UPC. See: http://dwarfstd.org/doc/DWARF4.pdf. -pub const TAG_upc_shared_type = 0x8765; -pub const TAG_upc_strict_type = 0x8766; -pub const TAG_upc_relaxed_type = 0x8767; -// PGI (STMicroelectronics; extensions. No documentation available. -pub const TAG_PGI_kanji_type = 0xA000; -pub const TAG_PGI_interface_block = 0xA020; +const AbbrevTableHeader = struct { + // offset from .debug_abbrev + offset: u64, + table: AbbrevTable, +}; -pub const FORM_addr = 0x01; -pub const FORM_block2 = 0x03; -pub const FORM_block4 = 0x04; -pub const FORM_data2 = 0x05; -pub const FORM_data4 = 0x06; -pub const FORM_data8 = 0x07; -pub const FORM_string = 0x08; -pub const FORM_block = 0x09; -pub const FORM_block1 = 0x0a; -pub const FORM_data1 = 0x0b; -pub const FORM_flag = 0x0c; -pub const FORM_sdata = 0x0d; -pub const FORM_strp = 0x0e; -pub const FORM_udata = 0x0f; -pub const FORM_ref_addr = 0x10; -pub const FORM_ref1 = 0x11; -pub const FORM_ref2 = 0x12; -pub const FORM_ref4 = 0x13; -pub const FORM_ref8 = 0x14; -pub const FORM_ref_udata = 0x15; -pub const FORM_indirect = 0x16; -pub const FORM_sec_offset = 0x17; -pub const FORM_exprloc = 0x18; -pub const FORM_flag_present = 0x19; -pub const FORM_ref_sig8 = 0x20; +const AbbrevTableEntry = struct { + has_children: bool, + abbrev_code: u64, + tag_id: u64, + attrs: ArrayList(AbbrevAttr), +}; -// Extensions for Fission. See http://gcc.gnu.org/wiki/DebugFission. -pub const FORM_GNU_addr_index = 0x1f01; -pub const FORM_GNU_str_index = 0x1f02; +const AbbrevAttr = struct { + attr_id: u64, + form_id: u64, +}; -// Extensions for DWZ multifile. -// See http://www.dwarfstd.org/ShowIssue.php?issue=120604.1&type=open . -pub const FORM_GNU_ref_alt = 0x1f20; -pub const FORM_GNU_strp_alt = 0x1f21; +const FormValue = union(enum) { + Address: u64, + Block: []u8, + Const: Constant, + ExprLoc: []u8, + Flag: bool, + SecOffset: u64, + Ref: u64, + RefAddr: u64, + String: []const u8, + StrPtr: u64, +}; -pub const AT_sibling = 0x01; -pub const AT_location = 0x02; -pub const AT_name = 0x03; -pub const AT_ordering = 0x09; -pub const AT_subscr_data = 0x0a; -pub const AT_byte_size = 0x0b; -pub const AT_bit_offset = 0x0c; -pub const AT_bit_size = 0x0d; -pub const AT_element_list = 0x0f; -pub const AT_stmt_list = 0x10; -pub const AT_low_pc = 0x11; -pub const AT_high_pc = 0x12; -pub const AT_language = 0x13; -pub const AT_member = 0x14; -pub const AT_discr = 0x15; -pub const AT_discr_value = 0x16; -pub const AT_visibility = 0x17; -pub const AT_import = 0x18; -pub const AT_string_length = 0x19; -pub const AT_common_reference = 0x1a; -pub const AT_comp_dir = 0x1b; -pub const AT_const_value = 0x1c; -pub const AT_containing_type = 0x1d; -pub const AT_default_value = 0x1e; -pub const AT_inline = 0x20; -pub const AT_is_optional = 0x21; -pub const AT_lower_bound = 0x22; -pub const AT_producer = 0x25; -pub const AT_prototyped = 0x27; -pub const AT_return_addr = 0x2a; -pub const AT_start_scope = 0x2c; -pub const AT_bit_stride = 0x2e; -pub const AT_upper_bound = 0x2f; -pub const AT_abstract_origin = 0x31; -pub const AT_accessibility = 0x32; -pub const AT_address_class = 0x33; -pub const AT_artificial = 0x34; -pub const AT_base_types = 0x35; -pub const AT_calling_convention = 0x36; -pub const AT_count = 0x37; -pub const AT_data_member_location = 0x38; -pub const AT_decl_column = 0x39; -pub const AT_decl_file = 0x3a; -pub const AT_decl_line = 0x3b; -pub const AT_declaration = 0x3c; -pub const AT_discr_list = 0x3d; -pub const AT_encoding = 0x3e; -pub const AT_external = 0x3f; -pub const AT_frame_base = 0x40; -pub const AT_friend = 0x41; -pub const AT_identifier_case = 0x42; -pub const AT_macro_info = 0x43; -pub const AT_namelist_items = 0x44; -pub const AT_priority = 0x45; -pub const AT_segment = 0x46; -pub const AT_specification = 0x47; -pub const AT_static_link = 0x48; -pub const AT_type = 0x49; -pub const AT_use_location = 0x4a; -pub const AT_variable_parameter = 0x4b; -pub const AT_virtuality = 0x4c; -pub const AT_vtable_elem_location = 0x4d; +const Constant = struct { + payload: u64, + signed: bool, -// DWARF 3 values. -pub const AT_allocated = 0x4e; -pub const AT_associated = 0x4f; -pub const AT_data_location = 0x50; -pub const AT_byte_stride = 0x51; -pub const AT_entry_pc = 0x52; -pub const AT_use_UTF8 = 0x53; -pub const AT_extension = 0x54; -pub const AT_ranges = 0x55; -pub const AT_trampoline = 0x56; -pub const AT_call_column = 0x57; -pub const AT_call_file = 0x58; -pub const AT_call_line = 0x59; -pub const AT_description = 0x5a; -pub const AT_binary_scale = 0x5b; -pub const AT_decimal_scale = 0x5c; -pub const AT_small = 0x5d; -pub const AT_decimal_sign = 0x5e; -pub const AT_digit_count = 0x5f; -pub const AT_picture_string = 0x60; -pub const AT_mutable = 0x61; -pub const AT_threads_scaled = 0x62; -pub const AT_explicit = 0x63; -pub const AT_object_pointer = 0x64; -pub const AT_endianity = 0x65; -pub const AT_elemental = 0x66; -pub const AT_pure = 0x67; -pub const AT_recursive = 0x68; + fn asUnsignedLe(self: *const Constant) !u64 { + if (self.signed) return error.InvalidDebugInfo; + return self.payload; + } +}; -// DWARF 4. -pub const AT_signature = 0x69; -pub const AT_main_subprogram = 0x6a; -pub const AT_data_bit_offset = 0x6b; -pub const AT_const_expr = 0x6c; -pub const AT_enum_class = 0x6d; -pub const AT_linkage_name = 0x6e; +const Die = struct { + tag_id: u64, + has_children: bool, + attrs: ArrayList(Attr), -// DWARF 5 -pub const AT_alignment = 0x88; + const Attr = struct { + id: u64, + value: FormValue, + }; -pub const AT_lo_user = 0x2000; // Implementation-defined range start. -pub const AT_hi_user = 0x3fff; // Implementation-defined range end. + fn getAttr(self: *const Die, id: u64) ?*const FormValue { + for (self.attrs.toSliceConst()) |*attr| { + if (attr.id == id) return &attr.value; + } + return null; + } -// SGI/MIPS extensions. -pub const AT_MIPS_fde = 0x2001; -pub const AT_MIPS_loop_begin = 0x2002; -pub const AT_MIPS_tail_loop_begin = 0x2003; -pub const AT_MIPS_epilog_begin = 0x2004; -pub const AT_MIPS_loop_unroll_factor = 0x2005; -pub const AT_MIPS_software_pipeline_depth = 0x2006; -pub const AT_MIPS_linkage_name = 0x2007; -pub const AT_MIPS_stride = 0x2008; -pub const AT_MIPS_abstract_name = 0x2009; -pub const AT_MIPS_clone_origin = 0x200a; -pub const AT_MIPS_has_inlines = 0x200b; + fn getAttrAddr(self: *const Die, id: u64) !u64 { + const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; + return switch (form_value.*) { + FormValue.Address => |value| value, + else => error.InvalidDebugInfo, + }; + } -// HP extensions. -pub const AT_HP_block_index = 0x2000; -pub const AT_HP_unmodifiable = 0x2001; // Same as DW_AT_MIPS_fde. -pub const AT_HP_prologue = 0x2005; // Same as DW_AT_MIPS_loop_unroll. -pub const AT_HP_epilogue = 0x2008; // Same as DW_AT_MIPS_stride. -pub const AT_HP_actuals_stmt_list = 0x2010; -pub const AT_HP_proc_per_section = 0x2011; -pub const AT_HP_raw_data_ptr = 0x2012; -pub const AT_HP_pass_by_reference = 0x2013; -pub const AT_HP_opt_level = 0x2014; -pub const AT_HP_prof_version_id = 0x2015; -pub const AT_HP_opt_flags = 0x2016; -pub const AT_HP_cold_region_low_pc = 0x2017; -pub const AT_HP_cold_region_high_pc = 0x2018; -pub const AT_HP_all_variables_modifiable = 0x2019; -pub const AT_HP_linkage_name = 0x201a; -pub const AT_HP_prof_flags = 0x201b; // In comp unit of procs_info for -g. -pub const AT_HP_unit_name = 0x201f; -pub const AT_HP_unit_size = 0x2020; -pub const AT_HP_widened_byte_size = 0x2021; -pub const AT_HP_definition_points = 0x2022; -pub const AT_HP_default_location = 0x2023; -pub const AT_HP_is_result_param = 0x2029; + fn getAttrSecOffset(self: *const Die, id: u64) !u64 { + const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; + return switch (form_value.*) { + FormValue.Const => |value| value.asUnsignedLe(), + FormValue.SecOffset => |value| value, + else => error.InvalidDebugInfo, + }; + } -// GNU extensions. -pub const AT_sf_names = 0x2101; -pub const AT_src_info = 0x2102; -pub const AT_mac_info = 0x2103; -pub const AT_src_coords = 0x2104; -pub const AT_body_begin = 0x2105; -pub const AT_body_end = 0x2106; -pub const AT_GNU_vector = 0x2107; -// Thread-safety annotations. -// See http://gcc.gnu.org/wiki/ThreadSafetyAnnotation . -pub const AT_GNU_guarded_by = 0x2108; -pub const AT_GNU_pt_guarded_by = 0x2109; -pub const AT_GNU_guarded = 0x210a; -pub const AT_GNU_pt_guarded = 0x210b; -pub const AT_GNU_locks_excluded = 0x210c; -pub const AT_GNU_exclusive_locks_required = 0x210d; -pub const AT_GNU_shared_locks_required = 0x210e; -// One-definition rule violation detection. -// See http://gcc.gnu.org/wiki/DwarfSeparateTypeInfo . -pub const AT_GNU_odr_signature = 0x210f; -// Template template argument name. -// See http://gcc.gnu.org/wiki/TemplateParmsDwarf . -pub const AT_GNU_template_name = 0x2110; -// The GNU call site extension. -// See http://www.dwarfstd.org/ShowIssue.php?issue=100909.2&type=open . -pub const AT_GNU_call_site_value = 0x2111; -pub const AT_GNU_call_site_data_value = 0x2112; -pub const AT_GNU_call_site_target = 0x2113; -pub const AT_GNU_call_site_target_clobbered = 0x2114; -pub const AT_GNU_tail_call = 0x2115; -pub const AT_GNU_all_tail_call_sites = 0x2116; -pub const AT_GNU_all_call_sites = 0x2117; -pub const AT_GNU_all_source_call_sites = 0x2118; -// Section offset into .debug_macro section. -pub const AT_GNU_macros = 0x2119; -// Extensions for Fission. See http://gcc.gnu.org/wiki/DebugFission. -pub const AT_GNU_dwo_name = 0x2130; -pub const AT_GNU_dwo_id = 0x2131; -pub const AT_GNU_ranges_base = 0x2132; -pub const AT_GNU_addr_base = 0x2133; -pub const AT_GNU_pubnames = 0x2134; -pub const AT_GNU_pubtypes = 0x2135; -// VMS extensions. -pub const AT_VMS_rtnbeg_pd_address = 0x2201; -// GNAT extensions. -// GNAT descriptive type. -// See http://gcc.gnu.org/wiki/DW_AT_GNAT_descriptive_type . -pub const AT_use_GNAT_descriptive_type = 0x2301; -pub const AT_GNAT_descriptive_type = 0x2302; -// UPC extension. -pub const AT_upc_threads_scaled = 0x3210; -// PGI (STMicroelectronics) extensions. -pub const AT_PGI_lbase = 0x3a00; -pub const AT_PGI_soffset = 0x3a01; -pub const AT_PGI_lstride = 0x3a02; + fn getAttrUnsignedLe(self: *const Die, id: u64) !u64 { + const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; + return switch (form_value.*) { + FormValue.Const => |value| value.asUnsignedLe(), + else => error.InvalidDebugInfo, + }; + } -pub const OP_addr = 0x03; -pub const OP_deref = 0x06; -pub const OP_const1u = 0x08; -pub const OP_const1s = 0x09; -pub const OP_const2u = 0x0a; -pub const OP_const2s = 0x0b; -pub const OP_const4u = 0x0c; -pub const OP_const4s = 0x0d; -pub const OP_const8u = 0x0e; -pub const OP_const8s = 0x0f; -pub const OP_constu = 0x10; -pub const OP_consts = 0x11; -pub const OP_dup = 0x12; -pub const OP_drop = 0x13; -pub const OP_over = 0x14; -pub const OP_pick = 0x15; -pub const OP_swap = 0x16; -pub const OP_rot = 0x17; -pub const OP_xderef = 0x18; -pub const OP_abs = 0x19; -pub const OP_and = 0x1a; -pub const OP_div = 0x1b; -pub const OP_minus = 0x1c; -pub const OP_mod = 0x1d; -pub const OP_mul = 0x1e; -pub const OP_neg = 0x1f; -pub const OP_not = 0x20; -pub const OP_or = 0x21; -pub const OP_plus = 0x22; -pub const OP_plus_uconst = 0x23; -pub const OP_shl = 0x24; -pub const OP_shr = 0x25; -pub const OP_shra = 0x26; -pub const OP_xor = 0x27; -pub const OP_bra = 0x28; -pub const OP_eq = 0x29; -pub const OP_ge = 0x2a; -pub const OP_gt = 0x2b; -pub const OP_le = 0x2c; -pub const OP_lt = 0x2d; -pub const OP_ne = 0x2e; -pub const OP_skip = 0x2f; -pub const OP_lit0 = 0x30; -pub const OP_lit1 = 0x31; -pub const OP_lit2 = 0x32; -pub const OP_lit3 = 0x33; -pub const OP_lit4 = 0x34; -pub const OP_lit5 = 0x35; -pub const OP_lit6 = 0x36; -pub const OP_lit7 = 0x37; -pub const OP_lit8 = 0x38; -pub const OP_lit9 = 0x39; -pub const OP_lit10 = 0x3a; -pub const OP_lit11 = 0x3b; -pub const OP_lit12 = 0x3c; -pub const OP_lit13 = 0x3d; -pub const OP_lit14 = 0x3e; -pub const OP_lit15 = 0x3f; -pub const OP_lit16 = 0x40; -pub const OP_lit17 = 0x41; -pub const OP_lit18 = 0x42; -pub const OP_lit19 = 0x43; -pub const OP_lit20 = 0x44; -pub const OP_lit21 = 0x45; -pub const OP_lit22 = 0x46; -pub const OP_lit23 = 0x47; -pub const OP_lit24 = 0x48; -pub const OP_lit25 = 0x49; -pub const OP_lit26 = 0x4a; -pub const OP_lit27 = 0x4b; -pub const OP_lit28 = 0x4c; -pub const OP_lit29 = 0x4d; -pub const OP_lit30 = 0x4e; -pub const OP_lit31 = 0x4f; -pub const OP_reg0 = 0x50; -pub const OP_reg1 = 0x51; -pub const OP_reg2 = 0x52; -pub const OP_reg3 = 0x53; -pub const OP_reg4 = 0x54; -pub const OP_reg5 = 0x55; -pub const OP_reg6 = 0x56; -pub const OP_reg7 = 0x57; -pub const OP_reg8 = 0x58; -pub const OP_reg9 = 0x59; -pub const OP_reg10 = 0x5a; -pub const OP_reg11 = 0x5b; -pub const OP_reg12 = 0x5c; -pub const OP_reg13 = 0x5d; -pub const OP_reg14 = 0x5e; -pub const OP_reg15 = 0x5f; -pub const OP_reg16 = 0x60; -pub const OP_reg17 = 0x61; -pub const OP_reg18 = 0x62; -pub const OP_reg19 = 0x63; -pub const OP_reg20 = 0x64; -pub const OP_reg21 = 0x65; -pub const OP_reg22 = 0x66; -pub const OP_reg23 = 0x67; -pub const OP_reg24 = 0x68; -pub const OP_reg25 = 0x69; -pub const OP_reg26 = 0x6a; -pub const OP_reg27 = 0x6b; -pub const OP_reg28 = 0x6c; -pub const OP_reg29 = 0x6d; -pub const OP_reg30 = 0x6e; -pub const OP_reg31 = 0x6f; -pub const OP_breg0 = 0x70; -pub const OP_breg1 = 0x71; -pub const OP_breg2 = 0x72; -pub const OP_breg3 = 0x73; -pub const OP_breg4 = 0x74; -pub const OP_breg5 = 0x75; -pub const OP_breg6 = 0x76; -pub const OP_breg7 = 0x77; -pub const OP_breg8 = 0x78; -pub const OP_breg9 = 0x79; -pub const OP_breg10 = 0x7a; -pub const OP_breg11 = 0x7b; -pub const OP_breg12 = 0x7c; -pub const OP_breg13 = 0x7d; -pub const OP_breg14 = 0x7e; -pub const OP_breg15 = 0x7f; -pub const OP_breg16 = 0x80; -pub const OP_breg17 = 0x81; -pub const OP_breg18 = 0x82; -pub const OP_breg19 = 0x83; -pub const OP_breg20 = 0x84; -pub const OP_breg21 = 0x85; -pub const OP_breg22 = 0x86; -pub const OP_breg23 = 0x87; -pub const OP_breg24 = 0x88; -pub const OP_breg25 = 0x89; -pub const OP_breg26 = 0x8a; -pub const OP_breg27 = 0x8b; -pub const OP_breg28 = 0x8c; -pub const OP_breg29 = 0x8d; -pub const OP_breg30 = 0x8e; -pub const OP_breg31 = 0x8f; -pub const OP_regx = 0x90; -pub const OP_fbreg = 0x91; -pub const OP_bregx = 0x92; -pub const OP_piece = 0x93; -pub const OP_deref_size = 0x94; -pub const OP_xderef_size = 0x95; -pub const OP_nop = 0x96; + fn getAttrRef(self: *const Die, id: u64) !u64 { + const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; + return switch (form_value.*) { + FormValue.Ref => |value| value, + else => error.InvalidDebugInfo, + }; + } -// DWARF 3 extensions. -pub const OP_push_object_address = 0x97; -pub const OP_call2 = 0x98; -pub const OP_call4 = 0x99; -pub const OP_call_ref = 0x9a; -pub const OP_form_tls_address = 0x9b; -pub const OP_call_frame_cfa = 0x9c; -pub const OP_bit_piece = 0x9d; + fn getAttrString(self: *const Die, di: *DwarfInfo, id: u64) ![]const u8 { + const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; + return switch (form_value.*) { + FormValue.String => |value| value, + FormValue.StrPtr => |offset| di.getString(offset), + else => error.InvalidDebugInfo, + }; + } +}; -// DWARF 4 extensions. -pub const OP_implicit_value = 0x9e; -pub const OP_stack_value = 0x9f; +const FileEntry = struct { + file_name: []const u8, + dir_index: usize, + mtime: usize, + len_bytes: usize, +}; -pub const OP_lo_user = 0xe0; // Implementation-defined range start. -pub const OP_hi_user = 0xff; // Implementation-defined range end. +const LineNumberProgram = struct { + address: usize, + file: usize, + line: i64, + column: u64, + is_stmt: bool, + basic_block: bool, + end_sequence: bool, -// GNU extensions. -pub const OP_GNU_push_tls_address = 0xe0; -// The following is for marking variables that are uninitialized. -pub const OP_GNU_uninit = 0xf0; -pub const OP_GNU_encoded_addr = 0xf1; -// The GNU implicit pointer extension. -// See http://www.dwarfstd.org/ShowIssue.php?issue=100831.1&type=open . -pub const OP_GNU_implicit_pointer = 0xf2; -// The GNU entry value extension. -// See http://www.dwarfstd.org/ShowIssue.php?issue=100909.1&type=open . -pub const OP_GNU_entry_value = 0xf3; -// The GNU typed stack extension. -// See http://www.dwarfstd.org/doc/040408.1.html . -pub const OP_GNU_const_type = 0xf4; -pub const OP_GNU_regval_type = 0xf5; -pub const OP_GNU_deref_type = 0xf6; -pub const OP_GNU_convert = 0xf7; -pub const OP_GNU_reinterpret = 0xf9; -// The GNU parameter ref extension. -pub const OP_GNU_parameter_ref = 0xfa; -// Extension for Fission. See http://gcc.gnu.org/wiki/DebugFission. -pub const OP_GNU_addr_index = 0xfb; -pub const OP_GNU_const_index = 0xfc; -// HP extensions. -pub const OP_HP_unknown = 0xe0; // Ouch, the same as GNU_push_tls_address. -pub const OP_HP_is_value = 0xe1; -pub const OP_HP_fltconst4 = 0xe2; -pub const OP_HP_fltconst8 = 0xe3; -pub const OP_HP_mod_range = 0xe4; -pub const OP_HP_unmod_range = 0xe5; -pub const OP_HP_tls = 0xe6; -// PGI (STMicroelectronics) extensions. -pub const OP_PGI_omp_thread_num = 0xf8; + default_is_stmt: bool, + target_address: usize, + include_dirs: []const []const u8, + file_entries: *ArrayList(FileEntry), -pub const ATE_void = 0x0; -pub const ATE_address = 0x1; -pub const ATE_boolean = 0x2; -pub const ATE_complex_float = 0x3; -pub const ATE_float = 0x4; -pub const ATE_signed = 0x5; -pub const ATE_signed_char = 0x6; -pub const ATE_unsigned = 0x7; -pub const ATE_unsigned_char = 0x8; + prev_address: usize, + prev_file: usize, + prev_line: i64, + prev_column: u64, + prev_is_stmt: bool, + prev_basic_block: bool, + prev_end_sequence: bool, -// DWARF 3. -pub const ATE_imaginary_float = 0x9; -pub const ATE_packed_decimal = 0xa; -pub const ATE_numeric_string = 0xb; -pub const ATE_edited = 0xc; -pub const ATE_signed_fixed = 0xd; -pub const ATE_unsigned_fixed = 0xe; -pub const ATE_decimal_float = 0xf; + // Reset the state machine following the DWARF specification + pub fn reset(self: *LineNumberProgram) void { + self.address = 0; + self.file = 1; + self.line = 1; + self.column = 0; + self.is_stmt = self.default_is_stmt; + self.basic_block = false; + self.end_sequence = false; + // Invalidate all the remaining fields + self.prev_address = 0; + self.prev_file = undefined; + self.prev_line = undefined; + self.prev_column = undefined; + self.prev_is_stmt = undefined; + self.prev_basic_block = undefined; + self.prev_end_sequence = undefined; + } -// DWARF 4. -pub const ATE_UTF = 0x10; + pub fn init(is_stmt: bool, include_dirs: []const []const u8, file_entries: *ArrayList(FileEntry), target_address: usize) LineNumberProgram { + return LineNumberProgram{ + .address = 0, + .file = 1, + .line = 1, + .column = 0, + .is_stmt = is_stmt, + .basic_block = false, + .end_sequence = false, + .include_dirs = include_dirs, + .file_entries = file_entries, + .default_is_stmt = is_stmt, + .target_address = target_address, + .prev_address = 0, + .prev_file = undefined, + .prev_line = undefined, + .prev_column = undefined, + .prev_is_stmt = undefined, + .prev_basic_block = undefined, + .prev_end_sequence = undefined, + }; + } -pub const ATE_lo_user = 0x80; -pub const ATE_hi_user = 0xff; + pub fn checkLineMatch(self: *LineNumberProgram) !?debug.LineInfo { + if (self.target_address >= self.prev_address and self.target_address < self.address) { + const file_entry = if (self.prev_file == 0) { + return error.MissingDebugInfo; + } else if (self.prev_file - 1 >= self.file_entries.len) { + return error.InvalidDebugInfo; + } else + &self.file_entries.items[self.prev_file - 1]; -// HP extensions. -pub const ATE_HP_float80 = 0x80; // Floating-point (80 bit). -pub const ATE_HP_complex_float80 = 0x81; // Complex floating-point (80 bit). -pub const ATE_HP_float128 = 0x82; // Floating-point (128 bit). -pub const ATE_HP_complex_float128 = 0x83; // Complex fp (128 bit). -pub const ATE_HP_floathpintel = 0x84; // Floating-point (82 bit IA64). -pub const ATE_HP_imaginary_float80 = 0x85; -pub const ATE_HP_imaginary_float128 = 0x86; -pub const ATE_HP_VAX_float = 0x88; // F or G floating. -pub const ATE_HP_VAX_float_d = 0x89; // D floating. -pub const ATE_HP_packed_decimal = 0x8a; // Cobol. -pub const ATE_HP_zoned_decimal = 0x8b; // Cobol. -pub const ATE_HP_edited = 0x8c; // Cobol. -pub const ATE_HP_signed_fixed = 0x8d; // Cobol. -pub const ATE_HP_unsigned_fixed = 0x8e; // Cobol. -pub const ATE_HP_VAX_complex_float = 0x8f; // F or G floating complex. -pub const ATE_HP_VAX_complex_float_d = 0x90; // D floating complex. + const dir_name = if (file_entry.dir_index >= self.include_dirs.len) { + return error.InvalidDebugInfo; + } else + self.include_dirs[file_entry.dir_index]; + const file_name = try fs.path.join(self.file_entries.allocator, &[_][]const u8{ dir_name, file_entry.file_name }); + errdefer self.file_entries.allocator.free(file_name); + return debug.LineInfo{ + .line = if (self.prev_line >= 0) @intCast(u64, self.prev_line) else 0, + .column = self.prev_column, + .file_name = file_name, + .allocator = self.file_entries.allocator, + }; + } -pub const CFA_advance_loc = 0x40; -pub const CFA_offset = 0x80; -pub const CFA_restore = 0xc0; -pub const CFA_nop = 0x00; -pub const CFA_set_loc = 0x01; -pub const CFA_advance_loc1 = 0x02; -pub const CFA_advance_loc2 = 0x03; -pub const CFA_advance_loc4 = 0x04; -pub const CFA_offset_extended = 0x05; -pub const CFA_restore_extended = 0x06; -pub const CFA_undefined = 0x07; -pub const CFA_same_value = 0x08; -pub const CFA_register = 0x09; -pub const CFA_remember_state = 0x0a; -pub const CFA_restore_state = 0x0b; -pub const CFA_def_cfa = 0x0c; -pub const CFA_def_cfa_register = 0x0d; -pub const CFA_def_cfa_offset = 0x0e; + self.prev_address = self.address; + self.prev_file = self.file; + self.prev_line = self.line; + self.prev_column = self.column; + self.prev_is_stmt = self.is_stmt; + self.prev_basic_block = self.basic_block; + self.prev_end_sequence = self.end_sequence; + return null; + } +}; -// DWARF 3. -pub const CFA_def_cfa_expression = 0x0f; -pub const CFA_expression = 0x10; -pub const CFA_offset_extended_sf = 0x11; -pub const CFA_def_cfa_sf = 0x12; -pub const CFA_def_cfa_offset_sf = 0x13; -pub const CFA_val_offset = 0x14; -pub const CFA_val_offset_sf = 0x15; -pub const CFA_val_expression = 0x16; +fn readInitialLength(comptime E: type, in_stream: *io.InStream(E), is_64: *bool) !u64 { + const first_32_bits = try in_stream.readIntLittle(u32); + is_64.* = (first_32_bits == 0xffffffff); + if (is_64.*) { + return in_stream.readIntLittle(u64); + } else { + if (first_32_bits >= 0xfffffff0) return error.InvalidDebugInfo; + // TODO this cast should not be needed + return @as(u64, first_32_bits); + } +} -pub const CFA_lo_user = 0x1c; -pub const CFA_hi_user = 0x3f; +// TODO the noasyncs here are workarounds +fn readAllocBytes(allocator: *mem.Allocator, in_stream: var, size: usize) ![]u8 { + const buf = try allocator.alloc(u8, size); + errdefer allocator.free(buf); + if ((try noasync in_stream.read(buf)) < size) return error.EndOfFile; + return buf; +} -// SGI/MIPS specific. -pub const CFA_MIPS_advance_loc8 = 0x1d; +fn parseFormValueBlockLen(allocator: *mem.Allocator, in_stream: var, size: usize) !FormValue { + const buf = try readAllocBytes(allocator, in_stream, size); + return FormValue{ .Block = buf }; +} -// GNU extensions. -pub const CFA_GNU_window_save = 0x2d; -pub const CFA_GNU_args_size = 0x2e; -pub const CFA_GNU_negative_offset_extended = 0x2f; +// TODO the noasyncs here are workarounds +fn parseFormValueBlock(allocator: *mem.Allocator, in_stream: var, size: usize) !FormValue { + const block_len = try noasync in_stream.readVarInt(usize, builtin.Endian.Little, size); + return parseFormValueBlockLen(allocator, in_stream, block_len); +} -pub const CHILDREN_no = 0x00; -pub const CHILDREN_yes = 0x01; +fn parseFormValueConstant(allocator: *mem.Allocator, in_stream: var, signed: bool, comptime size: i32) !FormValue { + // TODO: Please forgive me, I've worked around zig not properly spilling some intermediate values here. + // `noasync` should be removed from all the function calls once it is fixed. + return FormValue{ + .Const = Constant{ + .signed = signed, + .payload = switch (size) { + 1 => try noasync in_stream.readIntLittle(u8), + 2 => try noasync in_stream.readIntLittle(u16), + 4 => try noasync in_stream.readIntLittle(u32), + 8 => try noasync in_stream.readIntLittle(u64), + -1 => blk: { + if (signed) { + const x = try noasync leb.readILEB128(i64, in_stream); + break :blk @bitCast(u64, x); + } else { + const x = try noasync leb.readULEB128(u64, in_stream); + break :blk x; + } + }, + else => @compileError("Invalid size"), + }, + }, + }; +} -pub const LNS_extended_op = 0x00; -pub const LNS_copy = 0x01; -pub const LNS_advance_pc = 0x02; -pub const LNS_advance_line = 0x03; -pub const LNS_set_file = 0x04; -pub const LNS_set_column = 0x05; -pub const LNS_negate_stmt = 0x06; -pub const LNS_set_basic_block = 0x07; -pub const LNS_const_add_pc = 0x08; -pub const LNS_fixed_advance_pc = 0x09; -pub const LNS_set_prologue_end = 0x0a; -pub const LNS_set_epilogue_begin = 0x0b; -pub const LNS_set_isa = 0x0c; +// TODO the noasyncs here are workarounds +fn parseFormValueDwarfOffsetSize(in_stream: var, is_64: bool) !u64 { + return if (is_64) try noasync in_stream.readIntLittle(u64) else @as(u64, try noasync in_stream.readIntLittle(u32)); +} -pub const LNE_end_sequence = 0x01; -pub const LNE_set_address = 0x02; -pub const LNE_define_file = 0x03; -pub const LNE_set_discriminator = 0x04; -pub const LNE_lo_user = 0x80; -pub const LNE_hi_user = 0xff; +// TODO the noasyncs here are workarounds +fn parseFormValueTargetAddrSize(in_stream: var) !u64 { + if (@sizeOf(usize) == 4) { + // TODO this cast should not be needed + return @as(u64, try noasync in_stream.readIntLittle(u32)); + } else if (@sizeOf(usize) == 8) { + return noasync in_stream.readIntLittle(u64); + } else { + unreachable; + } +} -pub const LANG_C89 = 0x0001; -pub const LANG_C = 0x0002; -pub const LANG_Ada83 = 0x0003; -pub const LANG_C_plus_plus = 0x0004; -pub const LANG_Cobol74 = 0x0005; -pub const LANG_Cobol85 = 0x0006; -pub const LANG_Fortran77 = 0x0007; -pub const LANG_Fortran90 = 0x0008; -pub const LANG_Pascal83 = 0x0009; -pub const LANG_Modula2 = 0x000a; -pub const LANG_Java = 0x000b; -pub const LANG_C99 = 0x000c; -pub const LANG_Ada95 = 0x000d; -pub const LANG_Fortran95 = 0x000e; -pub const LANG_PLI = 0x000f; -pub const LANG_ObjC = 0x0010; -pub const LANG_ObjC_plus_plus = 0x0011; -pub const LANG_UPC = 0x0012; -pub const LANG_D = 0x0013; -pub const LANG_Python = 0x0014; -pub const LANG_Go = 0x0016; -pub const LANG_C_plus_plus_11 = 0x001a; -pub const LANG_Rust = 0x001c; -pub const LANG_C11 = 0x001d; -pub const LANG_C_plus_plus_14 = 0x0021; -pub const LANG_Fortran03 = 0x0022; -pub const LANG_Fortran08 = 0x0023; -pub const LANG_lo_user = 0x8000; -pub const LANG_hi_user = 0xffff; -pub const LANG_Mips_Assembler = 0x8001; -pub const LANG_Upc = 0x8765; -pub const LANG_HP_Bliss = 0x8003; -pub const LANG_HP_Basic91 = 0x8004; -pub const LANG_HP_Pascal91 = 0x8005; -pub const LANG_HP_IMacro = 0x8006; -pub const LANG_HP_Assembler = 0x8007; +// TODO the noasyncs here are workarounds +fn parseFormValueRef(allocator: *mem.Allocator, in_stream: var, size: i32) !FormValue { + return FormValue{ + .Ref = switch (size) { + 1 => try noasync in_stream.readIntLittle(u8), + 2 => try noasync in_stream.readIntLittle(u16), + 4 => try noasync in_stream.readIntLittle(u32), + 8 => try noasync in_stream.readIntLittle(u64), + -1 => try noasync leb.readULEB128(u64, in_stream), + else => unreachable, + }, + }; +} + +// TODO the noasyncs here are workarounds +fn parseFormValue(allocator: *mem.Allocator, in_stream: var, form_id: u64, is_64: bool) anyerror!FormValue { + return switch (form_id) { + FORM_addr => FormValue{ .Address = try parseFormValueTargetAddrSize(in_stream) }, + FORM_block1 => parseFormValueBlock(allocator, in_stream, 1), + FORM_block2 => parseFormValueBlock(allocator, in_stream, 2), + FORM_block4 => parseFormValueBlock(allocator, in_stream, 4), + FORM_block => x: { + const block_len = try noasync leb.readULEB128(usize, in_stream); + return parseFormValueBlockLen(allocator, in_stream, block_len); + }, + FORM_data1 => parseFormValueConstant(allocator, in_stream, false, 1), + FORM_data2 => parseFormValueConstant(allocator, in_stream, false, 2), + FORM_data4 => parseFormValueConstant(allocator, in_stream, false, 4), + FORM_data8 => parseFormValueConstant(allocator, in_stream, false, 8), + FORM_udata, FORM_sdata => { + const signed = form_id == FORM_sdata; + return parseFormValueConstant(allocator, in_stream, signed, -1); + }, + FORM_exprloc => { + const size = try noasync leb.readULEB128(usize, in_stream); + const buf = try readAllocBytes(allocator, in_stream, size); + return FormValue{ .ExprLoc = buf }; + }, + FORM_flag => FormValue{ .Flag = (try noasync in_stream.readByte()) != 0 }, + FORM_flag_present => FormValue{ .Flag = true }, + FORM_sec_offset => FormValue{ .SecOffset = try parseFormValueDwarfOffsetSize(in_stream, is_64) }, + + FORM_ref1 => parseFormValueRef(allocator, in_stream, 1), + FORM_ref2 => parseFormValueRef(allocator, in_stream, 2), + FORM_ref4 => parseFormValueRef(allocator, in_stream, 4), + FORM_ref8 => parseFormValueRef(allocator, in_stream, 8), + FORM_ref_udata => parseFormValueRef(allocator, in_stream, -1), + + FORM_ref_addr => FormValue{ .RefAddr = try parseFormValueDwarfOffsetSize(in_stream, is_64) }, + FORM_ref_sig8 => FormValue{ .Ref = try noasync in_stream.readIntLittle(u64) }, + + FORM_string => FormValue{ .String = try in_stream.readUntilDelimiterAlloc(allocator, 0, math.maxInt(usize)) }, + FORM_strp => FormValue{ .StrPtr = try parseFormValueDwarfOffsetSize(in_stream, is_64) }, + FORM_indirect => { + const child_form_id = try noasync leb.readULEB128(u64, in_stream); + const F = @TypeOf(async parseFormValue(allocator, in_stream, child_form_id, is_64)); + var frame = try allocator.create(F); + defer allocator.destroy(frame); + return await @asyncCall(frame, {}, parseFormValue, allocator, in_stream, child_form_id, is_64); + }, + else => error.InvalidDebugInfo, + }; +} + +fn getAbbrevTableEntry(abbrev_table: *const AbbrevTable, abbrev_code: u64) ?*const AbbrevTableEntry { + for (abbrev_table.toSliceConst()) |*table_entry| { + if (table_entry.abbrev_code == abbrev_code) return table_entry; + } + return null; +} + +pub const DwarfInfo = struct { + endian: builtin.Endian, + // No memory is owned by the DwarfInfo + debug_info: []const u8, + debug_abbrev: []const u8, + debug_str: []const u8, + debug_line: []const u8, + debug_ranges: ?[]const u8, + // Filled later by the initializer + abbrev_table_list: ArrayList(AbbrevTableHeader) = undefined, + compile_unit_list: ArrayList(CompileUnit) = undefined, + func_list: ArrayList(Func) = undefined, + + pub fn allocator(self: DwarfInfo) *mem.Allocator { + return self.abbrev_table_list.allocator; + } + + fn getSymbolName(di: *DwarfInfo, address: u64) ?[]const u8 { + for (di.func_list.toSliceConst()) |*func| { + if (func.pc_range) |range| { + if (address >= range.start and address < range.end) { + return func.name; + } + } + } + + return null; + } + + fn scanAllFunctions(di: *DwarfInfo) !void { + var s = io.SliceSeekableInStream.init(di.debug_info); + var this_unit_offset: u64 = 0; + + while (this_unit_offset < try s.seekable_stream.getEndPos()) { + s.seekable_stream.seekTo(this_unit_offset) catch |err| switch (err) { + error.EndOfStream => unreachable, + else => return err, + }; + + var is_64: bool = undefined; + const unit_length = try readInitialLength(@TypeOf(s.stream.readFn).ReturnType.ErrorSet, &s.stream, &is_64); + if (unit_length == 0) return; + const next_offset = unit_length + (if (is_64) @as(usize, 12) else @as(usize, 4)); + + const version = try s.stream.readInt(u16, di.endian); + if (version < 2 or version > 5) return error.InvalidDebugInfo; + + const debug_abbrev_offset = if (is_64) try s.stream.readInt(u64, di.endian) else try s.stream.readInt(u32, di.endian); + + const address_size = try s.stream.readByte(); + if (address_size != @sizeOf(usize)) return error.InvalidDebugInfo; + + const compile_unit_pos = try s.seekable_stream.getPos(); + const abbrev_table = try di.getAbbrevTable(debug_abbrev_offset); + + try s.seekable_stream.seekTo(compile_unit_pos); + + const next_unit_pos = this_unit_offset + next_offset; + + while ((try s.seekable_stream.getPos()) < next_unit_pos) { + const die_obj = (try di.parseDie(&s.stream, abbrev_table, is_64)) orelse continue; + defer die_obj.attrs.deinit(); + + const after_die_offset = try s.seekable_stream.getPos(); + + switch (die_obj.tag_id) { + TAG_subprogram, TAG_inlined_subroutine, TAG_subroutine, TAG_entry_point => { + const fn_name = x: { + var depth: i32 = 3; + var this_die_obj = die_obj; + // Prenvent endless loops + while (depth > 0) : (depth -= 1) { + if (this_die_obj.getAttr(AT_name)) |_| { + const name = try this_die_obj.getAttrString(di, AT_name); + break :x name; + } else if (this_die_obj.getAttr(AT_abstract_origin)) |ref| { + // Follow the DIE it points to and repeat + const ref_offset = try this_die_obj.getAttrRef(AT_abstract_origin); + if (ref_offset > next_offset) return error.InvalidDebugInfo; + try s.seekable_stream.seekTo(this_unit_offset + ref_offset); + this_die_obj = (try di.parseDie(&s.stream, abbrev_table, is_64)) orelse return error.InvalidDebugInfo; + } else if (this_die_obj.getAttr(AT_specification)) |ref| { + // Follow the DIE it points to and repeat + const ref_offset = try this_die_obj.getAttrRef(AT_specification); + if (ref_offset > next_offset) return error.InvalidDebugInfo; + try s.seekable_stream.seekTo(this_unit_offset + ref_offset); + this_die_obj = (try di.parseDie(&s.stream, abbrev_table, is_64)) orelse return error.InvalidDebugInfo; + } else { + break :x null; + } + } + + break :x null; + }; + + const pc_range = x: { + if (die_obj.getAttrAddr(AT_low_pc)) |low_pc| { + if (die_obj.getAttr(AT_high_pc)) |high_pc_value| { + const pc_end = switch (high_pc_value.*) { + FormValue.Address => |value| value, + FormValue.Const => |value| b: { + const offset = try value.asUnsignedLe(); + break :b (low_pc + offset); + }, + else => return error.InvalidDebugInfo, + }; + break :x PcRange{ + .start = low_pc, + .end = pc_end, + }; + } else { + break :x null; + } + } else |err| { + if (err != error.MissingDebugInfo) return err; + break :x null; + } + }; + + try di.func_list.append(Func{ + .name = fn_name, + .pc_range = pc_range, + }); + }, + else => {}, + } + + try s.seekable_stream.seekTo(after_die_offset); + } + + this_unit_offset += next_offset; + } + } + + fn scanAllCompileUnits(di: *DwarfInfo) !void { + var s = io.SliceSeekableInStream.init(di.debug_info); + var this_unit_offset: u64 = 0; + + while (this_unit_offset < try s.seekable_stream.getEndPos()) { + s.seekable_stream.seekTo(this_unit_offset) catch |err| switch (err) { + error.EndOfStream => unreachable, + else => return err, + }; + + var is_64: bool = undefined; + const unit_length = try readInitialLength(@TypeOf(s.stream.readFn).ReturnType.ErrorSet, &s.stream, &is_64); + if (unit_length == 0) return; + const next_offset = unit_length + (if (is_64) @as(usize, 12) else @as(usize, 4)); + + const version = try s.stream.readInt(u16, di.endian); + if (version < 2 or version > 5) return error.InvalidDebugInfo; + + const debug_abbrev_offset = if (is_64) try s.stream.readInt(u64, di.endian) else try s.stream.readInt(u32, di.endian); + + const address_size = try s.stream.readByte(); + if (address_size != @sizeOf(usize)) return error.InvalidDebugInfo; + + const compile_unit_pos = try s.seekable_stream.getPos(); + const abbrev_table = try di.getAbbrevTable(debug_abbrev_offset); + + try s.seekable_stream.seekTo(compile_unit_pos); + + const compile_unit_die = try di.allocator().create(Die); + compile_unit_die.* = (try di.parseDie(&s.stream, abbrev_table, is_64)) orelse return error.InvalidDebugInfo; + + if (compile_unit_die.tag_id != TAG_compile_unit) return error.InvalidDebugInfo; + + const pc_range = x: { + if (compile_unit_die.getAttrAddr(AT_low_pc)) |low_pc| { + if (compile_unit_die.getAttr(AT_high_pc)) |high_pc_value| { + const pc_end = switch (high_pc_value.*) { + FormValue.Address => |value| value, + FormValue.Const => |value| b: { + const offset = try value.asUnsignedLe(); + break :b (low_pc + offset); + }, + else => return error.InvalidDebugInfo, + }; + break :x PcRange{ + .start = low_pc, + .end = pc_end, + }; + } else { + break :x null; + } + } else |err| { + if (err != error.MissingDebugInfo) return err; + break :x null; + } + }; + + try di.compile_unit_list.append(CompileUnit{ + .version = version, + .is_64 = is_64, + .pc_range = pc_range, + .die = compile_unit_die, + }); + + this_unit_offset += next_offset; + } + } + + fn findCompileUnit(di: *DwarfInfo, target_address: u64) !*const CompileUnit { + for (di.compile_unit_list.toSlice()) |*compile_unit| { + if (compile_unit.pc_range) |range| { + if (target_address >= range.start and target_address < range.end) return compile_unit; + } + if (di.debug_ranges) |debug_ranges| { + if (compile_unit.die.getAttrSecOffset(AT_ranges)) |ranges_offset| { + var s = io.SliceSeekableInStream.init(debug_ranges); + + // All the addresses in the list are relative to the value + // specified by DW_AT_low_pc or to some other value encoded + // in the list itself. + // If no starting value is specified use zero. + var base_address = compile_unit.die.getAttrAddr(AT_low_pc) catch |err| switch (err) { + error.MissingDebugInfo => 0, + else => return err, + }; + + try s.seekable_stream.seekTo(ranges_offset); + + while (true) { + const begin_addr = try s.stream.readIntLittle(usize); + const end_addr = try s.stream.readIntLittle(usize); + if (begin_addr == 0 and end_addr == 0) { + break; + } + // This entry selects a new value for the base address + if (begin_addr == math.maxInt(usize)) { + base_address = end_addr; + continue; + } + if (target_address >= base_address + begin_addr and target_address < base_address + end_addr) { + return compile_unit; + } + } + } else |err| { + if (err != error.MissingDebugInfo) return err; + continue; + } + } + } + return error.MissingDebugInfo; + } + + /// Gets an already existing AbbrevTable given the abbrev_offset, or if not found, + /// seeks in the stream and parses it. + fn getAbbrevTable(di: *DwarfInfo, abbrev_offset: u64) !*const AbbrevTable { + for (di.abbrev_table_list.toSlice()) |*header| { + if (header.offset == abbrev_offset) { + return &header.table; + } + } + try di.abbrev_table_list.append(AbbrevTableHeader{ + .offset = abbrev_offset, + .table = try di.parseAbbrevTable(abbrev_offset), + }); + return &di.abbrev_table_list.items[di.abbrev_table_list.len - 1].table; + } + + fn parseAbbrevTable(di: *DwarfInfo, offset: u64) !AbbrevTable { + var s = io.SliceSeekableInStream.init(di.debug_abbrev); + + try s.seekable_stream.seekTo(offset); + var result = AbbrevTable.init(di.allocator()); + errdefer result.deinit(); + while (true) { + const abbrev_code = try leb.readULEB128(u64, &s.stream); + if (abbrev_code == 0) return result; + try result.append(AbbrevTableEntry{ + .abbrev_code = abbrev_code, + .tag_id = try leb.readULEB128(u64, &s.stream), + .has_children = (try s.stream.readByte()) == CHILDREN_yes, + .attrs = ArrayList(AbbrevAttr).init(di.allocator()), + }); + const attrs = &result.items[result.len - 1].attrs; + + while (true) { + const attr_id = try leb.readULEB128(u64, &s.stream); + const form_id = try leb.readULEB128(u64, &s.stream); + if (attr_id == 0 and form_id == 0) break; + try attrs.append(AbbrevAttr{ + .attr_id = attr_id, + .form_id = form_id, + }); + } + } + } + + fn parseDie(di: *DwarfInfo, in_stream: var, abbrev_table: *const AbbrevTable, is_64: bool) !?Die { + const abbrev_code = try leb.readULEB128(u64, in_stream); + if (abbrev_code == 0) return null; + const table_entry = getAbbrevTableEntry(abbrev_table, abbrev_code) orelse return error.InvalidDebugInfo; + + var result = Die{ + .tag_id = table_entry.tag_id, + .has_children = table_entry.has_children, + .attrs = ArrayList(Die.Attr).init(di.allocator()), + }; + try result.attrs.resize(table_entry.attrs.len); + for (table_entry.attrs.toSliceConst()) |attr, i| { + result.attrs.items[i] = Die.Attr{ + .id = attr.attr_id, + .value = try parseFormValue(di.allocator(), in_stream, attr.form_id, is_64), + }; + } + return result; + } + + fn getLineNumberInfo(di: *DwarfInfo, compile_unit: CompileUnit, target_address: usize) !debug.LineInfo { + var s = io.SliceSeekableInStream.init(di.debug_line); + + const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT_comp_dir); + const line_info_offset = try compile_unit.die.getAttrSecOffset(AT_stmt_list); + + try s.seekable_stream.seekTo(line_info_offset); + + var is_64: bool = undefined; + const unit_length = try readInitialLength(@TypeOf(s.stream.readFn).ReturnType.ErrorSet, &s.stream, &is_64); + if (unit_length == 0) { + return error.MissingDebugInfo; + } + const next_offset = unit_length + (if (is_64) @as(usize, 12) else @as(usize, 4)); + + const version = try s.stream.readInt(u16, di.endian); + // TODO support 3 and 5 + if (version != 2 and version != 4) return error.InvalidDebugInfo; + + const prologue_length = if (is_64) try s.stream.readInt(u64, di.endian) else try s.stream.readInt(u32, di.endian); + const prog_start_offset = (try s.seekable_stream.getPos()) + prologue_length; + + const minimum_instruction_length = try s.stream.readByte(); + if (minimum_instruction_length == 0) return error.InvalidDebugInfo; + + if (version >= 4) { + // maximum_operations_per_instruction + _ = try s.stream.readByte(); + } + + const default_is_stmt = (try s.stream.readByte()) != 0; + const line_base = try s.stream.readByteSigned(); + + const line_range = try s.stream.readByte(); + if (line_range == 0) return error.InvalidDebugInfo; + + const opcode_base = try s.stream.readByte(); + + const standard_opcode_lengths = try di.allocator().alloc(u8, opcode_base - 1); + defer di.allocator().free(standard_opcode_lengths); + + { + var i: usize = 0; + while (i < opcode_base - 1) : (i += 1) { + standard_opcode_lengths[i] = try s.stream.readByte(); + } + } + + var include_directories = ArrayList([]const u8).init(di.allocator()); + try include_directories.append(compile_unit_cwd); + while (true) { + const dir = try s.stream.readUntilDelimiterAlloc(di.allocator(), 0, math.maxInt(usize)); + if (dir.len == 0) break; + try include_directories.append(dir); + } + + var file_entries = ArrayList(FileEntry).init(di.allocator()); + var prog = LineNumberProgram.init(default_is_stmt, include_directories.toSliceConst(), &file_entries, target_address); + + while (true) { + const file_name = try s.stream.readUntilDelimiterAlloc(di.allocator(), 0, math.maxInt(usize)); + if (file_name.len == 0) break; + const dir_index = try leb.readULEB128(usize, &s.stream); + const mtime = try leb.readULEB128(usize, &s.stream); + const len_bytes = try leb.readULEB128(usize, &s.stream); + try file_entries.append(FileEntry{ + .file_name = file_name, + .dir_index = dir_index, + .mtime = mtime, + .len_bytes = len_bytes, + }); + } + + try s.seekable_stream.seekTo(prog_start_offset); + + const next_unit_pos = line_info_offset + next_offset; + + while ((try s.seekable_stream.getPos()) < next_unit_pos) { + const opcode = try s.stream.readByte(); + + if (opcode == LNS_extended_op) { + const op_size = try leb.readULEB128(u64, &s.stream); + if (op_size < 1) return error.InvalidDebugInfo; + var sub_op = try s.stream.readByte(); + switch (sub_op) { + LNE_end_sequence => { + prog.end_sequence = true; + if (try prog.checkLineMatch()) |info| return info; + prog.reset(); + }, + LNE_set_address => { + const addr = try s.stream.readInt(usize, di.endian); + prog.address = addr; + }, + LNE_define_file => { + const file_name = try s.stream.readUntilDelimiterAlloc(di.allocator(), 0, math.maxInt(usize)); + const dir_index = try leb.readULEB128(usize, &s.stream); + const mtime = try leb.readULEB128(usize, &s.stream); + const len_bytes = try leb.readULEB128(usize, &s.stream); + try file_entries.append(FileEntry{ + .file_name = file_name, + .dir_index = dir_index, + .mtime = mtime, + .len_bytes = len_bytes, + }); + }, + else => { + const fwd_amt = math.cast(isize, op_size - 1) catch return error.InvalidDebugInfo; + try s.seekable_stream.seekBy(fwd_amt); + }, + } + } else if (opcode >= opcode_base) { + // special opcodes + const adjusted_opcode = opcode - opcode_base; + const inc_addr = minimum_instruction_length * (adjusted_opcode / line_range); + const inc_line = @as(i32, line_base) + @as(i32, adjusted_opcode % line_range); + prog.line += inc_line; + prog.address += inc_addr; + if (try prog.checkLineMatch()) |info| return info; + prog.basic_block = false; + } else { + switch (opcode) { + LNS_copy => { + if (try prog.checkLineMatch()) |info| return info; + prog.basic_block = false; + }, + LNS_advance_pc => { + const arg = try leb.readULEB128(usize, &s.stream); + prog.address += arg * minimum_instruction_length; + }, + LNS_advance_line => { + const arg = try leb.readILEB128(i64, &s.stream); + prog.line += arg; + }, + LNS_set_file => { + const arg = try leb.readULEB128(usize, &s.stream); + prog.file = arg; + }, + LNS_set_column => { + const arg = try leb.readULEB128(u64, &s.stream); + prog.column = arg; + }, + LNS_negate_stmt => { + prog.is_stmt = !prog.is_stmt; + }, + LNS_set_basic_block => { + prog.basic_block = true; + }, + LNS_const_add_pc => { + const inc_addr = minimum_instruction_length * ((255 - opcode_base) / line_range); + prog.address += inc_addr; + }, + LNS_fixed_advance_pc => { + const arg = try s.stream.readInt(u16, di.endian); + prog.address += arg; + }, + LNS_set_prologue_end => {}, + else => { + if (opcode - 1 >= standard_opcode_lengths.len) return error.InvalidDebugInfo; + const len_bytes = standard_opcode_lengths[opcode - 1]; + try s.seekable_stream.seekBy(len_bytes); + }, + } + } + } + + return error.MissingDebugInfo; + } + + fn getString(di: *DwarfInfo, offset: u64) ![]const u8 { + if (offset > di.debug_str.len) + return error.InvalidDebugInfo; + const casted_offset = math.cast(usize, offset) catch + return error.InvalidDebugInfo; + + // Valid strings always have a terminating zero byte + if (mem.indexOfScalarPos(u8, di.debug_str, casted_offset, 0)) |last| { + return di.debug_str[casted_offset..last]; + } + + return error.InvalidDebugInfo; + } +}; + +/// Initialize DWARF info. The caller has the responsibility to initialize most +/// the DwarfInfo fields before calling. These fields can be left undefined: +/// * abbrev_table_list +/// * compile_unit_list +pub fn openDwarfDebugInfo(di: *DwarfInfo, allocator: *mem.Allocator) !void { + di.abbrev_table_list = ArrayList(AbbrevTableHeader).init(allocator); + di.compile_unit_list = ArrayList(CompileUnit).init(allocator); + di.func_list = ArrayList(Func).init(allocator); + try di.scanAllFunctions(); + try di.scanAllCompileUnits(); +} diff --git a/lib/std/dwarf_bits.zig b/lib/std/dwarf_bits.zig new file mode 100644 index 0000000000..2f3b29302d --- /dev/null +++ b/lib/std/dwarf_bits.zig @@ -0,0 +1,682 @@ +pub const TAG_padding = 0x00; +pub const TAG_array_type = 0x01; +pub const TAG_class_type = 0x02; +pub const TAG_entry_point = 0x03; +pub const TAG_enumeration_type = 0x04; +pub const TAG_formal_parameter = 0x05; +pub const TAG_imported_declaration = 0x08; +pub const TAG_label = 0x0a; +pub const TAG_lexical_block = 0x0b; +pub const TAG_member = 0x0d; +pub const TAG_pointer_type = 0x0f; +pub const TAG_reference_type = 0x10; +pub const TAG_compile_unit = 0x11; +pub const TAG_string_type = 0x12; +pub const TAG_structure_type = 0x13; +pub const TAG_subroutine = 0x14; +pub const TAG_subroutine_type = 0x15; +pub const TAG_typedef = 0x16; +pub const TAG_union_type = 0x17; +pub const TAG_unspecified_parameters = 0x18; +pub const TAG_variant = 0x19; +pub const TAG_common_block = 0x1a; +pub const TAG_common_inclusion = 0x1b; +pub const TAG_inheritance = 0x1c; +pub const TAG_inlined_subroutine = 0x1d; +pub const TAG_module = 0x1e; +pub const TAG_ptr_to_member_type = 0x1f; +pub const TAG_set_type = 0x20; +pub const TAG_subrange_type = 0x21; +pub const TAG_with_stmt = 0x22; +pub const TAG_access_declaration = 0x23; +pub const TAG_base_type = 0x24; +pub const TAG_catch_block = 0x25; +pub const TAG_const_type = 0x26; +pub const TAG_constant = 0x27; +pub const TAG_enumerator = 0x28; +pub const TAG_file_type = 0x29; +pub const TAG_friend = 0x2a; +pub const TAG_namelist = 0x2b; +pub const TAG_namelist_item = 0x2c; +pub const TAG_packed_type = 0x2d; +pub const TAG_subprogram = 0x2e; +pub const TAG_template_type_param = 0x2f; +pub const TAG_template_value_param = 0x30; +pub const TAG_thrown_type = 0x31; +pub const TAG_try_block = 0x32; +pub const TAG_variant_part = 0x33; +pub const TAG_variable = 0x34; +pub const TAG_volatile_type = 0x35; + +// DWARF 3 +pub const TAG_dwarf_procedure = 0x36; +pub const TAG_restrict_type = 0x37; +pub const TAG_interface_type = 0x38; +pub const TAG_namespace = 0x39; +pub const TAG_imported_module = 0x3a; +pub const TAG_unspecified_type = 0x3b; +pub const TAG_partial_unit = 0x3c; +pub const TAG_imported_unit = 0x3d; +pub const TAG_condition = 0x3f; +pub const TAG_shared_type = 0x40; + +// DWARF 4 +pub const TAG_type_unit = 0x41; +pub const TAG_rvalue_reference_type = 0x42; +pub const TAG_template_alias = 0x43; + +pub const TAG_lo_user = 0x4080; +pub const TAG_hi_user = 0xffff; + +// SGI/MIPS Extensions. +pub const DW_TAG_MIPS_loop = 0x4081; + +// HP extensions. See: ftp://ftp.hp.com/pub/lang/tools/WDB/wdb-4.0.tar.gz . +pub const TAG_HP_array_descriptor = 0x4090; +pub const TAG_HP_Bliss_field = 0x4091; +pub const TAG_HP_Bliss_field_set = 0x4092; + +// GNU extensions. +pub const TAG_format_label = 0x4101; // For FORTRAN 77 and Fortran 90. +pub const TAG_function_template = 0x4102; // For C++. +pub const TAG_class_template = 0x4103; //For C++. +pub const TAG_GNU_BINCL = 0x4104; +pub const TAG_GNU_EINCL = 0x4105; + +// Template template parameter. +// See http://gcc.gnu.org/wiki/TemplateParmsDwarf . +pub const TAG_GNU_template_template_param = 0x4106; + +// Template parameter pack extension = specified at +// http://wiki.dwarfstd.org/index.php?title=C%2B%2B0x:_Variadic_templates +// The values of these two TAGS are in the DW_TAG_GNU_* space until the tags +// are properly part of DWARF 5. +pub const TAG_GNU_template_parameter_pack = 0x4107; +pub const TAG_GNU_formal_parameter_pack = 0x4108; +// The GNU call site extension = specified at +// http://www.dwarfstd.org/ShowIssue.php?issue=100909.2&type=open . +// The values of these two TAGS are in the DW_TAG_GNU_* space until the tags +// are properly part of DWARF 5. +pub const TAG_GNU_call_site = 0x4109; +pub const TAG_GNU_call_site_parameter = 0x410a; +// Extensions for UPC. See: http://dwarfstd.org/doc/DWARF4.pdf. +pub const TAG_upc_shared_type = 0x8765; +pub const TAG_upc_strict_type = 0x8766; +pub const TAG_upc_relaxed_type = 0x8767; +// PGI (STMicroelectronics; extensions. No documentation available. +pub const TAG_PGI_kanji_type = 0xA000; +pub const TAG_PGI_interface_block = 0xA020; + +pub const FORM_addr = 0x01; +pub const FORM_block2 = 0x03; +pub const FORM_block4 = 0x04; +pub const FORM_data2 = 0x05; +pub const FORM_data4 = 0x06; +pub const FORM_data8 = 0x07; +pub const FORM_string = 0x08; +pub const FORM_block = 0x09; +pub const FORM_block1 = 0x0a; +pub const FORM_data1 = 0x0b; +pub const FORM_flag = 0x0c; +pub const FORM_sdata = 0x0d; +pub const FORM_strp = 0x0e; +pub const FORM_udata = 0x0f; +pub const FORM_ref_addr = 0x10; +pub const FORM_ref1 = 0x11; +pub const FORM_ref2 = 0x12; +pub const FORM_ref4 = 0x13; +pub const FORM_ref8 = 0x14; +pub const FORM_ref_udata = 0x15; +pub const FORM_indirect = 0x16; +pub const FORM_sec_offset = 0x17; +pub const FORM_exprloc = 0x18; +pub const FORM_flag_present = 0x19; +pub const FORM_ref_sig8 = 0x20; + +// Extensions for Fission. See http://gcc.gnu.org/wiki/DebugFission. +pub const FORM_GNU_addr_index = 0x1f01; +pub const FORM_GNU_str_index = 0x1f02; + +// Extensions for DWZ multifile. +// See http://www.dwarfstd.org/ShowIssue.php?issue=120604.1&type=open . +pub const FORM_GNU_ref_alt = 0x1f20; +pub const FORM_GNU_strp_alt = 0x1f21; + +pub const AT_sibling = 0x01; +pub const AT_location = 0x02; +pub const AT_name = 0x03; +pub const AT_ordering = 0x09; +pub const AT_subscr_data = 0x0a; +pub const AT_byte_size = 0x0b; +pub const AT_bit_offset = 0x0c; +pub const AT_bit_size = 0x0d; +pub const AT_element_list = 0x0f; +pub const AT_stmt_list = 0x10; +pub const AT_low_pc = 0x11; +pub const AT_high_pc = 0x12; +pub const AT_language = 0x13; +pub const AT_member = 0x14; +pub const AT_discr = 0x15; +pub const AT_discr_value = 0x16; +pub const AT_visibility = 0x17; +pub const AT_import = 0x18; +pub const AT_string_length = 0x19; +pub const AT_common_reference = 0x1a; +pub const AT_comp_dir = 0x1b; +pub const AT_const_value = 0x1c; +pub const AT_containing_type = 0x1d; +pub const AT_default_value = 0x1e; +pub const AT_inline = 0x20; +pub const AT_is_optional = 0x21; +pub const AT_lower_bound = 0x22; +pub const AT_producer = 0x25; +pub const AT_prototyped = 0x27; +pub const AT_return_addr = 0x2a; +pub const AT_start_scope = 0x2c; +pub const AT_bit_stride = 0x2e; +pub const AT_upper_bound = 0x2f; +pub const AT_abstract_origin = 0x31; +pub const AT_accessibility = 0x32; +pub const AT_address_class = 0x33; +pub const AT_artificial = 0x34; +pub const AT_base_types = 0x35; +pub const AT_calling_convention = 0x36; +pub const AT_count = 0x37; +pub const AT_data_member_location = 0x38; +pub const AT_decl_column = 0x39; +pub const AT_decl_file = 0x3a; +pub const AT_decl_line = 0x3b; +pub const AT_declaration = 0x3c; +pub const AT_discr_list = 0x3d; +pub const AT_encoding = 0x3e; +pub const AT_external = 0x3f; +pub const AT_frame_base = 0x40; +pub const AT_friend = 0x41; +pub const AT_identifier_case = 0x42; +pub const AT_macro_info = 0x43; +pub const AT_namelist_items = 0x44; +pub const AT_priority = 0x45; +pub const AT_segment = 0x46; +pub const AT_specification = 0x47; +pub const AT_static_link = 0x48; +pub const AT_type = 0x49; +pub const AT_use_location = 0x4a; +pub const AT_variable_parameter = 0x4b; +pub const AT_virtuality = 0x4c; +pub const AT_vtable_elem_location = 0x4d; + +// DWARF 3 values. +pub const AT_allocated = 0x4e; +pub const AT_associated = 0x4f; +pub const AT_data_location = 0x50; +pub const AT_byte_stride = 0x51; +pub const AT_entry_pc = 0x52; +pub const AT_use_UTF8 = 0x53; +pub const AT_extension = 0x54; +pub const AT_ranges = 0x55; +pub const AT_trampoline = 0x56; +pub const AT_call_column = 0x57; +pub const AT_call_file = 0x58; +pub const AT_call_line = 0x59; +pub const AT_description = 0x5a; +pub const AT_binary_scale = 0x5b; +pub const AT_decimal_scale = 0x5c; +pub const AT_small = 0x5d; +pub const AT_decimal_sign = 0x5e; +pub const AT_digit_count = 0x5f; +pub const AT_picture_string = 0x60; +pub const AT_mutable = 0x61; +pub const AT_threads_scaled = 0x62; +pub const AT_explicit = 0x63; +pub const AT_object_pointer = 0x64; +pub const AT_endianity = 0x65; +pub const AT_elemental = 0x66; +pub const AT_pure = 0x67; +pub const AT_recursive = 0x68; + +// DWARF 4. +pub const AT_signature = 0x69; +pub const AT_main_subprogram = 0x6a; +pub const AT_data_bit_offset = 0x6b; +pub const AT_const_expr = 0x6c; +pub const AT_enum_class = 0x6d; +pub const AT_linkage_name = 0x6e; + +// DWARF 5 +pub const AT_alignment = 0x88; + +pub const AT_lo_user = 0x2000; // Implementation-defined range start. +pub const AT_hi_user = 0x3fff; // Implementation-defined range end. + +// SGI/MIPS extensions. +pub const AT_MIPS_fde = 0x2001; +pub const AT_MIPS_loop_begin = 0x2002; +pub const AT_MIPS_tail_loop_begin = 0x2003; +pub const AT_MIPS_epilog_begin = 0x2004; +pub const AT_MIPS_loop_unroll_factor = 0x2005; +pub const AT_MIPS_software_pipeline_depth = 0x2006; +pub const AT_MIPS_linkage_name = 0x2007; +pub const AT_MIPS_stride = 0x2008; +pub const AT_MIPS_abstract_name = 0x2009; +pub const AT_MIPS_clone_origin = 0x200a; +pub const AT_MIPS_has_inlines = 0x200b; + +// HP extensions. +pub const AT_HP_block_index = 0x2000; +pub const AT_HP_unmodifiable = 0x2001; // Same as DW_AT_MIPS_fde. +pub const AT_HP_prologue = 0x2005; // Same as DW_AT_MIPS_loop_unroll. +pub const AT_HP_epilogue = 0x2008; // Same as DW_AT_MIPS_stride. +pub const AT_HP_actuals_stmt_list = 0x2010; +pub const AT_HP_proc_per_section = 0x2011; +pub const AT_HP_raw_data_ptr = 0x2012; +pub const AT_HP_pass_by_reference = 0x2013; +pub const AT_HP_opt_level = 0x2014; +pub const AT_HP_prof_version_id = 0x2015; +pub const AT_HP_opt_flags = 0x2016; +pub const AT_HP_cold_region_low_pc = 0x2017; +pub const AT_HP_cold_region_high_pc = 0x2018; +pub const AT_HP_all_variables_modifiable = 0x2019; +pub const AT_HP_linkage_name = 0x201a; +pub const AT_HP_prof_flags = 0x201b; // In comp unit of procs_info for -g. +pub const AT_HP_unit_name = 0x201f; +pub const AT_HP_unit_size = 0x2020; +pub const AT_HP_widened_byte_size = 0x2021; +pub const AT_HP_definition_points = 0x2022; +pub const AT_HP_default_location = 0x2023; +pub const AT_HP_is_result_param = 0x2029; + +// GNU extensions. +pub const AT_sf_names = 0x2101; +pub const AT_src_info = 0x2102; +pub const AT_mac_info = 0x2103; +pub const AT_src_coords = 0x2104; +pub const AT_body_begin = 0x2105; +pub const AT_body_end = 0x2106; +pub const AT_GNU_vector = 0x2107; +// Thread-safety annotations. +// See http://gcc.gnu.org/wiki/ThreadSafetyAnnotation . +pub const AT_GNU_guarded_by = 0x2108; +pub const AT_GNU_pt_guarded_by = 0x2109; +pub const AT_GNU_guarded = 0x210a; +pub const AT_GNU_pt_guarded = 0x210b; +pub const AT_GNU_locks_excluded = 0x210c; +pub const AT_GNU_exclusive_locks_required = 0x210d; +pub const AT_GNU_shared_locks_required = 0x210e; +// One-definition rule violation detection. +// See http://gcc.gnu.org/wiki/DwarfSeparateTypeInfo . +pub const AT_GNU_odr_signature = 0x210f; +// Template template argument name. +// See http://gcc.gnu.org/wiki/TemplateParmsDwarf . +pub const AT_GNU_template_name = 0x2110; +// The GNU call site extension. +// See http://www.dwarfstd.org/ShowIssue.php?issue=100909.2&type=open . +pub const AT_GNU_call_site_value = 0x2111; +pub const AT_GNU_call_site_data_value = 0x2112; +pub const AT_GNU_call_site_target = 0x2113; +pub const AT_GNU_call_site_target_clobbered = 0x2114; +pub const AT_GNU_tail_call = 0x2115; +pub const AT_GNU_all_tail_call_sites = 0x2116; +pub const AT_GNU_all_call_sites = 0x2117; +pub const AT_GNU_all_source_call_sites = 0x2118; +// Section offset into .debug_macro section. +pub const AT_GNU_macros = 0x2119; +// Extensions for Fission. See http://gcc.gnu.org/wiki/DebugFission. +pub const AT_GNU_dwo_name = 0x2130; +pub const AT_GNU_dwo_id = 0x2131; +pub const AT_GNU_ranges_base = 0x2132; +pub const AT_GNU_addr_base = 0x2133; +pub const AT_GNU_pubnames = 0x2134; +pub const AT_GNU_pubtypes = 0x2135; +// VMS extensions. +pub const AT_VMS_rtnbeg_pd_address = 0x2201; +// GNAT extensions. +// GNAT descriptive type. +// See http://gcc.gnu.org/wiki/DW_AT_GNAT_descriptive_type . +pub const AT_use_GNAT_descriptive_type = 0x2301; +pub const AT_GNAT_descriptive_type = 0x2302; +// UPC extension. +pub const AT_upc_threads_scaled = 0x3210; +// PGI (STMicroelectronics) extensions. +pub const AT_PGI_lbase = 0x3a00; +pub const AT_PGI_soffset = 0x3a01; +pub const AT_PGI_lstride = 0x3a02; + +pub const OP_addr = 0x03; +pub const OP_deref = 0x06; +pub const OP_const1u = 0x08; +pub const OP_const1s = 0x09; +pub const OP_const2u = 0x0a; +pub const OP_const2s = 0x0b; +pub const OP_const4u = 0x0c; +pub const OP_const4s = 0x0d; +pub const OP_const8u = 0x0e; +pub const OP_const8s = 0x0f; +pub const OP_constu = 0x10; +pub const OP_consts = 0x11; +pub const OP_dup = 0x12; +pub const OP_drop = 0x13; +pub const OP_over = 0x14; +pub const OP_pick = 0x15; +pub const OP_swap = 0x16; +pub const OP_rot = 0x17; +pub const OP_xderef = 0x18; +pub const OP_abs = 0x19; +pub const OP_and = 0x1a; +pub const OP_div = 0x1b; +pub const OP_minus = 0x1c; +pub const OP_mod = 0x1d; +pub const OP_mul = 0x1e; +pub const OP_neg = 0x1f; +pub const OP_not = 0x20; +pub const OP_or = 0x21; +pub const OP_plus = 0x22; +pub const OP_plus_uconst = 0x23; +pub const OP_shl = 0x24; +pub const OP_shr = 0x25; +pub const OP_shra = 0x26; +pub const OP_xor = 0x27; +pub const OP_bra = 0x28; +pub const OP_eq = 0x29; +pub const OP_ge = 0x2a; +pub const OP_gt = 0x2b; +pub const OP_le = 0x2c; +pub const OP_lt = 0x2d; +pub const OP_ne = 0x2e; +pub const OP_skip = 0x2f; +pub const OP_lit0 = 0x30; +pub const OP_lit1 = 0x31; +pub const OP_lit2 = 0x32; +pub const OP_lit3 = 0x33; +pub const OP_lit4 = 0x34; +pub const OP_lit5 = 0x35; +pub const OP_lit6 = 0x36; +pub const OP_lit7 = 0x37; +pub const OP_lit8 = 0x38; +pub const OP_lit9 = 0x39; +pub const OP_lit10 = 0x3a; +pub const OP_lit11 = 0x3b; +pub const OP_lit12 = 0x3c; +pub const OP_lit13 = 0x3d; +pub const OP_lit14 = 0x3e; +pub const OP_lit15 = 0x3f; +pub const OP_lit16 = 0x40; +pub const OP_lit17 = 0x41; +pub const OP_lit18 = 0x42; +pub const OP_lit19 = 0x43; +pub const OP_lit20 = 0x44; +pub const OP_lit21 = 0x45; +pub const OP_lit22 = 0x46; +pub const OP_lit23 = 0x47; +pub const OP_lit24 = 0x48; +pub const OP_lit25 = 0x49; +pub const OP_lit26 = 0x4a; +pub const OP_lit27 = 0x4b; +pub const OP_lit28 = 0x4c; +pub const OP_lit29 = 0x4d; +pub const OP_lit30 = 0x4e; +pub const OP_lit31 = 0x4f; +pub const OP_reg0 = 0x50; +pub const OP_reg1 = 0x51; +pub const OP_reg2 = 0x52; +pub const OP_reg3 = 0x53; +pub const OP_reg4 = 0x54; +pub const OP_reg5 = 0x55; +pub const OP_reg6 = 0x56; +pub const OP_reg7 = 0x57; +pub const OP_reg8 = 0x58; +pub const OP_reg9 = 0x59; +pub const OP_reg10 = 0x5a; +pub const OP_reg11 = 0x5b; +pub const OP_reg12 = 0x5c; +pub const OP_reg13 = 0x5d; +pub const OP_reg14 = 0x5e; +pub const OP_reg15 = 0x5f; +pub const OP_reg16 = 0x60; +pub const OP_reg17 = 0x61; +pub const OP_reg18 = 0x62; +pub const OP_reg19 = 0x63; +pub const OP_reg20 = 0x64; +pub const OP_reg21 = 0x65; +pub const OP_reg22 = 0x66; +pub const OP_reg23 = 0x67; +pub const OP_reg24 = 0x68; +pub const OP_reg25 = 0x69; +pub const OP_reg26 = 0x6a; +pub const OP_reg27 = 0x6b; +pub const OP_reg28 = 0x6c; +pub const OP_reg29 = 0x6d; +pub const OP_reg30 = 0x6e; +pub const OP_reg31 = 0x6f; +pub const OP_breg0 = 0x70; +pub const OP_breg1 = 0x71; +pub const OP_breg2 = 0x72; +pub const OP_breg3 = 0x73; +pub const OP_breg4 = 0x74; +pub const OP_breg5 = 0x75; +pub const OP_breg6 = 0x76; +pub const OP_breg7 = 0x77; +pub const OP_breg8 = 0x78; +pub const OP_breg9 = 0x79; +pub const OP_breg10 = 0x7a; +pub const OP_breg11 = 0x7b; +pub const OP_breg12 = 0x7c; +pub const OP_breg13 = 0x7d; +pub const OP_breg14 = 0x7e; +pub const OP_breg15 = 0x7f; +pub const OP_breg16 = 0x80; +pub const OP_breg17 = 0x81; +pub const OP_breg18 = 0x82; +pub const OP_breg19 = 0x83; +pub const OP_breg20 = 0x84; +pub const OP_breg21 = 0x85; +pub const OP_breg22 = 0x86; +pub const OP_breg23 = 0x87; +pub const OP_breg24 = 0x88; +pub const OP_breg25 = 0x89; +pub const OP_breg26 = 0x8a; +pub const OP_breg27 = 0x8b; +pub const OP_breg28 = 0x8c; +pub const OP_breg29 = 0x8d; +pub const OP_breg30 = 0x8e; +pub const OP_breg31 = 0x8f; +pub const OP_regx = 0x90; +pub const OP_fbreg = 0x91; +pub const OP_bregx = 0x92; +pub const OP_piece = 0x93; +pub const OP_deref_size = 0x94; +pub const OP_xderef_size = 0x95; +pub const OP_nop = 0x96; + +// DWARF 3 extensions. +pub const OP_push_object_address = 0x97; +pub const OP_call2 = 0x98; +pub const OP_call4 = 0x99; +pub const OP_call_ref = 0x9a; +pub const OP_form_tls_address = 0x9b; +pub const OP_call_frame_cfa = 0x9c; +pub const OP_bit_piece = 0x9d; + +// DWARF 4 extensions. +pub const OP_implicit_value = 0x9e; +pub const OP_stack_value = 0x9f; + +pub const OP_lo_user = 0xe0; // Implementation-defined range start. +pub const OP_hi_user = 0xff; // Implementation-defined range end. + +// GNU extensions. +pub const OP_GNU_push_tls_address = 0xe0; +// The following is for marking variables that are uninitialized. +pub const OP_GNU_uninit = 0xf0; +pub const OP_GNU_encoded_addr = 0xf1; +// The GNU implicit pointer extension. +// See http://www.dwarfstd.org/ShowIssue.php?issue=100831.1&type=open . +pub const OP_GNU_implicit_pointer = 0xf2; +// The GNU entry value extension. +// See http://www.dwarfstd.org/ShowIssue.php?issue=100909.1&type=open . +pub const OP_GNU_entry_value = 0xf3; +// The GNU typed stack extension. +// See http://www.dwarfstd.org/doc/040408.1.html . +pub const OP_GNU_const_type = 0xf4; +pub const OP_GNU_regval_type = 0xf5; +pub const OP_GNU_deref_type = 0xf6; +pub const OP_GNU_convert = 0xf7; +pub const OP_GNU_reinterpret = 0xf9; +// The GNU parameter ref extension. +pub const OP_GNU_parameter_ref = 0xfa; +// Extension for Fission. See http://gcc.gnu.org/wiki/DebugFission. +pub const OP_GNU_addr_index = 0xfb; +pub const OP_GNU_const_index = 0xfc; +// HP extensions. +pub const OP_HP_unknown = 0xe0; // Ouch, the same as GNU_push_tls_address. +pub const OP_HP_is_value = 0xe1; +pub const OP_HP_fltconst4 = 0xe2; +pub const OP_HP_fltconst8 = 0xe3; +pub const OP_HP_mod_range = 0xe4; +pub const OP_HP_unmod_range = 0xe5; +pub const OP_HP_tls = 0xe6; +// PGI (STMicroelectronics) extensions. +pub const OP_PGI_omp_thread_num = 0xf8; + +pub const ATE_void = 0x0; +pub const ATE_address = 0x1; +pub const ATE_boolean = 0x2; +pub const ATE_complex_float = 0x3; +pub const ATE_float = 0x4; +pub const ATE_signed = 0x5; +pub const ATE_signed_char = 0x6; +pub const ATE_unsigned = 0x7; +pub const ATE_unsigned_char = 0x8; + +// DWARF 3. +pub const ATE_imaginary_float = 0x9; +pub const ATE_packed_decimal = 0xa; +pub const ATE_numeric_string = 0xb; +pub const ATE_edited = 0xc; +pub const ATE_signed_fixed = 0xd; +pub const ATE_unsigned_fixed = 0xe; +pub const ATE_decimal_float = 0xf; + +// DWARF 4. +pub const ATE_UTF = 0x10; + +pub const ATE_lo_user = 0x80; +pub const ATE_hi_user = 0xff; + +// HP extensions. +pub const ATE_HP_float80 = 0x80; // Floating-point (80 bit). +pub const ATE_HP_complex_float80 = 0x81; // Complex floating-point (80 bit). +pub const ATE_HP_float128 = 0x82; // Floating-point (128 bit). +pub const ATE_HP_complex_float128 = 0x83; // Complex fp (128 bit). +pub const ATE_HP_floathpintel = 0x84; // Floating-point (82 bit IA64). +pub const ATE_HP_imaginary_float80 = 0x85; +pub const ATE_HP_imaginary_float128 = 0x86; +pub const ATE_HP_VAX_float = 0x88; // F or G floating. +pub const ATE_HP_VAX_float_d = 0x89; // D floating. +pub const ATE_HP_packed_decimal = 0x8a; // Cobol. +pub const ATE_HP_zoned_decimal = 0x8b; // Cobol. +pub const ATE_HP_edited = 0x8c; // Cobol. +pub const ATE_HP_signed_fixed = 0x8d; // Cobol. +pub const ATE_HP_unsigned_fixed = 0x8e; // Cobol. +pub const ATE_HP_VAX_complex_float = 0x8f; // F or G floating complex. +pub const ATE_HP_VAX_complex_float_d = 0x90; // D floating complex. + +pub const CFA_advance_loc = 0x40; +pub const CFA_offset = 0x80; +pub const CFA_restore = 0xc0; +pub const CFA_nop = 0x00; +pub const CFA_set_loc = 0x01; +pub const CFA_advance_loc1 = 0x02; +pub const CFA_advance_loc2 = 0x03; +pub const CFA_advance_loc4 = 0x04; +pub const CFA_offset_extended = 0x05; +pub const CFA_restore_extended = 0x06; +pub const CFA_undefined = 0x07; +pub const CFA_same_value = 0x08; +pub const CFA_register = 0x09; +pub const CFA_remember_state = 0x0a; +pub const CFA_restore_state = 0x0b; +pub const CFA_def_cfa = 0x0c; +pub const CFA_def_cfa_register = 0x0d; +pub const CFA_def_cfa_offset = 0x0e; + +// DWARF 3. +pub const CFA_def_cfa_expression = 0x0f; +pub const CFA_expression = 0x10; +pub const CFA_offset_extended_sf = 0x11; +pub const CFA_def_cfa_sf = 0x12; +pub const CFA_def_cfa_offset_sf = 0x13; +pub const CFA_val_offset = 0x14; +pub const CFA_val_offset_sf = 0x15; +pub const CFA_val_expression = 0x16; + +pub const CFA_lo_user = 0x1c; +pub const CFA_hi_user = 0x3f; + +// SGI/MIPS specific. +pub const CFA_MIPS_advance_loc8 = 0x1d; + +// GNU extensions. +pub const CFA_GNU_window_save = 0x2d; +pub const CFA_GNU_args_size = 0x2e; +pub const CFA_GNU_negative_offset_extended = 0x2f; + +pub const CHILDREN_no = 0x00; +pub const CHILDREN_yes = 0x01; + +pub const LNS_extended_op = 0x00; +pub const LNS_copy = 0x01; +pub const LNS_advance_pc = 0x02; +pub const LNS_advance_line = 0x03; +pub const LNS_set_file = 0x04; +pub const LNS_set_column = 0x05; +pub const LNS_negate_stmt = 0x06; +pub const LNS_set_basic_block = 0x07; +pub const LNS_const_add_pc = 0x08; +pub const LNS_fixed_advance_pc = 0x09; +pub const LNS_set_prologue_end = 0x0a; +pub const LNS_set_epilogue_begin = 0x0b; +pub const LNS_set_isa = 0x0c; + +pub const LNE_end_sequence = 0x01; +pub const LNE_set_address = 0x02; +pub const LNE_define_file = 0x03; +pub const LNE_set_discriminator = 0x04; +pub const LNE_lo_user = 0x80; +pub const LNE_hi_user = 0xff; + +pub const LANG_C89 = 0x0001; +pub const LANG_C = 0x0002; +pub const LANG_Ada83 = 0x0003; +pub const LANG_C_plus_plus = 0x0004; +pub const LANG_Cobol74 = 0x0005; +pub const LANG_Cobol85 = 0x0006; +pub const LANG_Fortran77 = 0x0007; +pub const LANG_Fortran90 = 0x0008; +pub const LANG_Pascal83 = 0x0009; +pub const LANG_Modula2 = 0x000a; +pub const LANG_Java = 0x000b; +pub const LANG_C99 = 0x000c; +pub const LANG_Ada95 = 0x000d; +pub const LANG_Fortran95 = 0x000e; +pub const LANG_PLI = 0x000f; +pub const LANG_ObjC = 0x0010; +pub const LANG_ObjC_plus_plus = 0x0011; +pub const LANG_UPC = 0x0012; +pub const LANG_D = 0x0013; +pub const LANG_Python = 0x0014; +pub const LANG_Go = 0x0016; +pub const LANG_C_plus_plus_11 = 0x001a; +pub const LANG_Rust = 0x001c; +pub const LANG_C11 = 0x001d; +pub const LANG_C_plus_plus_14 = 0x0021; +pub const LANG_Fortran03 = 0x0022; +pub const LANG_Fortran08 = 0x0023; +pub const LANG_lo_user = 0x8000; +pub const LANG_hi_user = 0xffff; +pub const LANG_Mips_Assembler = 0x8001; +pub const LANG_Upc = 0x8765; +pub const LANG_HP_Bliss = 0x8003; +pub const LANG_HP_Basic91 = 0x8004; +pub const LANG_HP_Pascal91 = 0x8005; +pub const LANG_HP_IMacro = 0x8006; +pub const LANG_HP_Assembler = 0x8007; diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index 409dace20f..0d14f8d032 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -11,7 +11,7 @@ const system = std.os.system; const maxInt = std.math.maxInt; const max = std.math.max; -pub const DynLib = switch (builtin.os) { +pub const DynLib = switch (builtin.os.tag) { .linux => if (builtin.link_libc) DlDynlib else ElfDynLib, .windows => WindowsDynLib, .macosx, .tvos, .watchos, .ios, .freebsd => DlDynlib, @@ -82,12 +82,12 @@ pub fn linkmap_iterator(phdrs: []elf.Phdr) !LinkMap.Iterator { for (dyn_table) |*dyn| { switch (dyn.d_tag) { elf.DT_DEBUG => { - const r_debug = @intToPtr(*RDebug, dyn.d_un.d_ptr); + const r_debug = @intToPtr(*RDebug, dyn.d_val); if (r_debug.r_version != 1) return error.InvalidExe; break :init r_debug.r_map; }, elf.DT_PLTGOT => { - const got_table = @intToPtr([*]usize, dyn.d_un.d_ptr); + const got_table = @intToPtr([*]usize, dyn.d_val); // The address to the link_map structure is stored in the // second slot break :init @intToPtr(?*LinkMap, got_table[1]); @@ -390,7 +390,7 @@ pub const DlDynlib = struct { }; test "dynamic_library" { - const libname = switch (builtin.os) { + const libname = switch (builtin.os.tag) { .linux, .freebsd => "invalid_so.so", .windows => "invalid_dll.dll", .macosx, .tvos, .watchos, .ios => "invalid_dylib.dylib", diff --git a/lib/std/elf.zig b/lib/std/elf.zig index 9e4c1ac5f6..fc57db7c98 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -349,16 +349,6 @@ pub const Elf = struct { program_headers: []ProgramHeader, allocator: *mem.Allocator, - /// Call close when done. - pub fn openPath(allocator: *mem.Allocator, path: []const u8) !Elf { - @compileError("TODO implement"); - } - - /// Call close when done. - pub fn openFile(allocator: *mem.Allocator, file: File) !Elf { - @compileError("TODO implement"); - } - pub fn openStream( allocator: *mem.Allocator, seekable_stream: *io.SeekableStream(anyerror, anyerror), @@ -380,8 +370,8 @@ pub const Elf = struct { }; elf.endian = switch (try in.readByte()) { - 1 => builtin.Endian.Little, - 2 => builtin.Endian.Big, + 1 => .Little, + 2 => .Big, else => return error.InvalidFormat, }; @@ -554,6 +544,21 @@ pub const Elf = struct { }; pub const EI_NIDENT = 16; + +pub const EI_CLASS = 4; +pub const ELFCLASSNONE = 0; +pub const ELFCLASS32 = 1; +pub const ELFCLASS64 = 2; +pub const ELFCLASSNUM = 3; + +pub const EI_DATA = 5; +pub const ELFDATANONE = 0; +pub const ELFDATA2LSB = 1; +pub const ELFDATA2MSB = 2; +pub const ELFDATANUM = 3; + +pub const EI_VERSION = 6; + pub const Elf32_Half = u16; pub const Elf64_Half = u16; pub const Elf32_Word = u32; @@ -703,17 +708,11 @@ pub const Elf64_Rela = extern struct { }; pub const Elf32_Dyn = extern struct { d_tag: Elf32_Sword, - d_un: extern union { - d_val: Elf32_Word, - d_ptr: Elf32_Addr, - }, + d_val: Elf32_Addr, }; pub const Elf64_Dyn = extern struct { d_tag: Elf64_Sxword, - d_un: extern union { - d_val: Elf64_Xword, - d_ptr: Elf64_Addr, - }, + d_val: Elf64_Addr, }; pub const Elf32_Verdef = extern struct { vd_version: Elf32_Half, diff --git a/lib/std/event/channel.zig b/lib/std/event/channel.zig index fd70f73aab..3c5b48d047 100644 --- a/lib/std/event/channel.zig +++ b/lib/std/event/channel.zig @@ -273,7 +273,7 @@ test "std.event.Channel" { if (builtin.single_threaded) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/3251 - if (builtin.os == .freebsd) return error.SkipZigTest; + if (builtin.os.tag == .freebsd) return error.SkipZigTest; var channel: Channel(i32) = undefined; channel.init(&[0]i32{}); diff --git a/lib/std/event/future.zig b/lib/std/event/future.zig index 492582da75..51a63e90ee 100644 --- a/lib/std/event/future.zig +++ b/lib/std/event/future.zig @@ -86,7 +86,7 @@ test "std.event.Future" { // https://github.com/ziglang/zig/issues/1908 if (builtin.single_threaded) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/3251 - if (builtin.os == .freebsd) return error.SkipZigTest; + if (builtin.os.tag == .freebsd) return error.SkipZigTest; // TODO provide a way to run tests in evented I/O mode if (!std.io.is_async) return error.SkipZigTest; diff --git a/lib/std/event/lock.zig b/lib/std/event/lock.zig index e1b3495e5c..b9cbb5d95f 100644 --- a/lib/std/event/lock.zig +++ b/lib/std/event/lock.zig @@ -123,7 +123,7 @@ test "std.event.Lock" { if (builtin.single_threaded) return error.SkipZigTest; // TODO https://github.com/ziglang/zig/issues/3251 - if (builtin.os == .freebsd) return error.SkipZigTest; + if (builtin.os.tag == .freebsd) return error.SkipZigTest; var lock = Lock.init(); defer lock.deinit(); diff --git a/lib/std/event/loop.zig b/lib/std/event/loop.zig index 085e56fc15..80ba5a79b5 100644 --- a/lib/std/event/loop.zig +++ b/lib/std/event/loop.zig @@ -34,7 +34,7 @@ pub const Loop = struct { handle: anyframe, overlapped: Overlapped, - pub const overlapped_init = switch (builtin.os) { + pub const overlapped_init = switch (builtin.os.tag) { .windows => windows.OVERLAPPED{ .Internal = 0, .InternalHigh = 0, @@ -52,7 +52,7 @@ pub const Loop = struct { EventFd, }; - pub const EventFd = switch (builtin.os) { + pub const EventFd = switch (builtin.os.tag) { .macosx, .freebsd, .netbsd, .dragonfly => KEventFd, .linux => struct { base: ResumeNode, @@ -71,7 +71,7 @@ pub const Loop = struct { kevent: os.Kevent, }; - pub const Basic = switch (builtin.os) { + pub const Basic = switch (builtin.os.tag) { .macosx, .freebsd, .netbsd, .dragonfly => KEventBasic, .linux => struct { base: ResumeNode, @@ -173,7 +173,7 @@ pub const Loop = struct { const wakeup_bytes = [_]u8{0x1} ** 8; fn initOsData(self: *Loop, extra_thread_count: usize) InitOsDataError!void { - switch (builtin.os) { + switch (builtin.os.tag) { .linux => { self.os_data.fs_queue = std.atomic.Queue(Request).init(); self.os_data.fs_queue_item = 0; @@ -404,7 +404,7 @@ pub const Loop = struct { } fn deinitOsData(self: *Loop) void { - switch (builtin.os) { + switch (builtin.os.tag) { .linux => { noasync os.close(self.os_data.final_eventfd); while (self.available_eventfd_resume_nodes.pop()) |node| noasync os.close(node.data.eventfd); @@ -568,7 +568,7 @@ pub const Loop = struct { }; const eventfd_node = &resume_stack_node.data; eventfd_node.base.handle = next_tick_node.data; - switch (builtin.os) { + switch (builtin.os.tag) { .macosx, .freebsd, .netbsd, .dragonfly => { const kevent_array = @as(*const [1]os.Kevent, &eventfd_node.kevent); const empty_kevs = &[0]os.Kevent{}; @@ -628,7 +628,7 @@ pub const Loop = struct { self.workerRun(); - switch (builtin.os) { + switch (builtin.os.tag) { .linux, .macosx, .freebsd, @@ -678,7 +678,7 @@ pub const Loop = struct { const prev = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); if (prev == 1) { // cause all the threads to stop - switch (builtin.os) { + switch (builtin.os.tag) { .linux => { self.posixFsRequest(&self.os_data.fs_end_request); // writing 8 bytes to an eventfd cannot fail @@ -902,7 +902,7 @@ pub const Loop = struct { self.finishOneEvent(); } - switch (builtin.os) { + switch (builtin.os.tag) { .linux => { // only process 1 event so we don't steal from other threads var events: [1]os.linux.epoll_event = undefined; @@ -989,7 +989,7 @@ pub const Loop = struct { fn posixFsRequest(self: *Loop, request_node: *Request.Node) void { self.beginOneEvent(); // finished in posixFsRun after processing the msg self.os_data.fs_queue.put(request_node); - switch (builtin.os) { + switch (builtin.os.tag) { .macosx, .freebsd, .netbsd, .dragonfly => { const fs_kevs = @as(*const [1]os.Kevent, &self.os_data.fs_kevent_wake); const empty_kevs = &[0]os.Kevent{}; @@ -1018,7 +1018,7 @@ pub const Loop = struct { // https://github.com/ziglang/zig/issues/3157 fn posixFsRun(self: *Loop) void { while (true) { - if (builtin.os == .linux) { + if (builtin.os.tag == .linux) { @atomicStore(i32, &self.os_data.fs_queue_item, 0, .SeqCst); } while (self.os_data.fs_queue.get()) |node| { @@ -1053,7 +1053,7 @@ pub const Loop = struct { } self.finishOneEvent(); } - switch (builtin.os) { + switch (builtin.os.tag) { .linux => { const rc = os.linux.futex_wait(&self.os_data.fs_queue_item, os.linux.FUTEX_WAIT, 0, null); switch (os.linux.getErrno(rc)) { @@ -1071,7 +1071,7 @@ pub const Loop = struct { } } - const OsData = switch (builtin.os) { + const OsData = switch (builtin.os.tag) { .linux => LinuxOsData, .macosx, .freebsd, .netbsd, .dragonfly => KEventData, .windows => struct { diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index effb18babd..38371b3aea 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -414,10 +414,9 @@ pub fn formatType( if (max_depth == 0) { return output(context, "{ ... }"); } - comptime var field_i = 0; try output(context, "{"); - inline for (StructT.fields) |f| { - if (field_i == 0) { + inline for (StructT.fields) |f, i| { + if (i == 0) { try output(context, " ."); } else { try output(context, ", ."); @@ -425,7 +424,6 @@ pub fn formatType( try output(context, f.name); try output(context, " = "); try formatType(@field(value, f.name), fmt, options, context, Errors, output, max_depth - 1); - field_i += 1; } try output(context, " }"); }, @@ -443,10 +441,12 @@ pub fn formatType( else => return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }), }, .Many, .C => { + if (ptr_info.sentinel) |sentinel| { + return formatType(mem.span(value), fmt, options, context, Errors, output, max_depth); + } if (ptr_info.child == u8) { if (fmt.len > 0 and fmt[0] == 's') { - const len = mem.len(u8, value); - return formatText(value[0..len], fmt, options, context, Errors, output); + return formatText(mem.span(value), fmt, options, context, Errors, output); } } return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }); diff --git a/lib/std/fs.zig b/lib/std/fs.zig index c3d418eb8c..5077c52cd9 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -29,7 +29,7 @@ pub const Watch = @import("fs/watch.zig").Watch; /// All file system operations which return a path are guaranteed to /// fit into a UTF-8 encoded array of this length. /// The byte count includes room for a null sentinel byte. -pub const MAX_PATH_BYTES = switch (builtin.os) { +pub const MAX_PATH_BYTES = switch (builtin.os.tag) { .linux, .macosx, .ios, .freebsd, .netbsd, .dragonfly => os.PATH_MAX, // Each UTF-16LE character may be expanded to 3 UTF-8 bytes. // If it would require 4 UTF-8 bytes, then there would be a surrogate @@ -47,7 +47,7 @@ pub const base64_encoder = base64.Base64Encoder.init( /// Whether or not async file system syscalls need a dedicated thread because the operating /// system does not support non-blocking I/O on the file system. -pub const need_async_thread = std.io.is_async and switch (builtin.os) { +pub const need_async_thread = std.io.is_async and switch (builtin.os.tag) { .windows, .other => false, else => true, }; @@ -270,7 +270,7 @@ pub const AtomicFile = struct { assert(!self.finished); self.file.close(); self.finished = true; - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const dest_path_w = try os.windows.sliceToPrefixedFileW(self.dest_path); const tmp_path_w = try os.windows.cStrToPrefixedFileW(@ptrCast([*:0]u8, &self.tmp_path_buf)); return os.renameW(&tmp_path_w, &dest_path_w); @@ -394,7 +394,7 @@ pub const Dir = struct { const IteratorError = error{AccessDenied} || os.UnexpectedError; - pub const Iterator = switch (builtin.os) { + pub const Iterator = switch (builtin.os.tag) { .macosx, .ios, .freebsd, .netbsd, .dragonfly => struct { dir: Dir, seek: i64, @@ -409,7 +409,7 @@ pub const Dir = struct { /// Memory such as file names referenced in this returned entry becomes invalid /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized. pub fn next(self: *Self) Error!?Entry { - switch (builtin.os) { + switch (builtin.os.tag) { .macosx, .ios => return self.nextDarwin(), .freebsd, .netbsd, .dragonfly => return self.nextBsd(), else => @compileError("unimplemented"), @@ -644,7 +644,7 @@ pub const Dir = struct { }; pub fn iterate(self: Dir) Iterator { - switch (builtin.os) { + switch (builtin.os.tag) { .macosx, .ios, .freebsd, .netbsd, .dragonfly => return Iterator{ .dir = self, .seek = 0, @@ -710,7 +710,7 @@ pub const Dir = struct { /// Asserts that the path parameter has no null bytes. pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File { if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.openFileW(&path_w, flags); } @@ -720,7 +720,7 @@ pub const Dir = struct { /// Same as `openFile` but the path parameter is null-terminated. pub fn openFileC(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const path_w = try os.windows.cStrToPrefixedFileW(sub_path); return self.openFileW(&path_w, flags); } @@ -731,11 +731,18 @@ pub const Dir = struct { @as(u32, os.O_WRONLY) else @as(u32, os.O_RDONLY); - const fd = if (need_async_thread) + const fd = if (need_async_thread and !flags.always_blocking) try std.event.Loop.instance.?.openatZ(self.fd, sub_path, os_flags, 0) else try os.openatC(self.fd, sub_path, os_flags, 0); - return File{ .handle = fd, .io_mode = .blocking }; + return File{ + .handle = fd, + .io_mode = .blocking, + .async_block_allowed = if (flags.always_blocking) + File.async_block_allowed_yes + else + File.async_block_allowed_no, + }; } /// Same as `openFile` but Windows-only and the path parameter is @@ -753,7 +760,7 @@ pub const Dir = struct { /// Asserts that the path parameter has no null bytes. pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File { if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.createFileW(&path_w, flags); } @@ -763,7 +770,7 @@ pub const Dir = struct { /// Same as `createFile` but the path parameter is null-terminated. pub fn createFileC(self: Dir, sub_path_c: [*:0]const u8, flags: File.CreateFlags) File.OpenError!File { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const path_w = try os.windows.cStrToPrefixedFileW(sub_path_c); return self.createFileW(&path_w, flags); } @@ -894,7 +901,7 @@ pub const Dir = struct { /// Asserts that the path parameter has no null bytes. pub fn openDirTraverse(self: Dir, sub_path: []const u8) OpenError!Dir { if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.openDirTraverseW(&sub_path_w); } @@ -912,7 +919,7 @@ pub const Dir = struct { /// Asserts that the path parameter has no null bytes. pub fn openDirList(self: Dir, sub_path: []const u8) OpenError!Dir { if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.openDirListW(&sub_path_w); } @@ -923,7 +930,7 @@ pub const Dir = struct { /// Same as `openDirTraverse` except the parameter is null-terminated. pub fn openDirTraverseC(self: Dir, sub_path_c: [*:0]const u8) OpenError!Dir { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c); return self.openDirTraverseW(&sub_path_w); } else { @@ -934,7 +941,7 @@ pub const Dir = struct { /// Same as `openDirList` except the parameter is null-terminated. pub fn openDirListC(self: Dir, sub_path_c: [*:0]const u8) OpenError!Dir { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c); return self.openDirListW(&sub_path_w); } else { @@ -1076,7 +1083,7 @@ pub const Dir = struct { /// Asserts that the path parameter has no null bytes. pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void { if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.deleteDirW(&sub_path_w); } @@ -1333,7 +1340,7 @@ pub const Dir = struct { /// For example, instead of testing if a file exists and then opening it, just /// open it and handle the error for file not found. pub fn access(self: Dir, sub_path: []const u8, flags: File.OpenFlags) AccessError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.accessW(&sub_path_w, flags); } @@ -1343,7 +1350,7 @@ pub const Dir = struct { /// Same as `access` except the path parameter is null-terminated. pub fn accessZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) AccessError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path); return self.accessW(&sub_path_w, flags); } @@ -1374,7 +1381,7 @@ pub const Dir = struct { /// Closing the returned `Dir` is checked illegal behavior. Iterating over the result is illegal behavior. /// On POSIX targets, this function is comptime-callable. pub fn cwd() Dir { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle }; } else { return Dir{ .fd = os.AT_FDCWD }; @@ -1553,10 +1560,10 @@ pub fn readLinkC(pathname_c: [*]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 { pub const OpenSelfExeError = os.OpenError || os.windows.CreateFileError || SelfExePathError; pub fn openSelfExe() OpenSelfExeError!File { - if (builtin.os == .linux) { + if (builtin.os.tag == .linux) { return openFileAbsoluteC("/proc/self/exe", .{}); } - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const wide_slice = selfExePathW(); const prefixed_path_w = try os.windows.wToPrefixedFileW(wide_slice); return cwd().openReadW(&prefixed_path_w); @@ -1568,7 +1575,7 @@ pub fn openSelfExe() OpenSelfExeError!File { } test "openSelfExe" { - switch (builtin.os) { + switch (builtin.os.tag) { .linux, .macosx, .ios, .windows, .freebsd, .dragonfly => (try openSelfExe()).close(), else => return error.SkipZigTest, // Unsupported OS. } @@ -1593,7 +1600,7 @@ pub fn selfExePath(out_buffer: *[MAX_PATH_BYTES]u8) SelfExePathError![]u8 { if (rc != 0) return error.NameTooLong; return mem.toSlice(u8, @ptrCast([*:0]u8, out_buffer)); } - switch (builtin.os) { + switch (builtin.os.tag) { .linux => return os.readlinkC("/proc/self/exe", out_buffer), .freebsd, .dragonfly => { var mib = [4]c_int{ os.CTL_KERN, os.KERN_PROC, os.KERN_PROC_PATHNAME, -1 }; @@ -1635,7 +1642,7 @@ pub fn selfExeDirPathAlloc(allocator: *Allocator) ![]u8 { /// Get the directory path that contains the current executable. /// Returned value is a slice of out_buffer. pub fn selfExeDirPath(out_buffer: *[MAX_PATH_BYTES]u8) SelfExePathError![]const u8 { - if (builtin.os == .linux) { + if (builtin.os.tag == .linux) { // If the currently executing binary has been deleted, // the file path looks something like `/a/b/c/exe (deleted)` // This path cannot be opened, but it's valid for determining the directory diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index a3a428e77b..c243eeb62c 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -20,7 +20,7 @@ pub const File = struct { /// or, more specifically, whether the I/O is blocking. io_mode: io.Mode, - /// Even when std.io.mode is async, it is still sometimes desirable to perform blocking I/O, although + /// Even when 'std.io.mode' is async, it is still sometimes desirable to perform blocking I/O, although /// not by default. For example, when printing a stack trace to stderr. async_block_allowed: @TypeOf(async_block_allowed_no) = async_block_allowed_no, @@ -29,7 +29,7 @@ pub const File = struct { pub const Mode = os.mode_t; - pub const default_mode = switch (builtin.os) { + pub const default_mode = switch (builtin.os.tag) { .windows => 0, else => 0o666, }; @@ -40,6 +40,11 @@ pub const File = struct { pub const OpenFlags = struct { read: bool = true, write: bool = false, + + /// This prevents `O_NONBLOCK` from being passed even if `std.io.is_async`. + /// It allows the use of `noasync` when calling functions related to opening + /// the file, reading, and writing. + always_blocking: bool = false, }; /// TODO https://github.com/ziglang/zig/issues/3802 @@ -78,7 +83,7 @@ pub const File = struct { /// Test whether ANSI escape codes will be treated as such. pub fn supportsAnsiEscapeCodes(self: File) bool { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return os.isCygwinPty(self.handle); } if (self.isTty()) { @@ -123,7 +128,7 @@ pub const File = struct { /// TODO: integrate with async I/O pub fn getEndPos(self: File) GetPosError!u64 { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return windows.GetFileSizeEx(self.handle); } return (try self.stat()).size; @@ -133,7 +138,7 @@ pub const File = struct { /// TODO: integrate with async I/O pub fn mode(self: File) ModeError!Mode { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return {}; } return (try self.stat()).mode; @@ -157,7 +162,7 @@ pub const File = struct { /// TODO: integrate with async I/O pub fn stat(self: File) StatError!Stat { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { var io_status_block: windows.IO_STATUS_BLOCK = undefined; var info: windows.FILE_ALL_INFORMATION = undefined; const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation); @@ -204,7 +209,7 @@ pub const File = struct { /// last modification timestamp in nanoseconds mtime: i64, ) UpdateTimesError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const atime_ft = windows.nanoSecondsToFileTime(atime); const mtime_ft = windows.nanoSecondsToFileTime(mtime); return windows.SetFileTime(self.handle, null, &atime_ft, &mtime_ft); diff --git a/lib/std/fs/get_app_data_dir.zig b/lib/std/fs/get_app_data_dir.zig index 35c0265435..31aab590d8 100644 --- a/lib/std/fs/get_app_data_dir.zig +++ b/lib/std/fs/get_app_data_dir.zig @@ -13,7 +13,7 @@ pub const GetAppDataDirError = error{ /// Caller owns returned memory. /// TODO determine if we can remove the allocator requirement pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataDirError![]u8 { - switch (builtin.os) { + switch (builtin.os.tag) { .windows => { var dir_path_ptr: [*:0]u16 = undefined; switch (os.windows.shell32.SHGetKnownFolderPath( diff --git a/lib/std/fs/path.zig b/lib/std/fs/path.zig index 5d1c775629..35bc9b53b0 100644 --- a/lib/std/fs/path.zig +++ b/lib/std/fs/path.zig @@ -13,18 +13,18 @@ const process = std.process; pub const sep_windows = '\\'; pub const sep_posix = '/'; -pub const sep = if (builtin.os == .windows) sep_windows else sep_posix; +pub const sep = if (builtin.os.tag == .windows) sep_windows else sep_posix; pub const sep_str_windows = "\\"; pub const sep_str_posix = "/"; -pub const sep_str = if (builtin.os == .windows) sep_str_windows else sep_str_posix; +pub const sep_str = if (builtin.os.tag == .windows) sep_str_windows else sep_str_posix; pub const delimiter_windows = ';'; pub const delimiter_posix = ':'; -pub const delimiter = if (builtin.os == .windows) delimiter_windows else delimiter_posix; +pub const delimiter = if (builtin.os.tag == .windows) delimiter_windows else delimiter_posix; pub fn isSep(byte: u8) bool { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return byte == '/' or byte == '\\'; } else { return byte == '/'; @@ -74,7 +74,7 @@ fn joinSep(allocator: *Allocator, separator: u8, paths: []const []const u8) ![]u return buf; } -pub const join = if (builtin.os == .windows) joinWindows else joinPosix; +pub const join = if (builtin.os.tag == .windows) joinWindows else joinPosix; /// Naively combines a series of paths with the native path seperator. /// Allocates memory for the result, which must be freed by the caller. @@ -129,7 +129,7 @@ test "join" { } pub fn isAbsoluteC(path_c: [*:0]const u8) bool { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return isAbsoluteWindowsC(path_c); } else { return isAbsolutePosixC(path_c); @@ -137,7 +137,7 @@ pub fn isAbsoluteC(path_c: [*:0]const u8) bool { } pub fn isAbsolute(path: []const u8) bool { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return isAbsoluteWindows(path); } else { return isAbsolutePosix(path); @@ -318,7 +318,7 @@ test "windowsParsePath" { } pub fn diskDesignator(path: []const u8) []const u8 { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return diskDesignatorWindows(path); } else { return ""; @@ -383,7 +383,7 @@ fn asciiEqlIgnoreCase(s1: []const u8, s2: []const u8) bool { /// On Windows, this calls `resolveWindows` and on POSIX it calls `resolvePosix`. pub fn resolve(allocator: *Allocator, paths: []const []const u8) ![]u8 { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return resolveWindows(allocator, paths); } else { return resolvePosix(allocator, paths); @@ -400,7 +400,7 @@ pub fn resolve(allocator: *Allocator, paths: []const []const u8) ![]u8 { /// Without performing actual syscalls, resolving `..` could be incorrect. pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 { if (paths.len == 0) { - assert(builtin.os == .windows); // resolveWindows called on non windows can't use getCwd + assert(builtin.os.tag == .windows); // resolveWindows called on non windows can't use getCwd return process.getCwdAlloc(allocator); } @@ -495,7 +495,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 { result_disk_designator = result[0..result_index]; }, WindowsPath.Kind.None => { - assert(builtin.os == .windows); // resolveWindows called on non windows can't use getCwd + assert(builtin.os.tag == .windows); // resolveWindows called on non windows can't use getCwd const cwd = try process.getCwdAlloc(allocator); defer allocator.free(cwd); const parsed_cwd = windowsParsePath(cwd); @@ -510,7 +510,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 { }, } } else { - assert(builtin.os == .windows); // resolveWindows called on non windows can't use getCwd + assert(builtin.os.tag == .windows); // resolveWindows called on non windows can't use getCwd // TODO call get cwd for the result_disk_designator instead of the global one const cwd = try process.getCwdAlloc(allocator); defer allocator.free(cwd); @@ -581,7 +581,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 { /// Without performing actual syscalls, resolving `..` could be incorrect. pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 { if (paths.len == 0) { - assert(builtin.os != .windows); // resolvePosix called on windows can't use getCwd + assert(builtin.os.tag != .windows); // resolvePosix called on windows can't use getCwd return process.getCwdAlloc(allocator); } @@ -603,7 +603,7 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 { if (have_abs) { result = try allocator.alloc(u8, max_size); } else { - assert(builtin.os != .windows); // resolvePosix called on windows can't use getCwd + assert(builtin.os.tag != .windows); // resolvePosix called on windows can't use getCwd const cwd = try process.getCwdAlloc(allocator); defer allocator.free(cwd); result = try allocator.alloc(u8, max_size + cwd.len + 1); @@ -645,7 +645,7 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 { test "resolve" { const cwd = try process.getCwdAlloc(testing.allocator); defer testing.allocator.free(cwd); - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { if (windowsParsePath(cwd).kind == WindowsPath.Kind.Drive) { cwd[0] = asciiUpper(cwd[0]); } @@ -661,7 +661,7 @@ test "resolveWindows" { // TODO https://github.com/ziglang/zig/issues/3288 return error.SkipZigTest; } - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const cwd = try process.getCwdAlloc(testing.allocator); defer testing.allocator.free(cwd); const parsed_cwd = windowsParsePath(cwd); @@ -732,7 +732,7 @@ fn testResolvePosix(paths: []const []const u8, expected: []const u8) !void { /// If the path is a file in the current directory (no directory component) /// then returns null pub fn dirname(path: []const u8) ?[]const u8 { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return dirnameWindows(path); } else { return dirnamePosix(path); @@ -864,7 +864,7 @@ fn testDirnameWindows(input: []const u8, expected_output: ?[]const u8) void { } pub fn basename(path: []const u8) []const u8 { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return basenameWindows(path); } else { return basenamePosix(path); @@ -980,7 +980,7 @@ fn testBasenameWindows(input: []const u8, expected_output: []const u8) void { /// string is returned. /// On Windows this canonicalizes the drive to a capital letter and paths to `\\`. pub fn relative(allocator: *Allocator, from: []const u8, to: []const u8) ![]u8 { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return relativeWindows(allocator, from, to); } else { return relativePosix(allocator, from, to); diff --git a/lib/std/fs/watch.zig b/lib/std/fs/watch.zig index 0ff8c47ecf..1eb5a97ff1 100644 --- a/lib/std/fs/watch.zig +++ b/lib/std/fs/watch.zig @@ -42,7 +42,7 @@ pub fn Watch(comptime V: type) type { os_data: OsData, allocator: *Allocator, - const OsData = switch (builtin.os) { + const OsData = switch (builtin.os.tag) { // TODO https://github.com/ziglang/zig/issues/3778 .macosx, .freebsd, .netbsd, .dragonfly => KqOsData, .linux => LinuxOsData, @@ -121,7 +121,7 @@ pub fn Watch(comptime V: type) type { const self = try allocator.create(Self); errdefer allocator.destroy(self); - switch (builtin.os) { + switch (builtin.os.tag) { .linux => { const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC); errdefer os.close(inotify_fd); @@ -172,7 +172,7 @@ pub fn Watch(comptime V: type) type { /// All addFile calls and removeFile calls must have completed. pub fn deinit(self: *Self) void { - switch (builtin.os) { + switch (builtin.os.tag) { .macosx, .freebsd, .netbsd, .dragonfly => { // TODO we need to cancel the frames before destroying the lock self.os_data.table_lock.deinit(); @@ -223,7 +223,7 @@ pub fn Watch(comptime V: type) type { } pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V { - switch (builtin.os) { + switch (builtin.os.tag) { .macosx, .freebsd, .netbsd, .dragonfly => return addFileKEvent(self, file_path, value), .linux => return addFileLinux(self, file_path, value), .windows => return addFileWindows(self, file_path, value), diff --git a/lib/std/hash/auto_hash.zig b/lib/std/hash/auto_hash.zig index 1bd7dfc05b..2c6b37e3db 100644 --- a/lib/std/hash/auto_hash.zig +++ b/lib/std/hash/auto_hash.zig @@ -25,13 +25,13 @@ pub fn hashPointer(hasher: var, key: var, comptime strat: HashStrategy) void { const info = @typeInfo(@TypeOf(key)); switch (info.Pointer.size) { - builtin.TypeInfo.Pointer.Size.One => switch (strat) { + .One => switch (strat) { .Shallow => hash(hasher, @ptrToInt(key), .Shallow), .Deep => hash(hasher, key.*, .Shallow), .DeepRecursive => hash(hasher, key.*, .DeepRecursive), }, - builtin.TypeInfo.Pointer.Size.Slice => switch (strat) { + .Slice => switch (strat) { .Shallow => { hashPointer(hasher, key.ptr, .Shallow); hash(hasher, key.len, .Shallow); @@ -40,9 +40,7 @@ pub fn hashPointer(hasher: var, key: var, comptime strat: HashStrategy) void { .DeepRecursive => hashArray(hasher, key, .DeepRecursive), }, - builtin.TypeInfo.Pointer.Size.Many, - builtin.TypeInfo.Pointer.Size.C, - => switch (strat) { + .Many, .C, => switch (strat) { .Shallow => hash(hasher, @ptrToInt(key), .Shallow), else => @compileError( \\ unknown-length pointers and C pointers cannot be hashed deeply. diff --git a/lib/std/hash/benchmark.zig b/lib/std/hash/benchmark.zig index ed1bab9d87..4762b65afb 100644 --- a/lib/std/hash/benchmark.zig +++ b/lib/std/hash/benchmark.zig @@ -168,7 +168,7 @@ fn usage() void { } fn mode(comptime x: comptime_int) comptime_int { - return if (builtin.mode == builtin.Mode.Debug) x / 64 else x; + return if (builtin.mode == .Debug) x / 64 else x; } pub fn main() !void { diff --git a/lib/std/hash/cityhash.zig b/lib/std/hash/cityhash.zig index 18b3abe16d..a717303090 100644 --- a/lib/std/hash/cityhash.zig +++ b/lib/std/hash/cityhash.zig @@ -11,7 +11,7 @@ pub const CityHash32 = struct { fn fetch32(ptr: [*]const u8) u32 { var v: u32 = undefined; @memcpy(@ptrCast([*]u8, &v), ptr, 4); - if (builtin.endian == builtin.Endian.Big) + if (builtin.endian == .Big) return @byteSwap(u32, v); return v; } @@ -174,7 +174,7 @@ pub const CityHash64 = struct { fn fetch32(ptr: [*]const u8) u32 { var v: u32 = undefined; @memcpy(@ptrCast([*]u8, &v), ptr, 4); - if (builtin.endian == builtin.Endian.Big) + if (builtin.endian == .Big) return @byteSwap(u32, v); return v; } @@ -182,7 +182,7 @@ pub const CityHash64 = struct { fn fetch64(ptr: [*]const u8) u64 { var v: u64 = undefined; @memcpy(@ptrCast([*]u8, &v), ptr, 8); - if (builtin.endian == builtin.Endian.Big) + if (builtin.endian == .Big) return @byteSwap(u64, v); return v; } @@ -369,7 +369,7 @@ fn SMHasherTest(comptime hash_fn: var, comptime hashbits: u32) u32 { key[i] = @intCast(u8, i); var h = hash_fn(key[0..i], 256 - i); - if (builtin.endian == builtin.Endian.Big) + if (builtin.endian == .Big) h = @byteSwap(@TypeOf(h), h); @memcpy(@ptrCast([*]u8, &hashes[i * hashbytes]), @ptrCast([*]u8, &h), hashbytes); } diff --git a/lib/std/hash/murmur.zig b/lib/std/hash/murmur.zig index 23b11ef284..96efc8b9c1 100644 --- a/lib/std/hash/murmur.zig +++ b/lib/std/hash/murmur.zig @@ -17,7 +17,7 @@ pub const Murmur2_32 = struct { var h1: u32 = seed ^ len; for (@ptrCast([*]align(1) const u32, str.ptr)[0..(len >> 2)]) |v| { var k1: u32 = v; - if (builtin.endian == builtin.Endian.Big) + if (builtin.endian == .Big) k1 = @byteSwap(u32, k1); k1 *%= m; k1 ^= k1 >> 24; @@ -102,7 +102,7 @@ pub const Murmur2_64 = struct { var h1: u64 = seed ^ (len *% m); for (@ptrCast([*]align(1) const u64, str.ptr)[0..@intCast(usize, len >> 3)]) |v| { var k1: u64 = v; - if (builtin.endian == builtin.Endian.Big) + if (builtin.endian == .Big) k1 = @byteSwap(u64, k1); k1 *%= m; k1 ^= k1 >> 47; @@ -115,7 +115,7 @@ pub const Murmur2_64 = struct { if (rest > 0) { var k1: u64 = 0; @memcpy(@ptrCast([*]u8, &k1), @ptrCast([*]const u8, &str[@intCast(usize, offset)]), @intCast(usize, rest)); - if (builtin.endian == builtin.Endian.Big) + if (builtin.endian == .Big) k1 = @byteSwap(u64, k1); h1 ^= k1; h1 *%= m; @@ -182,7 +182,7 @@ pub const Murmur3_32 = struct { var h1: u32 = seed; for (@ptrCast([*]align(1) const u32, str.ptr)[0..(len >> 2)]) |v| { var k1: u32 = v; - if (builtin.endian == builtin.Endian.Big) + if (builtin.endian == .Big) k1 = @byteSwap(u32, k1); k1 *%= c1; k1 = rotl32(k1, 15); @@ -294,7 +294,7 @@ fn SMHasherTest(comptime hash_fn: var, comptime hashbits: u32) u32 { key[i] = @truncate(u8, i); var h = hash_fn(key[0..i], 256 - i); - if (builtin.endian == builtin.Endian.Big) + if (builtin.endian == .Big) h = @byteSwap(@TypeOf(h), h); @memcpy(@ptrCast([*]u8, &hashes[i * hashbytes]), @ptrCast([*]u8, &h), hashbytes); } @@ -308,7 +308,7 @@ test "murmur2_32" { var v1: u64 = 0x1234567812345678; var v0le: u32 = v0; var v1le: u64 = v1; - if (builtin.endian == builtin.Endian.Big) { + if (builtin.endian == .Big) { v0le = @byteSwap(u32, v0le); v1le = @byteSwap(u64, v1le); } @@ -322,7 +322,7 @@ test "murmur2_64" { var v1: u64 = 0x1234567812345678; var v0le: u32 = v0; var v1le: u64 = v1; - if (builtin.endian == builtin.Endian.Big) { + if (builtin.endian == .Big) { v0le = @byteSwap(u32, v0le); v1le = @byteSwap(u64, v1le); } @@ -336,7 +336,7 @@ test "murmur3_32" { var v1: u64 = 0x1234567812345678; var v0le: u32 = v0; var v1le: u64 = v1; - if (builtin.endian == builtin.Endian.Big) { + if (builtin.endian == .Big) { v0le = @byteSwap(u32, v0le); v1le = @byteSwap(u64, v1le); } diff --git a/lib/std/hash_map.zig b/lib/std/hash_map.zig index 80ca218df6..4b3e082f0c 100644 --- a/lib/std/hash_map.zig +++ b/lib/std/hash_map.zig @@ -10,7 +10,7 @@ const Wyhash = std.hash.Wyhash; const Allocator = mem.Allocator; const builtin = @import("builtin"); -const want_modification_safety = builtin.mode != builtin.Mode.ReleaseFast; +const want_modification_safety = builtin.mode != .ReleaseFast; const debug_u32 = if (want_modification_safety) u32 else void; pub fn AutoHashMap(comptime K: type, comptime V: type) type { diff --git a/lib/std/heap.zig b/lib/std/heap.zig index 4295f1393d..65809e97b4 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -36,7 +36,7 @@ fn cShrink(self: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new /// Thread-safe and lock-free. pub const page_allocator = if (std.Target.current.isWasm()) &wasm_page_allocator_state -else if (std.Target.current.getOs() == .freestanding) +else if (std.Target.current.os.tag == .freestanding) root.os.heap.page_allocator else &page_allocator_state; @@ -57,7 +57,7 @@ const PageAllocator = struct { fn alloc(allocator: *Allocator, n: usize, alignment: u29) error{OutOfMemory}![]u8 { if (n == 0) return &[0]u8{}; - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const w = os.windows; // Although officially it's at least aligned to page boundary, @@ -143,7 +143,7 @@ const PageAllocator = struct { fn shrink(allocator: *Allocator, old_mem_unaligned: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { const old_mem = @alignCast(mem.page_size, old_mem_unaligned); - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const w = os.windows; if (new_size == 0) { // From the docs: @@ -183,7 +183,7 @@ const PageAllocator = struct { fn realloc(allocator: *Allocator, old_mem_unaligned: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { const old_mem = @alignCast(mem.page_size, old_mem_unaligned); - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { if (old_mem.len == 0) { return alloc(allocator, new_size, new_align); } @@ -412,7 +412,7 @@ const WasmPageAllocator = struct { } }; -pub const HeapAllocator = switch (builtin.os) { +pub const HeapAllocator = switch (builtin.os.tag) { .windows => struct { allocator: Allocator, heap_handle: ?HeapHandle, @@ -855,7 +855,7 @@ test "PageAllocator" { try testAllocatorAlignedShrink(allocator); } - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { // Trying really large alignment. As mentionned in the implementation, // VirtualAlloc returns 64K aligned addresses. We want to make sure // PageAllocator works beyond that, as it's not tested by @@ -868,7 +868,7 @@ test "PageAllocator" { } test "HeapAllocator" { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { var heap_allocator = HeapAllocator.init(); defer heap_allocator.deinit(); diff --git a/lib/std/io.zig b/lib/std/io.zig index 548f119b4f..d0bf26d548 100644 --- a/lib/std/io.zig +++ b/lib/std/io.zig @@ -35,7 +35,7 @@ else pub const is_async = mode != .blocking; fn getStdOutHandle() os.fd_t { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return os.windows.peb().ProcessParameters.hStdOutput; } @@ -54,7 +54,7 @@ pub fn getStdOut() File { } fn getStdErrHandle() os.fd_t { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return os.windows.peb().ProcessParameters.hStdError; } @@ -74,7 +74,7 @@ pub fn getStdErr() File { } fn getStdInHandle() os.fd_t { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return os.windows.peb().ProcessParameters.hStdInput; } @@ -348,11 +348,11 @@ pub fn BitInStream(endian: builtin.Endian, comptime Error: type) type { const n = if (self.bit_count >= bits) @intCast(u3, bits) else self.bit_count; const shift = u7_bit_count - n; switch (endian) { - builtin.Endian.Big => { + .Big => { out_buffer = @as(Buf, self.bit_buffer >> shift); self.bit_buffer <<= n; }, - builtin.Endian.Little => { + .Little => { const value = (self.bit_buffer << shift) >> shift; out_buffer = @as(Buf, value); self.bit_buffer >>= n; @@ -376,7 +376,7 @@ pub fn BitInStream(endian: builtin.Endian, comptime Error: type) type { }; switch (endian) { - builtin.Endian.Big => { + .Big => { if (n >= u8_bit_count) { out_buffer <<= @intCast(u3, u8_bit_count - 1); out_buffer <<= 1; @@ -392,7 +392,7 @@ pub fn BitInStream(endian: builtin.Endian, comptime Error: type) type { self.bit_buffer = @truncate(u7, next_byte << @intCast(u3, n - 1)); self.bit_count = shift; }, - builtin.Endian.Little => { + .Little => { if (n >= u8_bit_count) { out_buffer |= @as(Buf, next_byte) << @intCast(BufShift, out_bits.*); out_bits.* += u8_bit_count; @@ -666,8 +666,8 @@ pub fn BitOutStream(endian: builtin.Endian, comptime Error: type) type { const high_byte_shift = @intCast(BufShift, buf_bit_count - u8_bit_count); var in_buffer = switch (endian) { - builtin.Endian.Big => buf_value << @intCast(BufShift, buf_bit_count - bits), - builtin.Endian.Little => buf_value, + .Big => buf_value << @intCast(BufShift, buf_bit_count - bits), + .Little => buf_value, }; var in_bits = bits; @@ -675,13 +675,13 @@ pub fn BitOutStream(endian: builtin.Endian, comptime Error: type) type { const bits_remaining = u8_bit_count - self.bit_count; const n = @intCast(u3, if (bits_remaining > bits) bits else bits_remaining); switch (endian) { - builtin.Endian.Big => { + .Big => { const shift = @intCast(BufShift, high_byte_shift + self.bit_count); const v = @intCast(u8, in_buffer >> shift); self.bit_buffer |= v; in_buffer <<= n; }, - builtin.Endian.Little => { + .Little => { const v = @truncate(u8, in_buffer) << @intCast(u3, self.bit_count); self.bit_buffer |= v; in_buffer >>= n; @@ -701,13 +701,13 @@ pub fn BitOutStream(endian: builtin.Endian, comptime Error: type) type { //copy bytes until we can't fill one anymore, then leave the rest in bit_buffer while (in_bits >= u8_bit_count) { switch (endian) { - builtin.Endian.Big => { + .Big => { const v = @intCast(u8, in_buffer >> high_byte_shift); try self.out_stream.writeByte(v); in_buffer <<= @intCast(u3, u8_bit_count - 1); in_buffer <<= 1; }, - builtin.Endian.Little => { + .Little => { const v = @truncate(u8, in_buffer); try self.out_stream.writeByte(v); in_buffer >>= @intCast(u3, u8_bit_count - 1); @@ -720,8 +720,8 @@ pub fn BitOutStream(endian: builtin.Endian, comptime Error: type) type { if (in_bits > 0) { self.bit_count = @intCast(u4, in_bits); self.bit_buffer = switch (endian) { - builtin.Endian.Big => @truncate(u8, in_buffer >> high_byte_shift), - builtin.Endian.Little => @truncate(u8, in_buffer), + .Big => @truncate(u8, in_buffer >> high_byte_shift), + .Little => @truncate(u8, in_buffer), }; } } @@ -858,10 +858,10 @@ pub fn Deserializer(comptime endian: builtin.Endian, comptime packing: Packing, var result = @as(U, 0); for (buffer) |byte, i| { switch (endian) { - builtin.Endian.Big => { + .Big => { result = (result << u8_bit_count) | byte; }, - builtin.Endian.Little => { + .Little => { result |= @as(U, byte) << @intCast(Log2U, u8_bit_count * i); }, } diff --git a/lib/std/io/seekable_stream.zig b/lib/std/io/seekable_stream.zig index 48dc31b785..052abbc856 100644 --- a/lib/std/io/seekable_stream.zig +++ b/lib/std/io/seekable_stream.zig @@ -73,7 +73,7 @@ pub const SliceSeekableInStream = struct { fn seekToFn(in_stream: *SeekableInStream, pos: u64) SeekError!void { const self = @fieldParentPtr(Self, "seekable_stream", in_stream); const usize_pos = @intCast(usize, pos); - if (usize_pos >= self.slice.len) return error.EndOfStream; + if (usize_pos > self.slice.len) return error.EndOfStream; self.pos = usize_pos; } @@ -86,7 +86,7 @@ pub const SliceSeekableInStream = struct { self.pos -= abs_amt; } else { const usize_amt = @intCast(usize, amt); - if (self.pos + usize_amt >= self.slice.len) return error.EndOfStream; + if (self.pos + usize_amt > self.slice.len) return error.EndOfStream; self.pos += usize_amt; } } diff --git a/lib/std/macho.zig b/lib/std/macho.zig index e1bbd755c6..a499a93675 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -24,6 +24,17 @@ pub const load_command = extern struct { cmdsize: u32, }; +pub const uuid_command = extern struct { + /// LC_UUID + cmd: u32, + + /// sizeof(struct uuid_command) + cmdsize: u32, + + /// the 128-bit uuid + uuid: [16]u8, +}; + /// The symtab_command contains the offsets and sizes of the link-edit 4.3BSD /// "stab" style symbol table information as described in the header files /// and . diff --git a/lib/std/math/pow.zig b/lib/std/math/pow.zig index 4f623377e6..ac6aee4cce 100644 --- a/lib/std/math/pow.zig +++ b/lib/std/math/pow.zig @@ -32,7 +32,7 @@ const expect = std.testing.expect; /// - pow(-inf, y) = pow(-0, -y) /// - pow(x, y) = nan for finite x < 0 and finite non-integer y pub fn pow(comptime T: type, x: T, y: T) T { - if (@typeInfo(T) == builtin.TypeId.Int) { + if (@typeInfo(T) == .Int) { return math.powi(T, x, y) catch unreachable; } diff --git a/lib/std/math/powi.zig b/lib/std/math/powi.zig index d80700e5cd..ce3a3713e3 100644 --- a/lib/std/math/powi.zig +++ b/lib/std/math/powi.zig @@ -25,7 +25,7 @@ pub fn powi(comptime T: type, x: T, y: T) (error{ }!T) { const info = @typeInfo(T); - comptime assert(@typeInfo(T) == builtin.TypeId.Int); + comptime assert(@typeInfo(T) == .Int); // powi(x, +-0) = 1 for any x if (y == 0 or y == -0) { diff --git a/lib/std/mem.zig b/lib/std/mem.zig index a4b48bbc1c..391d587dbc 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -333,8 +333,20 @@ pub fn zeroes(comptime T: type) T { } return array; }, - .Vector, .ErrorUnion, .ErrorSet, .Union, .Fn, .BoundFn, .Type, .NoReturn, .Undefined, .Opaque, .Frame, .AnyFrame, => { - @compileError("Can't set a "++ @typeName(T) ++" to zero."); + .Vector, + .ErrorUnion, + .ErrorSet, + .Union, + .Fn, + .BoundFn, + .Type, + .NoReturn, + .Undefined, + .Opaque, + .Frame, + .AnyFrame, + => { + @compileError("Can't set a " ++ @typeName(T) ++ " to zero."); }, } } @@ -470,18 +482,115 @@ pub fn eql(comptime T: type, a: []const T, b: []const T) bool { return true; } -pub fn len(comptime T: type, ptr: [*:0]const T) usize { - var count: usize = 0; - while (ptr[count] != 0) : (count += 1) {} - return count; -} - +/// Deprecated. Use `span`. pub fn toSliceConst(comptime T: type, ptr: [*:0]const T) [:0]const T { - return ptr[0..len(T, ptr) :0]; + return ptr[0..len(ptr) :0]; } +/// Deprecated. Use `span`. pub fn toSlice(comptime T: type, ptr: [*:0]T) [:0]T { - return ptr[0..len(T, ptr) :0]; + return ptr[0..len(ptr) :0]; +} + +/// Takes a pointer to an array, a sentinel-terminated pointer, or a slice, and +/// returns a slice. If there is a sentinel on the input type, there will be a +/// sentinel on the output type. The constness of the output type matches +/// the constness of the input type. `[*c]` pointers are assumed to be 0-terminated, +/// and assumed to not allow null. +pub fn Span(comptime T: type) type { + var ptr_info = @typeInfo(T).Pointer; + switch (ptr_info.size) { + .One => switch (@typeInfo(ptr_info.child)) { + .Array => |info| { + ptr_info.child = info.child; + ptr_info.sentinel = info.sentinel; + }, + else => @compileError("invalid type given to std.mem.Span"), + }, + .C => { + ptr_info.sentinel = 0; + ptr_info.is_allowzero = false; + }, + .Many, .Slice => {}, + } + ptr_info.size = .Slice; + return @Type(std.builtin.TypeInfo{ .Pointer = ptr_info }); +} + +test "Span" { + testing.expect(Span(*[5]u16) == []u16); + testing.expect(Span(*const [5]u16) == []const u16); + testing.expect(Span([]u16) == []u16); + testing.expect(Span([]const u8) == []const u8); + testing.expect(Span([:1]u16) == [:1]u16); + testing.expect(Span([:1]const u8) == [:1]const u8); + testing.expect(Span([*:1]u16) == [:1]u16); + testing.expect(Span([*:1]const u8) == [:1]const u8); + testing.expect(Span([*c]u16) == [:0]u16); + testing.expect(Span([*c]const u8) == [:0]const u8); +} + +/// Takes a pointer to an array, a sentinel-terminated pointer, or a slice, and +/// returns a slice. If there is a sentinel on the input type, there will be a +/// sentinel on the output type. The constness of the output type matches +/// the constness of the input type. +pub fn span(ptr: var) Span(@TypeOf(ptr)) { + const Result = Span(@TypeOf(ptr)); + const l = len(ptr); + if (@typeInfo(Result).Pointer.sentinel) |s| { + return ptr[0..l :s]; + } else { + return ptr[0..l]; + } +} + +test "span" { + var array: [5]u16 = [_]u16{ 1, 2, 3, 4, 5 }; + const ptr = array[0..2 :3].ptr; + testing.expect(eql(u16, span(ptr), &[_]u16{ 1, 2 })); + testing.expect(eql(u16, span(&array), &[_]u16{ 1, 2, 3, 4, 5 })); +} + +/// Takes a pointer to an array, an array, a sentinel-terminated pointer, +/// or a slice, and returns the length. +pub fn len(ptr: var) usize { + return switch (@typeInfo(@TypeOf(ptr))) { + .Array => |info| info.len, + .Pointer => |info| switch (info.size) { + .One => switch (@typeInfo(info.child)) { + .Array => |x| x.len, + else => @compileError("invalid type given to std.mem.length"), + }, + .Many => if (info.sentinel) |sentinel| + indexOfSentinel(info.child, sentinel, ptr) + else + @compileError("length of pointer with no sentinel"), + .C => indexOfSentinel(info.child, 0, ptr), + .Slice => ptr.len, + }, + else => @compileError("invalid type given to std.mem.length"), + }; +} + +test "len" { + testing.expect(len("aoeu") == 4); + + { + var array: [5]u16 = [_]u16{ 1, 2, 3, 4, 5 }; + testing.expect(len(&array) == 5); + testing.expect(len(array[0..3]) == 3); + array[2] = 0; + const ptr = array[0..2 :0].ptr; + testing.expect(len(ptr) == 2); + } +} + +pub fn indexOfSentinel(comptime Elem: type, comptime sentinel: Elem, ptr: [*:sentinel]const Elem) usize { + var i: usize = 0; + while (ptr[i] != sentinel) { + i += 1; + } + return i; } /// Returns true if all elements in a slice are equal to the scalar value provided @@ -637,12 +746,12 @@ test "mem.indexOf" { pub fn readVarInt(comptime ReturnType: type, bytes: []const u8, endian: builtin.Endian) ReturnType { var result: ReturnType = 0; switch (endian) { - builtin.Endian.Big => { + .Big => { for (bytes) |b| { result = (result << 8) | b; } }, - builtin.Endian.Little => { + .Little => { const ShiftType = math.Log2Int(ReturnType); for (bytes) |b, index| { result = result | (@as(ReturnType, b) << @intCast(ShiftType, index * 8)); @@ -670,13 +779,13 @@ pub fn readIntForeign(comptime T: type, bytes: *const [@divExact(T.bit_count, 8) } pub const readIntLittle = switch (builtin.endian) { - builtin.Endian.Little => readIntNative, - builtin.Endian.Big => readIntForeign, + .Little => readIntNative, + .Big => readIntForeign, }; pub const readIntBig = switch (builtin.endian) { - builtin.Endian.Little => readIntForeign, - builtin.Endian.Big => readIntNative, + .Little => readIntForeign, + .Big => readIntNative, }; /// Asserts that bytes.len >= T.bit_count / 8. Reads the integer starting from index 0 @@ -700,13 +809,13 @@ pub fn readIntSliceForeign(comptime T: type, bytes: []const u8) T { } pub const readIntSliceLittle = switch (builtin.endian) { - builtin.Endian.Little => readIntSliceNative, - builtin.Endian.Big => readIntSliceForeign, + .Little => readIntSliceNative, + .Big => readIntSliceForeign, }; pub const readIntSliceBig = switch (builtin.endian) { - builtin.Endian.Little => readIntSliceForeign, - builtin.Endian.Big => readIntSliceNative, + .Little => readIntSliceForeign, + .Big => readIntSliceNative, }; /// Reads an integer from memory with bit count specified by T. @@ -783,13 +892,13 @@ pub fn writeIntForeign(comptime T: type, buf: *[@divExact(T.bit_count, 8)]u8, va } pub const writeIntLittle = switch (builtin.endian) { - builtin.Endian.Little => writeIntNative, - builtin.Endian.Big => writeIntForeign, + .Little => writeIntNative, + .Big => writeIntForeign, }; pub const writeIntBig = switch (builtin.endian) { - builtin.Endian.Little => writeIntForeign, - builtin.Endian.Big => writeIntNative, + .Little => writeIntForeign, + .Big => writeIntNative, }; /// Writes an integer to memory, storing it in twos-complement. @@ -841,13 +950,13 @@ pub fn writeIntSliceBig(comptime T: type, buffer: []u8, value: T) void { } pub const writeIntSliceNative = switch (builtin.endian) { - builtin.Endian.Little => writeIntSliceLittle, - builtin.Endian.Big => writeIntSliceBig, + .Little => writeIntSliceLittle, + .Big => writeIntSliceBig, }; pub const writeIntSliceForeign = switch (builtin.endian) { - builtin.Endian.Little => writeIntSliceBig, - builtin.Endian.Big => writeIntSliceLittle, + .Little => writeIntSliceBig, + .Big => writeIntSliceLittle, }; /// Writes a twos-complement integer to memory, with the specified endianness. @@ -858,10 +967,10 @@ pub const writeIntSliceForeign = switch (builtin.endian) { /// use writeInt instead. pub fn writeIntSlice(comptime T: type, buffer: []u8, value: T, endian: builtin.Endian) void { comptime assert(T.bit_count % 8 == 0); - switch (endian) { - builtin.Endian.Little => return writeIntSliceLittle(T, buffer, value), - builtin.Endian.Big => return writeIntSliceBig(T, buffer, value), - } + return switch (endian) { + .Little => writeIntSliceLittle(T, buffer, value), + .Big => writeIntSliceBig(T, buffer, value), + }; } test "writeIntBig and writeIntLittle" { @@ -1397,54 +1506,54 @@ test "rotate" { /// Converts a little-endian integer to host endianness. pub fn littleToNative(comptime T: type, x: T) T { return switch (builtin.endian) { - builtin.Endian.Little => x, - builtin.Endian.Big => @byteSwap(T, x), + .Little => x, + .Big => @byteSwap(T, x), }; } /// Converts a big-endian integer to host endianness. pub fn bigToNative(comptime T: type, x: T) T { return switch (builtin.endian) { - builtin.Endian.Little => @byteSwap(T, x), - builtin.Endian.Big => x, + .Little => @byteSwap(T, x), + .Big => x, }; } /// Converts an integer from specified endianness to host endianness. pub fn toNative(comptime T: type, x: T, endianness_of_x: builtin.Endian) T { return switch (endianness_of_x) { - builtin.Endian.Little => littleToNative(T, x), - builtin.Endian.Big => bigToNative(T, x), + .Little => littleToNative(T, x), + .Big => bigToNative(T, x), }; } /// Converts an integer which has host endianness to the desired endianness. pub fn nativeTo(comptime T: type, x: T, desired_endianness: builtin.Endian) T { return switch (desired_endianness) { - builtin.Endian.Little => nativeToLittle(T, x), - builtin.Endian.Big => nativeToBig(T, x), + .Little => nativeToLittle(T, x), + .Big => nativeToBig(T, x), }; } /// Converts an integer which has host endianness to little endian. pub fn nativeToLittle(comptime T: type, x: T) T { return switch (builtin.endian) { - builtin.Endian.Little => x, - builtin.Endian.Big => @byteSwap(T, x), + .Little => x, + .Big => @byteSwap(T, x), }; } /// Converts an integer which has host endianness to big endian. pub fn nativeToBig(comptime T: type, x: T) T { return switch (builtin.endian) { - builtin.Endian.Little => @byteSwap(T, x), - builtin.Endian.Big => x, + .Little => @byteSwap(T, x), + .Big => x, }; } fn AsBytesReturnType(comptime P: type) type { if (comptime !trait.isSingleItemPtr(P)) - @compileError("expected single item " ++ "pointer, passed " ++ @typeName(P)); + @compileError("expected single item pointer, passed " ++ @typeName(P)); const size = @as(usize, @sizeOf(meta.Child(P))); const alignment = comptime meta.alignment(P); @@ -1469,8 +1578,8 @@ pub fn asBytes(ptr: var) AsBytesReturnType(@TypeOf(ptr)) { test "asBytes" { const deadbeef = @as(u32, 0xDEADBEEF); const deadbeef_bytes = switch (builtin.endian) { - builtin.Endian.Big => "\xDE\xAD\xBE\xEF", - builtin.Endian.Little => "\xEF\xBE\xAD\xDE", + .Big => "\xDE\xAD\xBE\xEF", + .Little => "\xEF\xBE\xAD\xDE", }; testing.expect(eql(u8, asBytes(&deadbeef), deadbeef_bytes)); @@ -1508,21 +1617,21 @@ pub fn toBytes(value: var) [@sizeOf(@TypeOf(value))]u8 { test "toBytes" { var my_bytes = toBytes(@as(u32, 0x12345678)); switch (builtin.endian) { - builtin.Endian.Big => testing.expect(eql(u8, &my_bytes, "\x12\x34\x56\x78")), - builtin.Endian.Little => testing.expect(eql(u8, &my_bytes, "\x78\x56\x34\x12")), + .Big => testing.expect(eql(u8, &my_bytes, "\x12\x34\x56\x78")), + .Little => testing.expect(eql(u8, &my_bytes, "\x78\x56\x34\x12")), } my_bytes[0] = '\x99'; switch (builtin.endian) { - builtin.Endian.Big => testing.expect(eql(u8, &my_bytes, "\x99\x34\x56\x78")), - builtin.Endian.Little => testing.expect(eql(u8, &my_bytes, "\x99\x56\x34\x12")), + .Big => testing.expect(eql(u8, &my_bytes, "\x99\x34\x56\x78")), + .Little => testing.expect(eql(u8, &my_bytes, "\x99\x56\x34\x12")), } } fn BytesAsValueReturnType(comptime T: type, comptime B: type) type { const size = @as(usize, @sizeOf(T)); - if (comptime !trait.is(builtin.TypeId.Pointer)(B) or + if (comptime !trait.is(.Pointer)(B) or (meta.Child(B) != [size]u8 and meta.Child(B) != [size:0]u8)) { @compileError("expected *[N]u8 " ++ ", passed " ++ @typeName(B)); @@ -1542,15 +1651,15 @@ pub fn bytesAsValue(comptime T: type, bytes: var) BytesAsValueReturnType(T, @Typ test "bytesAsValue" { const deadbeef = @as(u32, 0xDEADBEEF); const deadbeef_bytes = switch (builtin.endian) { - builtin.Endian.Big => "\xDE\xAD\xBE\xEF", - builtin.Endian.Little => "\xEF\xBE\xAD\xDE", + .Big => "\xDE\xAD\xBE\xEF", + .Little => "\xEF\xBE\xAD\xDE", }; testing.expect(deadbeef == bytesAsValue(u32, deadbeef_bytes).*); var codeface_bytes: [4]u8 = switch (builtin.endian) { - builtin.Endian.Big => "\xC0\xDE\xFA\xCE", - builtin.Endian.Little => "\xCE\xFA\xDE\xC0", + .Big => "\xC0\xDE\xFA\xCE", + .Little => "\xCE\xFA\xDE\xC0", }.*; var codeface = bytesAsValue(u32, &codeface_bytes); testing.expect(codeface.* == 0xC0DEFACE); @@ -1583,8 +1692,8 @@ pub fn bytesToValue(comptime T: type, bytes: var) T { } test "bytesToValue" { const deadbeef_bytes = switch (builtin.endian) { - builtin.Endian.Big => "\xDE\xAD\xBE\xEF", - builtin.Endian.Little => "\xEF\xBE\xAD\xDE", + .Big => "\xDE\xAD\xBE\xEF", + .Little => "\xEF\xBE\xAD\xDE", }; const deadbeef = bytesToValue(u32, deadbeef_bytes); @@ -1753,8 +1862,13 @@ fn SubArrayPtrReturnType(comptime T: type, comptime length: usize) type { return *[length]meta.Child(meta.Child(T)); } -///Given a pointer to an array, returns a pointer to a portion of that array, preserving constness. -pub fn subArrayPtr(ptr: var, comptime start: usize, comptime length: usize) SubArrayPtrReturnType(@TypeOf(ptr), length) { +/// Given a pointer to an array, returns a pointer to a portion of that array, preserving constness. +/// TODO this will be obsoleted by https://github.com/ziglang/zig/issues/863 +pub fn subArrayPtr( + ptr: var, + comptime start: usize, + comptime length: usize, +) SubArrayPtrReturnType(@TypeOf(ptr), length) { assert(start + length <= ptr.*.len); const ReturnType = SubArrayPtrReturnType(@TypeOf(ptr), length); diff --git a/lib/std/meta.zig b/lib/std/meta.zig index 58fd6b9da7..65809abb5c 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -115,6 +115,32 @@ test "std.meta.Child" { testing.expect(Child(?u8) == u8); } +/// Given a type with a sentinel e.g. `[:0]u8`, returns the sentinel +pub fn Sentinel(comptime T: type) Child(T) { + // comptime asserts that ptr has a sentinel + switch (@typeInfo(T)) { + .Array => |arrayInfo| { + return comptime arrayInfo.sentinel.?; + }, + .Pointer => |ptrInfo| { + switch (ptrInfo.size) { + .Many, .Slice => { + return comptime ptrInfo.sentinel.?; + }, + else => {}, + } + }, + else => {}, + } + @compileError("not a sentinel type, found '" ++ @typeName(T) ++ "'"); +} + +test "std.meta.Sentinel" { + testing.expectEqual(@as(u8, 0), Sentinel([:0]u8)); + testing.expectEqual(@as(u8, 0), Sentinel([*:0]u8)); + testing.expectEqual(@as(u8, 0), Sentinel([5:0]u8)); +} + pub fn containerLayout(comptime T: type) TypeInfo.ContainerLayout { return switch (@typeInfo(T)) { .Struct => |info| info.layout, diff --git a/lib/std/mutex.zig b/lib/std/mutex.zig index 6954b2fb17..a57519cd14 100644 --- a/lib/std/mutex.zig +++ b/lib/std/mutex.zig @@ -73,7 +73,7 @@ pub const Mutex = if (builtin.single_threaded) return self.tryAcquire() orelse @panic("deadlock detected"); } } -else if (builtin.os == .windows) +else if (builtin.os.tag == .windows) // https://locklessinc.com/articles/keyed_events/ extern union { locked: u8, @@ -161,7 +161,7 @@ else if (builtin.os == .windows) } }; } -else if (builtin.link_libc or builtin.os == .linux) +else if (builtin.link_libc or builtin.os.tag == .linux) // stack-based version of https://github.com/Amanieu/parking_lot/blob/master/core/src/word_lock.rs struct { state: usize, diff --git a/lib/std/net.zig b/lib/std/net.zig index 898ba086be..6d0daefdc0 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -352,7 +352,7 @@ pub const Address = extern union { unreachable; } - const path_len = std.mem.len(u8, @ptrCast([*:0]const u8, &self.un.path)); + const path_len = std.mem.len(@ptrCast([*:0]const u8, &self.un.path)); return @intCast(os.socklen_t, @sizeOf(os.sockaddr_un) - self.un.path.len + path_len); }, else => unreachable, @@ -501,7 +501,7 @@ pub fn getAddressList(allocator: *mem.Allocator, name: []const u8, port: u16) !* return result; } - if (builtin.os == .linux) { + if (builtin.os.tag == .linux) { const flags = std.c.AI_NUMERICSERV; const family = os.AF_UNSPEC; var lookup_addrs = std.ArrayList(LookupAddr).init(allocator); diff --git a/lib/std/net/test.zig b/lib/std/net/test.zig index 45d8b1cffd..4f3d955f30 100644 --- a/lib/std/net/test.zig +++ b/lib/std/net/test.zig @@ -63,7 +63,7 @@ test "parse and render IPv4 addresses" { } test "resolve DNS" { - if (std.builtin.os == .windows) { + if (std.builtin.os.tag == .windows) { // DNS resolution not implemented on Windows yet. return error.SkipZigTest; } @@ -81,7 +81,7 @@ test "resolve DNS" { test "listen on a port, send bytes, receive bytes" { if (!std.io.is_async) return error.SkipZigTest; - if (std.builtin.os != .linux) { + if (std.builtin.os.tag != .linux) { // TODO build abstractions for other operating systems return error.SkipZigTest; } diff --git a/lib/std/os.zig b/lib/std/os.zig index 3b60a08cef..fbfef4ac4c 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -56,7 +56,7 @@ pub const system = if (@hasDecl(root, "os") and root.os != @This()) root.os.system else if (builtin.link_libc) std.c -else switch (builtin.os) { +else switch (builtin.os.tag) { .macosx, .ios, .watchos, .tvos => darwin, .freebsd => freebsd, .linux => linux, @@ -93,10 +93,10 @@ pub const errno = system.getErrno; /// must call `fsync` before `close`. /// Note: The Zig standard library does not support POSIX thread cancellation. pub fn close(fd: fd_t) void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return windows.CloseHandle(fd); } - if (builtin.os == .wasi) { + if (builtin.os.tag == .wasi) { _ = wasi.fd_close(fd); } if (comptime std.Target.current.isDarwin()) { @@ -121,12 +121,12 @@ pub const GetRandomError = OpenError; /// appropriate OS-specific library call. Otherwise it uses the zig standard /// library implementation. pub fn getrandom(buffer: []u8) GetRandomError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return windows.RtlGenRandom(buffer); } - if (builtin.os == .linux or builtin.os == .freebsd) { + if (builtin.os.tag == .linux or builtin.os.tag == .freebsd) { var buf = buffer; - const use_c = builtin.os != .linux or + const use_c = builtin.os.tag != .linux or std.c.versionCheck(builtin.Version{ .major = 2, .minor = 25, .patch = 0 }).ok; while (buf.len != 0) { @@ -153,7 +153,7 @@ pub fn getrandom(buffer: []u8) GetRandomError!void { } return; } - if (builtin.os == .wasi) { + if (builtin.os.tag == .wasi) { switch (wasi.random_get(buffer.ptr, buffer.len)) { 0 => return, else => |err| return unexpectedErrno(err), @@ -188,13 +188,13 @@ pub fn abort() noreturn { // MSVCRT abort() sometimes opens a popup window which is undesirable, so // even when linking libc on Windows we use our own abort implementation. // See https://github.com/ziglang/zig/issues/2071 for more details. - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { if (builtin.mode == .Debug) { @breakpoint(); } windows.kernel32.ExitProcess(3); } - if (!builtin.link_libc and builtin.os == .linux) { + if (!builtin.link_libc and builtin.os.tag == .linux) { raise(SIGABRT) catch {}; // TODO the rest of the implementation of abort() from musl libc here @@ -202,10 +202,10 @@ pub fn abort() noreturn { raise(SIGKILL) catch {}; exit(127); } - if (builtin.os == .uefi) { + if (builtin.os.tag == .uefi) { exit(0); // TODO choose appropriate exit code } - if (builtin.os == .wasi) { + if (builtin.os.tag == .wasi) { @breakpoint(); exit(1); } @@ -223,7 +223,7 @@ pub fn raise(sig: u8) RaiseError!void { } } - if (builtin.os == .linux) { + if (builtin.os.tag == .linux) { var set: linux.sigset_t = undefined; // block application signals _ = linux.sigprocmask(SIG_BLOCK, &linux.app_mask, &set); @@ -260,16 +260,16 @@ pub fn exit(status: u8) noreturn { if (builtin.link_libc) { system.exit(status); } - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { windows.kernel32.ExitProcess(status); } - if (builtin.os == .wasi) { + if (builtin.os.tag == .wasi) { wasi.proc_exit(status); } - if (builtin.os == .linux and !builtin.single_threaded) { + if (builtin.os.tag == .linux and !builtin.single_threaded) { linux.exit_group(status); } - if (builtin.os == .uefi) { + if (builtin.os.tag == .uefi) { // exit() is only avaliable if exitBootServices() has not been called yet. // This call to exit should not fail, so we don't care about its return value. if (uefi.system_table.boot_services) |bs| { @@ -299,11 +299,11 @@ pub const ReadError = error{ /// If the application has a global event loop enabled, EAGAIN is handled /// via the event loop. Otherwise EAGAIN results in error.WouldBlock. pub fn read(fd: fd_t, buf: []u8) ReadError!usize { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return windows.ReadFile(fd, buf, null); } - if (builtin.os == .wasi and !builtin.link_libc) { + if (builtin.os.tag == .wasi and !builtin.link_libc) { const iovs = [1]iovec{iovec{ .iov_base = buf.ptr, .iov_len = buf.len, @@ -352,7 +352,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { /// * Windows /// On these systems, the read races with concurrent writes to the same file descriptor. pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { // TODO batch these into parallel requests var off: usize = 0; var iov_i: usize = 0; @@ -406,7 +406,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. pub fn pread(fd: fd_t, buf: []u8, offset: u64) ReadError!usize { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return windows.ReadFile(fd, buf, offset); } @@ -493,7 +493,7 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize { } } - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { // TODO batch these into parallel requests var off: usize = 0; var iov_i: usize = 0; @@ -557,11 +557,11 @@ pub const WriteError = error{ /// If the application has a global event loop enabled, EAGAIN is handled /// via the event loop. Otherwise EAGAIN results in error.WouldBlock. pub fn write(fd: fd_t, bytes: []const u8) WriteError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return windows.WriteFile(fd, bytes, null); } - if (builtin.os == .wasi and !builtin.link_libc) { + if (builtin.os.tag == .wasi and !builtin.link_libc) { const ciovs = [1]iovec_const{iovec_const{ .iov_base = bytes.ptr, .iov_len = bytes.len, @@ -650,7 +650,7 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void { /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) WriteError!void { - if (comptime std.Target.current.isWindows()) { + if (std.Target.current.os.tag == .windows) { return windows.WriteFile(fd, bytes, offset); } @@ -739,7 +739,7 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void } } - if (comptime std.Target.current.isWindows()) { + if (std.Target.current.os.tag == .windows) { var off = offset; for (iov) |item| { try pwrite(fd, item.iov_base[0..item.iov_len], off); @@ -1095,7 +1095,7 @@ pub fn createNullDelimitedEnvMap(allocator: *mem.Allocator, env_map: *const std. pub fn freeNullDelimitedEnvMap(allocator: *mem.Allocator, envp_buf: []?[*:0]u8) void { for (envp_buf) |env| { - const env_buf = if (env) |ptr| ptr[0 .. mem.len(u8, ptr) + 1] else break; + const env_buf = if (env) |ptr| ptr[0 .. mem.len(ptr) + 1] else break; allocator.free(env_buf); } allocator.free(envp_buf); @@ -1129,7 +1129,7 @@ pub fn getenv(key: []const u8) ?[]const u8 { } return null; } - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { @compileError("std.os.getenv is unavailable for Windows because environment string is in WTF-16 format. See std.process.getEnvVarOwned for cross-platform API or std.os.getenvW for Windows-specific API."); } // TODO see https://github.com/ziglang/zig/issues/4524 @@ -1158,7 +1158,7 @@ pub fn getenvZ(key: [*:0]const u8) ?[]const u8 { const value = system.getenv(key) orelse return null; return mem.toSliceConst(u8, value); } - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { @compileError("std.os.getenvZ is unavailable for Windows because environment string is in WTF-16 format. See std.process.getEnvVarOwned for cross-platform API or std.os.getenvW for Windows-specific API."); } return getenv(mem.toSliceConst(u8, key)); @@ -1167,7 +1167,7 @@ pub fn getenvZ(key: [*:0]const u8) ?[]const u8 { /// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name. /// See also `getenv`. pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 { - if (builtin.os != .windows) { + if (builtin.os.tag != .windows) { @compileError("std.os.getenvW is a Windows-only API"); } const key_slice = mem.toSliceConst(u16, key); @@ -1199,7 +1199,7 @@ pub const GetCwdError = error{ /// The result is a slice of out_buffer, indexed from 0. pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return windows.GetCurrentDirectory(out_buffer); } @@ -1240,7 +1240,7 @@ pub const SymLinkError = error{ /// If `sym_link_path` exists, it will not be overwritten. /// See also `symlinkC` and `symlinkW`. pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const target_path_w = try windows.sliceToPrefixedFileW(target_path); const sym_link_path_w = try windows.sliceToPrefixedFileW(sym_link_path); return windows.CreateSymbolicLinkW(&sym_link_path_w, &target_path_w, 0); @@ -1254,7 +1254,7 @@ pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError! /// This is the same as `symlink` except the parameters are null-terminated pointers. /// See also `symlink`. pub fn symlinkC(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const target_path_w = try windows.cStrToPrefixedFileW(target_path); const sym_link_path_w = try windows.cStrToPrefixedFileW(sym_link_path); return windows.CreateSymbolicLinkW(&sym_link_path_w, &target_path_w, 0); @@ -1329,7 +1329,7 @@ pub const UnlinkError = error{ /// Delete a name and possibly the file it refers to. /// See also `unlinkC`. pub fn unlink(file_path: []const u8) UnlinkError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const file_path_w = try windows.sliceToPrefixedFileW(file_path); return windows.DeleteFileW(&file_path_w); } else { @@ -1340,7 +1340,7 @@ pub fn unlink(file_path: []const u8) UnlinkError!void { /// Same as `unlink` except the parameter is a null terminated UTF8-encoded string. pub fn unlinkC(file_path: [*:0]const u8) UnlinkError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const file_path_w = try windows.cStrToPrefixedFileW(file_path); return windows.DeleteFileW(&file_path_w); } @@ -1372,7 +1372,7 @@ pub const UnlinkatError = UnlinkError || error{ /// Asserts that the path parameter has no null bytes. pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { if (std.debug.runtime_safety) for (file_path) |byte| assert(byte != 0); - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const file_path_w = try windows.sliceToPrefixedFileW(file_path); return unlinkatW(dirfd, &file_path_w, flags); } @@ -1382,7 +1382,7 @@ pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!vo /// Same as `unlinkat` but `file_path` is a null-terminated string. pub fn unlinkatC(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const file_path_w = try windows.cStrToPrefixedFileW(file_path_c); return unlinkatW(dirfd, &file_path_w, flags); } @@ -1438,7 +1438,7 @@ pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*:0]const u16, flags: u32) UnlinkatEr var attr = w.OBJECT_ATTRIBUTES{ .Length = @sizeOf(w.OBJECT_ATTRIBUTES), - .RootDirectory = dirfd, + .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dirfd, .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. .ObjectName = &nt_name, .SecurityDescriptor = null, @@ -1493,7 +1493,7 @@ const RenameError = error{ /// Change the name or location of a file. pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const old_path_w = try windows.sliceToPrefixedFileW(old_path); const new_path_w = try windows.sliceToPrefixedFileW(new_path); return renameW(&old_path_w, &new_path_w); @@ -1506,7 +1506,7 @@ pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void { /// Same as `rename` except the parameters are null-terminated byte arrays. pub fn renameC(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const old_path_w = try windows.cStrToPrefixedFileW(old_path); const new_path_w = try windows.cStrToPrefixedFileW(new_path); return renameW(&old_path_w, &new_path_w); @@ -1561,7 +1561,7 @@ pub const MakeDirError = error{ /// Create a directory. /// `mode` is ignored on Windows. pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); return windows.CreateDirectoryW(&dir_path_w, null); } else { @@ -1572,7 +1572,7 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { /// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string. pub fn mkdirC(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); return windows.CreateDirectoryW(&dir_path_w, null); } @@ -1611,7 +1611,7 @@ pub const DeleteDirError = error{ /// Deletes an empty directory. pub fn rmdir(dir_path: []const u8) DeleteDirError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); return windows.RemoveDirectoryW(&dir_path_w); } else { @@ -1622,7 +1622,7 @@ pub fn rmdir(dir_path: []const u8) DeleteDirError!void { /// Same as `rmdir` except the parameter is null-terminated. pub fn rmdirC(dir_path: [*:0]const u8) DeleteDirError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); return windows.RemoveDirectoryW(&dir_path_w); } @@ -1658,7 +1658,7 @@ pub const ChangeCurDirError = error{ /// Changes the current working directory of the calling process. /// `dir_path` is recommended to be a UTF-8 encoded string. pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); @compileError("TODO implement chdir for Windows"); } else { @@ -1669,7 +1669,7 @@ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { /// Same as `chdir` except the parameter is null-terminated. pub fn chdirC(dir_path: [*:0]const u8) ChangeCurDirError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); @compileError("TODO implement chdir for Windows"); } @@ -1700,7 +1700,7 @@ pub const ReadLinkError = error{ /// Read value of a symbolic link. /// The return value is a slice of `out_buffer` from index 0. pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const file_path_w = try windows.sliceToPrefixedFileW(file_path); @compileError("TODO implement readlink for Windows"); } else { @@ -1711,7 +1711,7 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { /// Same as `readlink` except `file_path` is null-terminated. pub fn readlinkC(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const file_path_w = try windows.cStrToPrefixedFileW(file_path); @compileError("TODO implement readlink for Windows"); } @@ -1732,7 +1732,7 @@ pub fn readlinkC(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 } pub fn readlinkatC(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const file_path_w = try windows.cStrToPrefixedFileW(file_path); @compileError("TODO implement readlink for Windows"); } @@ -1800,7 +1800,7 @@ pub fn setregid(rgid: u32, egid: u32) SetIdError!void { /// Test whether a file descriptor refers to a terminal. pub fn isatty(handle: fd_t) bool { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { if (isCygwinPty(handle)) return true; @@ -1810,7 +1810,7 @@ pub fn isatty(handle: fd_t) bool { if (builtin.link_libc) { return system.isatty(handle) != 0; } - if (builtin.os == .wasi) { + if (builtin.os.tag == .wasi) { var statbuf: fdstat_t = undefined; const err = system.fd_fdstat_get(handle, &statbuf); if (err != 0) { @@ -1828,7 +1828,7 @@ pub fn isatty(handle: fd_t) bool { return true; } - if (builtin.os == .linux) { + if (builtin.os.tag == .linux) { var wsz: linux.winsize = undefined; return linux.syscall3(linux.SYS_ioctl, @bitCast(usize, @as(isize, handle)), linux.TIOCGWINSZ, @ptrToInt(&wsz)) == 0; } @@ -1836,7 +1836,7 @@ pub fn isatty(handle: fd_t) bool { } pub fn isCygwinPty(handle: fd_t) bool { - if (builtin.os != .windows) return false; + if (builtin.os.tag != .windows) return false; const size = @sizeOf(windows.FILE_NAME_INFO); var name_info_bytes align(@alignOf(windows.FILE_NAME_INFO)) = [_]u8{0} ** (size + windows.MAX_PATH); @@ -2589,7 +2589,7 @@ pub const AccessError = error{ /// check user's permissions for a file /// TODO currently this assumes `mode` is `F_OK` on Windows. pub fn access(path: []const u8, mode: u32) AccessError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const path_w = try windows.sliceToPrefixedFileW(path); _ = try windows.GetFileAttributesW(&path_w); return; @@ -2603,7 +2603,7 @@ pub const accessC = accessZ; /// Same as `access` except `path` is null-terminated. pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const path_w = try windows.cStrToPrefixedFileW(path); _ = try windows.GetFileAttributesW(&path_w); return; @@ -2644,7 +2644,7 @@ pub fn accessW(path: [*:0]const u16, mode: u32) windows.GetFileAttributesError!v /// Check user's permissions for a file, based on an open directory handle. /// TODO currently this ignores `mode` and `flags` on Windows. pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const path_w = try windows.sliceToPrefixedFileW(path); return faccessatW(dirfd, &path_w, mode, flags); } @@ -2654,7 +2654,7 @@ pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessErr /// Same as `faccessat` except the path parameter is null-terminated. pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) AccessError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const path_w = try windows.cStrToPrefixedFileW(path); return faccessatW(dirfd, &path_w, mode, flags); } @@ -2764,6 +2764,7 @@ pub const SysCtlError = error{ PermissionDenied, SystemResources, NameTooLong, + UnknownName, } || UnexpectedError; pub fn sysctl( @@ -2779,6 +2780,7 @@ pub fn sysctl( EFAULT => unreachable, EPERM => return error.PermissionDenied, ENOMEM => return error.SystemResources, + ENOENT => return error.UnknownName, else => |err| return unexpectedErrno(err), } } @@ -2795,6 +2797,7 @@ pub fn sysctlbynameC( EFAULT => unreachable, EPERM => return error.PermissionDenied, ENOMEM => return error.SystemResources, + ENOENT => return error.UnknownName, else => |err| return unexpectedErrno(err), } } @@ -2811,7 +2814,7 @@ pub const SeekError = error{Unseekable} || UnexpectedError; /// Repositions read/write file offset relative to the beginning. pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void { - if (builtin.os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { + if (builtin.os.tag == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { var result: u64 = undefined; switch (errno(system.llseek(fd, offset, &result, SEEK_SET))) { 0 => return, @@ -2823,7 +2826,7 @@ pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void { else => |err| return unexpectedErrno(err), } } - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return windows.SetFilePointerEx_BEGIN(fd, offset); } const ipos = @bitCast(i64, offset); // the OS treats this as unsigned @@ -2840,7 +2843,7 @@ pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void { /// Repositions read/write file offset relative to the current offset. pub fn lseek_CUR(fd: fd_t, offset: i64) SeekError!void { - if (builtin.os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { + if (builtin.os.tag == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { var result: u64 = undefined; switch (errno(system.llseek(fd, @bitCast(u64, offset), &result, SEEK_CUR))) { 0 => return, @@ -2852,7 +2855,7 @@ pub fn lseek_CUR(fd: fd_t, offset: i64) SeekError!void { else => |err| return unexpectedErrno(err), } } - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return windows.SetFilePointerEx_CURRENT(fd, offset); } switch (errno(system.lseek(fd, offset, SEEK_CUR))) { @@ -2868,7 +2871,7 @@ pub fn lseek_CUR(fd: fd_t, offset: i64) SeekError!void { /// Repositions read/write file offset relative to the end. pub fn lseek_END(fd: fd_t, offset: i64) SeekError!void { - if (builtin.os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { + if (builtin.os.tag == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { var result: u64 = undefined; switch (errno(system.llseek(fd, @bitCast(u64, offset), &result, SEEK_END))) { 0 => return, @@ -2880,7 +2883,7 @@ pub fn lseek_END(fd: fd_t, offset: i64) SeekError!void { else => |err| return unexpectedErrno(err), } } - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return windows.SetFilePointerEx_END(fd, offset); } switch (errno(system.lseek(fd, offset, SEEK_END))) { @@ -2896,7 +2899,7 @@ pub fn lseek_END(fd: fd_t, offset: i64) SeekError!void { /// Returns the read/write file offset relative to the beginning. pub fn lseek_CUR_get(fd: fd_t) SeekError!u64 { - if (builtin.os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { + if (builtin.os.tag == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { var result: u64 = undefined; switch (errno(system.llseek(fd, 0, &result, SEEK_CUR))) { 0 => return result, @@ -2908,7 +2911,7 @@ pub fn lseek_CUR_get(fd: fd_t) SeekError!u64 { else => |err| return unexpectedErrno(err), } } - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return windows.SetFilePointerEx_CURRENT_get(fd); } const rc = system.lseek(fd, 0, SEEK_CUR); @@ -2957,7 +2960,7 @@ pub const RealPathError = error{ /// The return value is a slice of `out_buffer`, but not necessarily from the beginning. /// See also `realpathC` and `realpathW`. pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const pathname_w = try windows.sliceToPrefixedFileW(pathname); return realpathW(&pathname_w, out_buffer); } @@ -2967,11 +2970,11 @@ pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathE /// Same as `realpath` except `pathname` is null-terminated. pub fn realpathC(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const pathname_w = try windows.cStrToPrefixedFileW(pathname); return realpathW(&pathname_w, out_buffer); } - if (builtin.os == .linux and !builtin.link_libc) { + if (builtin.os.tag == .linux and !builtin.link_libc) { const fd = try openC(pathname, linux.O_PATH | linux.O_NONBLOCK | linux.O_CLOEXEC, 0); defer close(fd); @@ -3121,7 +3124,7 @@ pub fn dl_iterate_phdr( pub const ClockGetTimeError = error{UnsupportedClock} || UnexpectedError; pub fn clock_gettime(clk_id: i32, tp: *timespec) ClockGetTimeError!void { - if (comptime std.Target.current.getOs() == .wasi) { + if (std.Target.current.os.tag == .wasi) { var ts: timestamp_t = undefined; switch (system.clock_time_get(@bitCast(u32, clk_id), 1, &ts)) { 0 => { @@ -3144,7 +3147,7 @@ pub fn clock_gettime(clk_id: i32, tp: *timespec) ClockGetTimeError!void { } pub fn clock_getres(clk_id: i32, res: *timespec) ClockGetTimeError!void { - if (comptime std.Target.current.getOs() == .wasi) { + if (std.Target.current.os.tag == .wasi) { var ts: timestamp_t = undefined; switch (system.clock_res_get(@bitCast(u32, clk_id), &ts)) { 0 => res.* = .{ @@ -3222,7 +3225,7 @@ pub const SigaltstackError = error{ } || UnexpectedError; pub fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) SigaltstackError!void { - if (builtin.os == .windows or builtin.os == .uefi or builtin.os == .wasi) + if (builtin.os.tag == .windows or builtin.os.tag == .uefi or builtin.os.tag == .wasi) @compileError("std.os.sigaltstack not available for this target"); switch (errno(system.sigaltstack(ss, old_ss))) { @@ -3294,23 +3297,25 @@ pub fn gethostname(name_buffer: *[HOST_NAME_MAX]u8) GetHostNameError![]u8 { else => |err| return unexpectedErrno(err), } } - if (builtin.os == .linux) { - var uts: utsname = undefined; - switch (errno(system.uname(&uts))) { - 0 => { - const hostname = mem.toSlice(u8, @ptrCast([*:0]u8, &uts.nodename)); - mem.copy(u8, name_buffer, hostname); - return name_buffer[0..hostname.len]; - }, - EFAULT => unreachable, - EPERM => return error.PermissionDenied, - else => |err| return unexpectedErrno(err), - } + if (builtin.os.tag == .linux) { + const uts = uname(); + const hostname = mem.toSliceConst(u8, @ptrCast([*:0]const u8, &uts.nodename)); + mem.copy(u8, name_buffer, hostname); + return name_buffer[0..hostname.len]; } @compileError("TODO implement gethostname for this OS"); } +pub fn uname() utsname { + var uts: utsname = undefined; + switch (errno(system.uname(&uts))) { + 0 => return uts, + EFAULT => unreachable, + else => unreachable, + } +} + pub fn res_mkquery( op: u4, dname: []const u8, @@ -3611,7 +3616,7 @@ pub const SchedYieldError = error{ }; pub fn sched_yield() SchedYieldError!void { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { // The return value has to do with how many other threads there are; it is not // an error condition on Windows. _ = windows.kernel32.SwitchToThread(); diff --git a/lib/std/os/bits.zig b/lib/std/os/bits.zig index bab9ad0ae5..38f019d775 100644 --- a/lib/std/os/bits.zig +++ b/lib/std/os/bits.zig @@ -3,10 +3,10 @@ //! Root source files can define `os.bits` and these will additionally be added //! to the namespace. -const builtin = @import("builtin"); +const std = @import("std"); const root = @import("root"); -pub usingnamespace switch (builtin.os) { +pub usingnamespace switch (std.Target.current.os.tag) { .macosx, .ios, .tvos, .watchos => @import("bits/darwin.zig"), .dragonfly => @import("bits/dragonfly.zig"), .freebsd => @import("bits/freebsd.zig"), diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index d11f206482..30dba85e51 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1070,7 +1070,7 @@ pub fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) usi } test "" { - if (builtin.os == .linux) { + if (builtin.os.tag == .linux) { _ = @import("linux/test.zig"); } } diff --git a/lib/std/os/linux/vdso.zig b/lib/std/os/linux/vdso.zig index 868eb26c69..beb98a063d 100644 --- a/lib/std/os/linux/vdso.zig +++ b/lib/std/os/linux/vdso.zig @@ -22,7 +22,11 @@ pub fn lookup(vername: []const u8, name: []const u8) usize { }) { const this_ph = @intToPtr(*elf.Phdr, ph_addr); switch (this_ph.p_type) { - elf.PT_LOAD => base = vdso_addr + this_ph.p_offset - this_ph.p_vaddr, + // On WSL1 as well as older kernels, the VDSO ELF image is pre-linked in the upper half + // of the memory space (e.g. p_vaddr = 0xffffffffff700000 on WSL1). + // Wrapping operations are used on this line as well as subsequent calculations relative to base + // (lines 47, 78) to ensure no overflow check is tripped. + elf.PT_LOAD => base = vdso_addr +% this_ph.p_offset -% this_ph.p_vaddr, elf.PT_DYNAMIC => maybe_dynv = @intToPtr([*]usize, vdso_addr + this_ph.p_offset), else => {}, } @@ -40,7 +44,7 @@ pub fn lookup(vername: []const u8, name: []const u8) usize { { var i: usize = 0; while (dynv[i] != 0) : (i += 2) { - const p = base + dynv[i + 1]; + const p = base +% dynv[i + 1]; switch (dynv[i]) { elf.DT_STRTAB => maybe_strings = @intToPtr([*]u8, p), elf.DT_SYMTAB => maybe_syms = @intToPtr([*]elf.Sym, p), @@ -71,7 +75,7 @@ pub fn lookup(vername: []const u8, name: []const u8) usize { if (!checkver(maybe_verdef.?, versym[i], vername, strings)) continue; } - return base + syms[i].st_value; + return base +% syms[i].st_value; } return 0; diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index 488a557ed6..197edd82c1 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -53,7 +53,7 @@ test "std.Thread.getCurrentId" { thread.wait(); if (Thread.use_pthreads) { expect(thread_current_id == thread_id); - } else if (builtin.os == .windows) { + } else if (builtin.os.tag == .windows) { expect(Thread.getCurrentId() != thread_current_id); } else { // If the thread completes very quickly, then thread_id can be 0. See the @@ -151,7 +151,7 @@ test "realpath" { } test "sigaltstack" { - if (builtin.os == .windows or builtin.os == .wasi) return error.SkipZigTest; + if (builtin.os.tag == .windows or builtin.os.tag == .wasi) return error.SkipZigTest; var st: os.stack_t = undefined; try os.sigaltstack(null, &st); @@ -204,7 +204,7 @@ fn iter_fn(info: *dl_phdr_info, size: usize, counter: *usize) IterFnError!void { } test "dl_iterate_phdr" { - if (builtin.os == .windows or builtin.os == .wasi or builtin.os == .macosx) + if (builtin.os.tag == .windows or builtin.os.tag == .wasi or builtin.os.tag == .macosx) return error.SkipZigTest; var counter: usize = 0; @@ -213,7 +213,7 @@ test "dl_iterate_phdr" { } test "gethostname" { - if (builtin.os == .windows) + if (builtin.os.tag == .windows) return error.SkipZigTest; var buf: [os.HOST_NAME_MAX]u8 = undefined; @@ -222,7 +222,7 @@ test "gethostname" { } test "pipe" { - if (builtin.os == .windows) + if (builtin.os.tag == .windows) return error.SkipZigTest; var fds = try os.pipe(); @@ -241,7 +241,7 @@ test "argsAlloc" { test "memfd_create" { // memfd_create is linux specific. - if (builtin.os != .linux) return error.SkipZigTest; + if (builtin.os.tag != .linux) return error.SkipZigTest; const fd = std.os.memfd_create("test", 0) catch |err| switch (err) { // Related: https://github.com/ziglang/zig/issues/4019 error.SystemOutdated => return error.SkipZigTest, @@ -258,7 +258,7 @@ test "memfd_create" { } test "mmap" { - if (builtin.os == .windows) + if (builtin.os.tag == .windows) return error.SkipZigTest; // Simple mmap() call with non page-aligned size @@ -353,7 +353,7 @@ test "mmap" { } test "getenv" { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { expect(os.getenvW(&[_:0]u16{ 'B', 'O', 'G', 'U', 'S', 0x11, 0x22, 0x33, 0x44, 0x55 }) == null); } else { expect(os.getenvZ("BOGUSDOESNOTEXISTENVVAR") == null); diff --git a/lib/std/os/windows/bits.zig b/lib/std/os/windows/bits.zig index 4e153d40ed..f9ba6cf40e 100644 --- a/lib/std/os/windows/bits.zig +++ b/lib/std/os/windows/bits.zig @@ -23,6 +23,7 @@ pub const BOOL = c_int; pub const BOOLEAN = BYTE; pub const BYTE = u8; pub const CHAR = u8; +pub const UCHAR = u8; pub const FLOAT = f32; pub const HANDLE = *c_void; pub const HCRYPTPROV = ULONG_PTR; @@ -54,6 +55,7 @@ pub const WORD = u16; pub const DWORD = u32; pub const DWORD64 = u64; pub const LARGE_INTEGER = i64; +pub const ULARGE_INTEGER = u64; pub const USHORT = u16; pub const SHORT = i16; pub const ULONG = u32; @@ -1145,32 +1147,202 @@ pub const UNICODE_STRING = extern struct { Buffer: [*]WCHAR, }; +const ACTIVATION_CONTEXT_DATA = @OpaqueType(); +const ASSEMBLY_STORAGE_MAP = @OpaqueType(); +const FLS_CALLBACK_INFO = @OpaqueType(); +const RTL_BITMAP = @OpaqueType(); +pub const PRTL_BITMAP = *RTL_BITMAP; +const KAFFINITY = usize; + +/// Process Environment Block +/// Microsoft documentation of this is incomplete, the fields here are taken from various resources including: +/// - https://github.com/wine-mirror/wine/blob/1aff1e6a370ee8c0213a0fd4b220d121da8527aa/include/winternl.h#L269 +/// - https://www.geoffchappell.com/studies/windows/win32/ntdll/structs/peb/index.htm pub const PEB = extern struct { - Reserved1: [2]BYTE, - BeingDebugged: BYTE, - Reserved2: [1]BYTE, - Reserved3: [2]PVOID, + // Versions: All + InheritedAddressSpace: BOOLEAN, + + // Versions: 3.51+ + ReadImageFileExecOptions: BOOLEAN, + BeingDebugged: BOOLEAN, + + // Versions: 5.2+ (previously was padding) + BitField: UCHAR, + + // Versions: all + Mutant: HANDLE, + ImageBaseAddress: HMODULE, Ldr: *PEB_LDR_DATA, ProcessParameters: *RTL_USER_PROCESS_PARAMETERS, - Reserved4: [3]PVOID, + SubSystemData: PVOID, + ProcessHeap: HANDLE, + + // Versions: 5.1+ + FastPebLock: *RTL_CRITICAL_SECTION, + + // Versions: 5.2+ AtlThunkSListPtr: PVOID, - Reserved5: PVOID, - Reserved6: ULONG, - Reserved7: PVOID, - Reserved8: ULONG, + IFEOKey: PVOID, + + // Versions: 6.0+ + + /// https://www.geoffchappell.com/studies/windows/win32/ntdll/structs/peb/crossprocessflags.htm + CrossProcessFlags: ULONG, + + // Versions: 6.0+ + union1: extern union { + KernelCallbackTable: PVOID, + UserSharedInfoPtr: PVOID, + }, + + // Versions: 5.1+ + SystemReserved: ULONG, + + // Versions: 5.1, (not 5.2, not 6.0), 6.1+ AtlThunkSListPtr32: ULONG, - Reserved9: [45]PVOID, - Reserved10: [96]BYTE, - PostProcessInitRoutine: PPS_POST_PROCESS_INIT_ROUTINE, - Reserved11: [128]BYTE, - Reserved12: [1]PVOID, + + // Versions: 6.1+ + ApiSetMap: PVOID, + + // Versions: all + TlsExpansionCounter: ULONG, + // note: there is padding here on 64 bit + TlsBitmap: PRTL_BITMAP, + TlsBitmapBits: [2]ULONG, + ReadOnlySharedMemoryBase: PVOID, + + // Versions: 1703+ + SharedData: PVOID, + + // Versions: all + ReadOnlyStaticServerData: *PVOID, + AnsiCodePageData: PVOID, + OemCodePageData: PVOID, + UnicodeCaseTableData: PVOID, + + // Versions: 3.51+ + NumberOfProcessors: ULONG, + NtGlobalFlag: ULONG, + + // Versions: all + CriticalSectionTimeout: LARGE_INTEGER, + + // End of Original PEB size + + // Fields appended in 3.51: + HeapSegmentReserve: ULONG_PTR, + HeapSegmentCommit: ULONG_PTR, + HeapDeCommitTotalFreeThreshold: ULONG_PTR, + HeapDeCommitFreeBlockThreshold: ULONG_PTR, + NumberOfHeaps: ULONG, + MaximumNumberOfHeaps: ULONG, + ProcessHeaps: *PVOID, + + // Fields appended in 4.0: + GdiSharedHandleTable: PVOID, + ProcessStarterHelper: PVOID, + GdiDCAttributeList: ULONG, + // note: there is padding here on 64 bit + LoaderLock: *RTL_CRITICAL_SECTION, + OSMajorVersion: ULONG, + OSMinorVersion: ULONG, + OSBuildNumber: USHORT, + OSCSDVersion: USHORT, + OSPlatformId: ULONG, + ImageSubSystem: ULONG, + ImageSubSystemMajorVersion: ULONG, + ImageSubSystemMinorVersion: ULONG, + // note: there is padding here on 64 bit + ActiveProcessAffinityMask: KAFFINITY, + GdiHandleBuffer: [switch (@sizeOf(usize)) { + 4 => 0x22, + 8 => 0x3C, + else => unreachable, + }]ULONG, + + // Fields appended in 5.0 (Windows 2000): + PostProcessInitRoutine: PVOID, + TlsExpansionBitmap: PRTL_BITMAP, + TlsExpansionBitmapBits: [32]ULONG, SessionId: ULONG, + // note: there is padding here on 64 bit + // Versions: 5.1+ + AppCompatFlags: ULARGE_INTEGER, + AppCompatFlagsUser: ULARGE_INTEGER, + ShimData: PVOID, + // Versions: 5.0+ + AppCompatInfo: PVOID, + CSDVersion: UNICODE_STRING, + + // Fields appended in 5.1 (Windows XP): + ActivationContextData: *const ACTIVATION_CONTEXT_DATA, + ProcessAssemblyStorageMap: *ASSEMBLY_STORAGE_MAP, + SystemDefaultActivationData: *const ACTIVATION_CONTEXT_DATA, + SystemAssemblyStorageMap: *ASSEMBLY_STORAGE_MAP, + MinimumStackCommit: ULONG_PTR, + + // Fields appended in 5.2 (Windows Server 2003): + FlsCallback: *FLS_CALLBACK_INFO, + FlsListHead: LIST_ENTRY, + FlsBitmap: PRTL_BITMAP, + FlsBitmapBits: [4]ULONG, + FlsHighIndex: ULONG, + + // Fields appended in 6.0 (Windows Vista): + WerRegistrationData: PVOID, + WerShipAssertPtr: PVOID, + + // Fields appended in 6.1 (Windows 7): + pUnused: PVOID, // previously pContextData + pImageHeaderHash: PVOID, + + /// TODO: https://www.geoffchappell.com/studies/windows/win32/ntdll/structs/peb/tracingflags.htm + TracingFlags: ULONG, + + // Fields appended in 6.2 (Windows 8): + CsrServerReadOnlySharedMemoryBase: ULONGLONG, + + // Fields appended in 1511: + TppWorkerpListLock: ULONG, + TppWorkerpList: LIST_ENTRY, + WaitOnAddressHashTable: [0x80]PVOID, + + // Fields appended in 1709: + TelemetryCoverageHeader: PVOID, + CloudFileFlags: ULONG, }; +/// The `PEB_LDR_DATA` structure is the main record of what modules are loaded in a process. +/// It is essentially the head of three double-linked lists of `LDR_DATA_TABLE_ENTRY` structures which each represent one loaded module. +/// +/// Microsoft documentation of this is incomplete, the fields here are taken from various resources including: +/// - https://www.geoffchappell.com/studies/windows/win32/ntdll/structs/peb_ldr_data.htm pub const PEB_LDR_DATA = extern struct { - Reserved1: [8]BYTE, - Reserved2: [3]PVOID, + // Versions: 3.51 and higher + /// The size in bytes of the structure + Length: ULONG, + + /// TRUE if the structure is prepared. + Initialized: BOOLEAN, + + SsHandle: PVOID, + InLoadOrderModuleList: LIST_ENTRY, InMemoryOrderModuleList: LIST_ENTRY, + InInitializationOrderModuleList: LIST_ENTRY, + + // Versions: 5.1 and higher + + /// No known use of this field is known in Windows 8 and higher. + EntryInProgress: PVOID, + + // Versions: 6.0 from Windows Vista SP1, and higher + ShutdownInProgress: BOOLEAN, + + /// Though ShutdownThreadId is declared as a HANDLE, + /// it is indeed the thread ID as suggested by its name. + /// It is picked up from the UniqueThread member of the CLIENT_ID in the + /// TEB of the thread that asks to terminate the process. + ShutdownThreadId: HANDLE, }; pub const RTL_USER_PROCESS_PARAMETERS = extern struct { @@ -1321,3 +1493,16 @@ pub const PSAPI_WS_WATCH_INFORMATION_EX = extern struct { Flags: ULONG_PTR, }; pub const PPSAPI_WS_WATCH_INFORMATION_EX = *PSAPI_WS_WATCH_INFORMATION_EX; + +pub const OSVERSIONINFOW = extern struct { + dwOSVersionInfoSize: ULONG, + dwMajorVersion: ULONG, + dwMinorVersion: ULONG, + dwBuildNumber: ULONG, + dwPlatformId: ULONG, + szCSDVersion: [128]WCHAR, +}; +pub const POSVERSIONINFOW = *OSVERSIONINFOW; +pub const LPOSVERSIONINFOW = *OSVERSIONINFOW; +pub const RTL_OSVERSIONINFOW = OSVERSIONINFOW; +pub const PRTL_OSVERSIONINFOW = *RTL_OSVERSIONINFOW; diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index e98a2e6a87..49e60803bc 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -1,6 +1,14 @@ usingnamespace @import("bits.zig"); -pub extern "NtDll" fn RtlCaptureStackBackTrace(FramesToSkip: DWORD, FramesToCapture: DWORD, BackTrace: **c_void, BackTraceHash: ?*DWORD) callconv(.Stdcall) WORD; +pub extern "NtDll" fn RtlGetVersion( + lpVersionInformation: PRTL_OSVERSIONINFOW, +) callconv(.Stdcall) NTSTATUS; +pub extern "NtDll" fn RtlCaptureStackBackTrace( + FramesToSkip: DWORD, + FramesToCapture: DWORD, + BackTrace: **c_void, + BackTraceHash: ?*DWORD, +) callconv(.Stdcall) WORD; pub extern "NtDll" fn NtQueryInformationFile( FileHandle: HANDLE, IoStatusBlock: *IO_STATUS_BLOCK, diff --git a/lib/std/packed_int_array.zig b/lib/std/packed_int_array.zig index 63b6fffa73..51be01315e 100644 --- a/lib/std/packed_int_array.zig +++ b/lib/std/packed_int_array.zig @@ -593,7 +593,7 @@ test "PackedInt(Array/Slice)Endian" { // after this one is not mapped and will cause a segfault if we // don't account for the bounds. test "PackedIntArray at end of available memory" { - switch (builtin.os) { + switch (builtin.os.tag) { .linux, .macosx, .ios, .freebsd, .netbsd, .windows => {}, else => return, } @@ -612,7 +612,7 @@ test "PackedIntArray at end of available memory" { } test "PackedIntSlice at end of available memory" { - switch (builtin.os) { + switch (builtin.os.tag) { .linux, .macosx, .ios, .freebsd, .netbsd, .windows => {}, else => return, } diff --git a/lib/std/process.zig b/lib/std/process.zig index 0dab8bb64b..01b9947518 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -36,7 +36,7 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap { var result = BufMap.init(allocator); errdefer result.deinit(); - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const ptr = os.windows.peb().ProcessParameters.Environment; var i: usize = 0; @@ -61,7 +61,7 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap { try result.setMove(key, value); } return result; - } else if (builtin.os == .wasi) { + } else if (builtin.os.tag == .wasi) { var environ_count: usize = undefined; var environ_buf_size: usize = undefined; @@ -137,7 +137,7 @@ pub const GetEnvVarOwnedError = error{ /// Caller must free returned memory. pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwnedError![]u8 { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { const result_w = blk: { const key_w = try std.unicode.utf8ToUtf16LeWithNull(allocator, key); defer allocator.free(key_w); @@ -338,12 +338,12 @@ pub const ArgIteratorWindows = struct { }; pub const ArgIterator = struct { - const InnerType = if (builtin.os == .windows) ArgIteratorWindows else ArgIteratorPosix; + const InnerType = if (builtin.os.tag == .windows) ArgIteratorWindows else ArgIteratorPosix; inner: InnerType, pub fn init() ArgIterator { - if (builtin.os == .wasi) { + if (builtin.os.tag == .wasi) { // TODO: Figure out a compatible interface accomodating WASI @compileError("ArgIterator is not yet supported in WASI. Use argsAlloc and argsFree instead."); } @@ -355,7 +355,7 @@ pub const ArgIterator = struct { /// You must free the returned memory when done. pub fn next(self: *ArgIterator, allocator: *Allocator) ?(NextError![]u8) { - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { return self.inner.next(allocator); } else { return mem.dupe(allocator, u8, self.inner.next() orelse return null); @@ -380,7 +380,7 @@ pub fn args() ArgIterator { /// Caller must call argsFree on result. pub fn argsAlloc(allocator: *mem.Allocator) ![][]u8 { - if (builtin.os == .wasi) { + if (builtin.os.tag == .wasi) { var count: usize = undefined; var buf_size: usize = undefined; @@ -445,7 +445,7 @@ pub fn argsAlloc(allocator: *mem.Allocator) ![][]u8 { } pub fn argsFree(allocator: *mem.Allocator, args_alloc: []const []u8) void { - if (builtin.os == .wasi) { + if (builtin.os.tag == .wasi) { const last_item = args_alloc[args_alloc.len - 1]; const last_byte_addr = @ptrToInt(last_item.ptr) + last_item.len + 1; // null terminated const first_item_ptr = args_alloc[0].ptr; @@ -498,7 +498,7 @@ pub const UserInfo = struct { /// POSIX function which gets a uid from username. pub fn getUserInfo(name: []const u8) !UserInfo { - return switch (builtin.os) { + return switch (builtin.os.tag) { .linux, .macosx, .watchos, .tvos, .ios, .freebsd, .netbsd => posixGetUserInfo(name), else => @compileError("Unsupported OS"), }; @@ -591,7 +591,7 @@ pub fn posixGetUserInfo(name: []const u8) !UserInfo { } pub fn getBaseAddress() usize { - switch (builtin.os) { + switch (builtin.os.tag) { .linux => { const base = os.system.getauxval(std.elf.AT_BASE); if (base != 0) { @@ -609,13 +609,17 @@ pub fn getBaseAddress() usize { } /// Caller owns the result value and each inner slice. +/// TODO Remove the `Allocator` requirement from this API, which will remove the `Allocator` +/// requirement from `std.zig.system.NativeTargetInfo.detect`. Most likely this will require +/// introducing a new, lower-level function which takes a callback function, and then this +/// function which takes an allocator can exist on top of it. pub fn getSelfExeSharedLibPaths(allocator: *Allocator) error{OutOfMemory}![][:0]u8 { switch (builtin.link_mode) { .Static => return &[_][:0]u8{}, .Dynamic => {}, } const List = std.ArrayList([:0]u8); - switch (builtin.os) { + switch (builtin.os.tag) { .linux, .freebsd, .netbsd, diff --git a/lib/std/reset_event.zig b/lib/std/reset_event.zig index b31906c5f8..c28db809ca 100644 --- a/lib/std/reset_event.zig +++ b/lib/std/reset_event.zig @@ -16,7 +16,7 @@ pub const ResetEvent = struct { pub const OsEvent = if (builtin.single_threaded) DebugEvent - else if (builtin.link_libc and builtin.os != .windows and builtin.os != .linux) + else if (builtin.link_libc and builtin.os.tag != .windows and builtin.os.tag != .linux) PosixEvent else AtomicEvent; @@ -106,7 +106,7 @@ const PosixEvent = struct { fn deinit(self: *PosixEvent) void { // on dragonfly, *destroy() functions can return EINVAL // for statically initialized pthread structures - const err = if (builtin.os == .dragonfly) os.EINVAL else 0; + const err = if (builtin.os.tag == .dragonfly) os.EINVAL else 0; const retm = c.pthread_mutex_destroy(&self.mutex); assert(retm == 0 or retm == err); @@ -215,7 +215,7 @@ const AtomicEvent = struct { } } - pub const Futex = switch (builtin.os) { + pub const Futex = switch (builtin.os.tag) { .windows => WindowsFutex, .linux => LinuxFutex, else => SpinFutex, diff --git a/lib/std/special/c.zig b/lib/std/special/c.zig index 56ae3d0d8f..e0c2636df1 100644 --- a/lib/std/special/c.zig +++ b/lib/std/special/c.zig @@ -17,7 +17,7 @@ const is_msvc = switch (builtin.abi) { .msvc => true, else => false, }; -const is_freestanding = switch (builtin.os) { +const is_freestanding = switch (builtin.os.tag) { .freestanding => true, else => false, }; @@ -47,7 +47,7 @@ fn strcmp(s1: [*:0]const u8, s2: [*:0]const u8) callconv(.C) c_int { } fn strlen(s: [*:0]const u8) callconv(.C) usize { - return std.mem.len(u8, s); + return std.mem.len(s); } fn strncmp(_l: [*:0]const u8, _r: [*:0]const u8, _n: usize) callconv(.C) c_int { @@ -81,7 +81,7 @@ pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn @setCold(true); std.debug.panic("{}", .{msg}); } - if (builtin.os != .freestanding and builtin.os != .other) { + if (builtin.os.tag != .freestanding and builtin.os.tag != .other) { std.os.abort(); } while (true) {} @@ -178,11 +178,11 @@ test "test_bcmp" { comptime { if (builtin.mode != builtin.Mode.ReleaseFast and builtin.mode != builtin.Mode.ReleaseSmall and - builtin.os != builtin.Os.windows) + builtin.os.tag != .windows) { @export(__stack_chk_fail, .{ .name = "__stack_chk_fail" }); } - if (builtin.os == builtin.Os.linux) { + if (builtin.os.tag == .linux) { @export(clone, .{ .name = "clone" }); } } diff --git a/lib/std/special/compiler_rt.zig b/lib/std/special/compiler_rt.zig index 8d49fdbd2a..9ed866f62d 100644 --- a/lib/std/special/compiler_rt.zig +++ b/lib/std/special/compiler_rt.zig @@ -1,11 +1,9 @@ -const builtin = @import("builtin"); +const std = @import("std"); +const builtin = std.builtin; const is_test = builtin.is_test; -const is_gnu = switch (builtin.abi) { - .gnu, .gnuabin32, .gnuabi64, .gnueabi, .gnueabihf, .gnux32 => true, - else => false, -}; -const is_mingw = builtin.os == .windows and is_gnu; +const is_gnu = std.Target.current.abi.isGnu(); +const is_mingw = builtin.os.tag == .windows and is_gnu; comptime { const linkage = if (is_test) builtin.GlobalLinkage.Internal else builtin.GlobalLinkage.Weak; @@ -180,7 +178,7 @@ comptime { @export(@import("compiler_rt/arm.zig").__aeabi_memclr, .{ .name = "__aeabi_memclr4", .linkage = linkage }); @export(@import("compiler_rt/arm.zig").__aeabi_memclr, .{ .name = "__aeabi_memclr8", .linkage = linkage }); - if (builtin.os == .linux) { + if (builtin.os.tag == .linux) { @export(@import("compiler_rt/arm.zig").__aeabi_read_tp, .{ .name = "__aeabi_read_tp", .linkage = linkage }); } @@ -250,7 +248,7 @@ comptime { @export(@import("compiler_rt/aullrem.zig")._aullrem, .{ .name = "\x01__aullrem", .linkage = strong_linkage }); } - if (builtin.os == .windows) { + if (builtin.os.tag == .windows) { // Default stack-probe functions emitted by LLVM if (is_mingw) { @export(@import("compiler_rt/stack_probe.zig")._chkstk, .{ .name = "_alloca", .linkage = strong_linkage }); @@ -288,7 +286,7 @@ comptime { else => {}, } } else { - if (builtin.glibc_version != null) { + if (std.Target.current.isGnuLibC() and builtin.link_libc) { @export(__stack_chk_guard, .{ .name = "__stack_chk_guard", .linkage = linkage }); } @export(@import("compiler_rt/divti3.zig").__divti3, .{ .name = "__divti3", .linkage = linkage }); @@ -307,7 +305,7 @@ comptime { pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn { @setCold(true); if (is_test) { - @import("std").debug.panic("{}", .{msg}); + std.debug.panic("{}", .{msg}); } else { unreachable; } diff --git a/lib/std/special/compiler_rt/ashlti3.zig b/lib/std/special/compiler_rt/ashlti3.zig index 211515f9dd..e3bc60bbb9 100644 --- a/lib/std/special/compiler_rt/ashlti3.zig +++ b/lib/std/special/compiler_rt/ashlti3.zig @@ -24,7 +24,7 @@ const twords = extern union { all: i128, s: S, - const S = if (builtin.endian == builtin.Endian.Little) + const S = if (builtin.endian == .Little) struct { low: u64, high: u64, diff --git a/lib/std/special/compiler_rt/ashrti3.zig b/lib/std/special/compiler_rt/ashrti3.zig index 1bcd40d2e4..1cbe24fdeb 100644 --- a/lib/std/special/compiler_rt/ashrti3.zig +++ b/lib/std/special/compiler_rt/ashrti3.zig @@ -25,7 +25,7 @@ const twords = extern union { all: i128, s: S, - const S = if (builtin.endian == builtin.Endian.Little) + const S = if (builtin.endian == .Little) struct { low: i64, high: i64, diff --git a/lib/std/special/compiler_rt/extendXfYf2_test.zig b/lib/std/special/compiler_rt/extendXfYf2_test.zig index aa2faae901..e2664f6bae 100644 --- a/lib/std/special/compiler_rt/extendXfYf2_test.zig +++ b/lib/std/special/compiler_rt/extendXfYf2_test.zig @@ -90,7 +90,7 @@ test "extendhfsf2" { test__extendhfsf2(0x7f00, 0x7fe00000); // sNaN // On x86 the NaN becomes quiet because the return is pushed on the x87 // stack due to ABI requirements - if (builtin.arch != .i386 and builtin.os == .windows) + if (builtin.arch != .i386 and builtin.os.tag == .windows) test__extendhfsf2(0x7c01, 0x7f802000); // sNaN test__extendhfsf2(0, 0); // 0 diff --git a/lib/std/special/compiler_rt/lshrti3.zig b/lib/std/special/compiler_rt/lshrti3.zig index 043be5e2a2..e1c2bb5bd3 100644 --- a/lib/std/special/compiler_rt/lshrti3.zig +++ b/lib/std/special/compiler_rt/lshrti3.zig @@ -24,7 +24,7 @@ const twords = extern union { all: i128, s: S, - const S = if (builtin.endian == builtin.Endian.Little) + const S = if (builtin.endian == .Little) struct { low: u64, high: u64, diff --git a/lib/std/special/compiler_rt/multi3.zig b/lib/std/special/compiler_rt/multi3.zig index f46bd55421..eba58c45fc 100644 --- a/lib/std/special/compiler_rt/multi3.zig +++ b/lib/std/special/compiler_rt/multi3.zig @@ -45,7 +45,7 @@ const twords = extern union { all: i128, s: S, - const S = if (builtin.endian == builtin.Endian.Little) + const S = if (builtin.endian == .Little) struct { low: u64, high: u64, diff --git a/lib/std/special/compiler_rt/truncXfYf2.zig b/lib/std/special/compiler_rt/truncXfYf2.zig index 7c83c66ec0..cba5b85264 100644 --- a/lib/std/special/compiler_rt/truncXfYf2.zig +++ b/lib/std/special/compiler_rt/truncXfYf2.zig @@ -1,23 +1,23 @@ const std = @import("std"); pub fn __truncsfhf2(a: f32) callconv(.C) u16 { - return @bitCast(u16, truncXfYf2(f16, f32, a)); + return @bitCast(u16, @call(.{ .modifier = .always_inline }, truncXfYf2, .{ f16, f32, a })); } pub fn __truncdfhf2(a: f64) callconv(.C) u16 { - return @bitCast(u16, truncXfYf2(f16, f64, a)); + return @bitCast(u16, @call(.{ .modifier = .always_inline }, truncXfYf2, .{ f16, f64, a })); } pub fn __trunctfsf2(a: f128) callconv(.C) f32 { - return truncXfYf2(f32, f128, a); + return @call(.{ .modifier = .always_inline }, truncXfYf2, .{ f32, f128, a }); } pub fn __trunctfdf2(a: f128) callconv(.C) f64 { - return truncXfYf2(f64, f128, a); + return @call(.{ .modifier = .always_inline }, truncXfYf2, .{ f64, f128, a }); } pub fn __truncdfsf2(a: f64) callconv(.C) f32 { - return truncXfYf2(f32, f64, a); + return @call(.{ .modifier = .always_inline }, truncXfYf2, .{ f32, f64, a }); } pub fn __aeabi_d2f(a: f64) callconv(.AAPCS) f32 { @@ -35,7 +35,7 @@ pub fn __aeabi_f2h(a: f32) callconv(.AAPCS) u16 { return @call(.{ .modifier = .always_inline }, __truncsfhf2, .{a}); } -inline fn truncXfYf2(comptime dst_t: type, comptime src_t: type, a: src_t) dst_t { +fn truncXfYf2(comptime dst_t: type, comptime src_t: type, a: src_t) dst_t { const src_rep_t = std.meta.IntType(false, @typeInfo(src_t).Float.bits); const dst_rep_t = std.meta.IntType(false, @typeInfo(dst_t).Float.bits); const srcSigBits = std.math.floatMantissaBits(src_t); diff --git a/lib/std/special/compiler_rt/udivmod.zig b/lib/std/special/compiler_rt/udivmod.zig index 1cf2589b16..9f0fb7d50f 100644 --- a/lib/std/special/compiler_rt/udivmod.zig +++ b/lib/std/special/compiler_rt/udivmod.zig @@ -2,8 +2,8 @@ const builtin = @import("builtin"); const is_test = builtin.is_test; const low = switch (builtin.endian) { - builtin.Endian.Big => 1, - builtin.Endian.Little => 0, + .Big => 1, + .Little => 0, }; const high = 1 - low; diff --git a/lib/std/special/init-exe/build.zig b/lib/std/special/init-exe/build.zig index 0b7410f2ad..fd71588c5f 100644 --- a/lib/std/special/init-exe/build.zig +++ b/lib/std/special/init-exe/build.zig @@ -1,8 +1,18 @@ const Builder = @import("std").build.Builder; pub fn build(b: *Builder) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard release options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. const mode = b.standardReleaseOptions(); + const exe = b.addExecutable("$", "src/main.zig"); + exe.setTarget(target); exe.setBuildMode(mode); exe.install(); diff --git a/lib/std/special/init-exe/src/main.zig b/lib/std/special/init-exe/src/main.zig index 5f35540dc0..c6a70af56d 100644 --- a/lib/std/special/init-exe/src/main.zig +++ b/lib/std/special/init-exe/src/main.zig @@ -1,5 +1,5 @@ const std = @import("std"); pub fn main() anyerror!void { - std.debug.warn("All your base are belong to us.\n", .{}); + std.debug.warn("All your codebase are belong to us.\n", .{}); } diff --git a/lib/std/spinlock.zig b/lib/std/spinlock.zig index 1a3239a95c..0af08e9a84 100644 --- a/lib/std/spinlock.zig +++ b/lib/std/spinlock.zig @@ -46,7 +46,7 @@ pub const SpinLock = struct { // and yielding for 380-410 iterations was found to be // a nice sweet spot. Posix systems on the other hand, // especially linux, perform better by yielding the thread. - switch (builtin.os) { + switch (builtin.os.tag) { .windows => loopHint(400), else => std.os.sched_yield() catch loopHint(1), } diff --git a/lib/std/start.zig b/lib/std/start.zig index b58b6e8144..b8e3e97f94 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -12,7 +12,7 @@ const start_sym_name = if (builtin.arch.isMIPS()) "__start" else "_start"; comptime { if (builtin.output_mode == .Lib and builtin.link_mode == .Dynamic) { - if (builtin.os == .windows and !@hasDecl(root, "_DllMainCRTStartup")) { + if (builtin.os.tag == .windows and !@hasDecl(root, "_DllMainCRTStartup")) { @export(_DllMainCRTStartup, .{ .name = "_DllMainCRTStartup" }); } } else if (builtin.output_mode == .Exe or @hasDecl(root, "main")) { @@ -20,17 +20,17 @@ comptime { if (@typeInfo(@TypeOf(root.main)).Fn.calling_convention != .C) { @export(main, .{ .name = "main", .linkage = .Weak }); } - } else if (builtin.os == .windows) { + } else if (builtin.os.tag == .windows) { if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup")) { @export(WinMainCRTStartup, .{ .name = "WinMainCRTStartup" }); } - } else if (builtin.os == .uefi) { + } else if (builtin.os.tag == .uefi) { if (!@hasDecl(root, "EfiMain")) @export(EfiMain, .{ .name = "EfiMain" }); - } else if (builtin.arch.isWasm() and builtin.os == .freestanding) { + } else if (builtin.arch.isWasm() and builtin.os.tag == .freestanding) { if (!@hasDecl(root, start_sym_name)) @export(wasm_freestanding_start, .{ .name = start_sym_name }); - } else if (builtin.os != .other and builtin.os != .freestanding) { + } else if (builtin.os.tag != .other and builtin.os.tag != .freestanding) { if (!@hasDecl(root, start_sym_name)) @export(_start, .{ .name = start_sym_name }); } } @@ -78,7 +78,7 @@ fn EfiMain(handle: uefi.Handle, system_table: *uefi.tables.SystemTable) callconv } fn _start() callconv(.Naked) noreturn { - if (builtin.os == builtin.Os.wasi) { + if (builtin.os.tag == .wasi) { // This is marked inline because for some reason LLVM in release mode fails to inline it, // and we want fewer call frames in stack traces. std.os.wasi.proc_exit(@call(.{ .modifier = .always_inline }, callMain, .{})); @@ -133,7 +133,7 @@ fn WinMainCRTStartup() callconv(.Stdcall) noreturn { // TODO https://github.com/ziglang/zig/issues/265 fn posixCallMainAndExit() noreturn { - if (builtin.os == builtin.Os.freebsd) { + if (builtin.os.tag == .freebsd) { @setAlignStack(16); } const argc = starting_stack_ptr[0]; @@ -144,7 +144,7 @@ fn posixCallMainAndExit() noreturn { while (envp_optional[envp_count]) |_| : (envp_count += 1) {} const envp = @ptrCast([*][*:0]u8, envp_optional)[0..envp_count]; - if (builtin.os == .linux) { + if (builtin.os.tag == .linux) { // Find the beginning of the auxiliary vector const auxv = @ptrCast([*]std.elf.Auxv, @alignCast(@alignOf(usize), envp.ptr + envp_count + 1)); std.os.linux.elf_aux_maybe = auxv; diff --git a/lib/std/target.zig b/lib/std/target.zig index ba61a2c5f0..778a93b90d 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -1,61 +1,291 @@ const std = @import("std.zig"); const mem = std.mem; const builtin = std.builtin; +const Version = std.builtin.Version; /// TODO Nearly all the functions in this namespace would be /// better off if https://github.com/ziglang/zig/issues/425 /// was solved. -pub const Target = union(enum) { - Native: void, - Cross: Cross, +pub const Target = struct { + cpu: Cpu, + os: Os, + abi: Abi, - pub const Os = enum { - freestanding, - ananas, - cloudabi, - dragonfly, - freebsd, - fuchsia, - ios, - kfreebsd, - linux, - lv2, - macosx, - netbsd, - openbsd, - solaris, - windows, - haiku, - minix, - rtems, - nacl, - cnk, - aix, - cuda, - nvcl, - amdhsa, - ps4, - elfiamcu, - tvos, - watchos, - mesa3d, - contiki, - amdpal, - hermit, - hurd, - wasi, - emscripten, - uefi, - other, + pub const Os = struct { + tag: Tag, + version_range: VersionRange, - pub fn parse(text: []const u8) !Os { - const info = @typeInfo(Os); - inline for (info.Enum.fields) |field| { - if (mem.eql(u8, text, field.name)) { - return @field(Os, field.name); + pub const Tag = enum { + freestanding, + ananas, + cloudabi, + dragonfly, + freebsd, + fuchsia, + ios, + kfreebsd, + linux, + lv2, + macosx, + netbsd, + openbsd, + solaris, + windows, + haiku, + minix, + rtems, + nacl, + cnk, + aix, + cuda, + nvcl, + amdhsa, + ps4, + elfiamcu, + tvos, + watchos, + mesa3d, + contiki, + amdpal, + hermit, + hurd, + wasi, + emscripten, + uefi, + other, + + pub fn isDarwin(tag: Tag) bool { + return switch (tag) { + .ios, .macosx, .watchos, .tvos => true, + else => false, + }; + } + + pub fn dynamicLibSuffix(tag: Tag) [:0]const u8 { + if (tag.isDarwin()) { + return ".dylib"; + } + switch (tag) { + .windows => return ".dll", + else => return ".so", } } - return error.UnknownOperatingSystem; + }; + + /// Based on NTDDI version constants from + /// https://docs.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt + pub const WindowsVersion = enum(u32) { + nt4 = 0x04000000, + win2k = 0x05000000, + xp = 0x05010000, + ws2003 = 0x05020000, + vista = 0x06000000, + win7 = 0x06010000, + win8 = 0x06020000, + win8_1 = 0x06030000, + win10 = 0x0A000000, + win10_th2 = 0x0A000001, + win10_rs1 = 0x0A000002, + win10_rs2 = 0x0A000003, + win10_rs3 = 0x0A000004, + win10_rs4 = 0x0A000005, + win10_rs5 = 0x0A000006, + win10_19h1 = 0x0A000007, + _, + + pub const Range = struct { + min: WindowsVersion, + max: WindowsVersion, + + pub fn includesVersion(self: Range, ver: WindowsVersion) bool { + return @enumToInt(ver) >= @enumToInt(self.min) and @enumToInt(ver) <= @enumToInt(self.max); + } + }; + }; + + pub const LinuxVersionRange = struct { + range: Version.Range, + glibc: Version, + + pub fn includesVersion(self: LinuxVersionRange, ver: Version) bool { + return self.range.includesVersion(ver); + } + }; + + /// The version ranges here represent the minimum OS version to be supported + /// and the maximum OS version to be supported. The default values represent + /// the range that the Zig Standard Library bases its abstractions on. + /// + /// The minimum version of the range is the main setting to tweak for a target. + /// Usually, the maximum target OS version will remain the default, which is + /// the latest released version of the OS. + /// + /// To test at compile time if the target is guaranteed to support a given OS feature, + /// one should check that the minimum version of the range is greater than or equal to + /// the version the feature was introduced in. + /// + /// To test at compile time if the target certainly will not support a given OS feature, + /// one should check that the maximum version of the range is less than the version the + /// feature was introduced in. + /// + /// If neither of these cases apply, a runtime check should be used to determine if the + /// target supports a given OS feature. + /// + /// Binaries built with a given maximum version will continue to function on newer operating system + /// versions. However, such a binary may not take full advantage of the newer operating system APIs. + pub const VersionRange = union { + none: void, + semver: Version.Range, + linux: LinuxVersionRange, + windows: WindowsVersion.Range, + + /// The default `VersionRange` represents the range that the Zig Standard Library + /// bases its abstractions on. + pub fn default(tag: Tag) VersionRange { + switch (tag) { + .freestanding, + .ananas, + .cloudabi, + .dragonfly, + .fuchsia, + .kfreebsd, + .lv2, + .solaris, + .haiku, + .minix, + .rtems, + .nacl, + .cnk, + .aix, + .cuda, + .nvcl, + .amdhsa, + .ps4, + .elfiamcu, + .mesa3d, + .contiki, + .amdpal, + .hermit, + .hurd, + .wasi, + .emscripten, + .uefi, + .other, + => return .{ .none = {} }, + + .freebsd => return .{ + .semver = Version.Range{ + .min = .{ .major = 12, .minor = 0 }, + .max = .{ .major = 12, .minor = 1 }, + }, + }, + .macosx => return .{ + .semver = .{ + .min = .{ .major = 10, .minor = 13 }, + .max = .{ .major = 10, .minor = 15, .patch = 3 }, + }, + }, + .ios => return .{ + .semver = .{ + .min = .{ .major = 12, .minor = 0 }, + .max = .{ .major = 13, .minor = 4, .patch = 0 }, + }, + }, + .watchos => return .{ + .semver = .{ + .min = .{ .major = 6, .minor = 0 }, + .max = .{ .major = 6, .minor = 2, .patch = 0 }, + }, + }, + .tvos => return .{ + .semver = .{ + .min = .{ .major = 13, .minor = 0 }, + .max = .{ .major = 13, .minor = 4, .patch = 0 }, + }, + }, + .netbsd => return .{ + .semver = .{ + .min = .{ .major = 8, .minor = 0 }, + .max = .{ .major = 9, .minor = 0 }, + }, + }, + .openbsd => return .{ + .semver = .{ + .min = .{ .major = 6, .minor = 6 }, + .max = .{ .major = 6, .minor = 6 }, + }, + }, + + .linux => return .{ + .linux = .{ + .range = .{ + .min = .{ .major = 3, .minor = 16 }, + .max = .{ .major = 5, .minor = 5, .patch = 5 }, + }, + .glibc = .{ .major = 2, .minor = 17 }, + }, + }, + + .windows => return .{ + .windows = .{ + .min = .win8_1, + .max = .win10_19h1, + }, + }, + } + } + }; + + pub fn defaultVersionRange(tag: Tag) Os { + return .{ + .tag = tag, + .version_range = VersionRange.default(tag), + }; + } + + pub fn requiresLibC(os: Os) bool { + return switch (os.tag) { + .freebsd, + .netbsd, + .macosx, + .ios, + .tvos, + .watchos, + .dragonfly, + .openbsd, + => true, + + .linux, + .windows, + .freestanding, + .ananas, + .cloudabi, + .fuchsia, + .kfreebsd, + .lv2, + .solaris, + .haiku, + .minix, + .rtems, + .nacl, + .cnk, + .aix, + .cuda, + .nvcl, + .amdhsa, + .ps4, + .elfiamcu, + .mesa3d, + .contiki, + .amdpal, + .hermit, + .hurd, + .wasi, + .emscripten, + .uefi, + .other, + => false, + }; } }; @@ -98,11 +328,10 @@ pub const Target = union(enum) { macabi, pub fn default(arch: Cpu.Arch, target_os: Os) Abi { - switch (arch) { - .wasm32, .wasm64 => return .musl, - else => {}, + if (arch.isWasm()) { + return .musl; } - switch (target_os) { + switch (target_os.tag) { .freestanding, .ananas, .cloudabi, @@ -147,14 +376,25 @@ pub const Target = union(enum) { } } - pub fn parse(text: []const u8) !Abi { - const info = @typeInfo(Abi); - inline for (info.Enum.fields) |field| { - if (mem.eql(u8, text, field.name)) { - return @field(Abi, field.name); - } - } - return error.UnknownApplicationBinaryInterface; + pub fn isGnu(abi: Abi) bool { + return switch (abi) { + .gnu, .gnuabin32, .gnuabi64, .gnueabi, .gnueabihf, .gnux32 => true, + else => false, + }; + } + + pub fn isMusl(abi: Abi) bool { + return switch (abi) { + .musl, .musleabi, .musleabihf => true, + else => false, + }; + } + + pub fn oFileExt(abi: Abi) [:0]const u8 { + return switch (abi) { + .msvc => ".obj", + else => ".o", + }; } }; @@ -177,12 +417,6 @@ pub const Target = union(enum) { EfiRuntimeDriver, }; - pub const Cross = struct { - cpu: Cpu, - os: Os, - abi: Abi, - }; - pub const Cpu = struct { /// Architecture arch: Arch, @@ -228,6 +462,12 @@ pub const Target = union(enum) { return Set{ .ints = [1]usize{0} ** usize_count }; } + pub fn isEmpty(set: Set) bool { + return for (set.ints) |x| { + if (x != 0) break false; + } else true; + } + pub fn isEnabled(set: Set, arch_feature_index: Index) bool { const usize_index = arch_feature_index / @bitSizeOf(usize); const bit_index = @intCast(ShiftInt, arch_feature_index % @bitSizeOf(usize)); @@ -254,6 +494,15 @@ pub const Target = union(enum) { set.ints[usize_index] &= ~(@as(usize, 1) << bit_index); } + /// Removes the specified feature but not its dependents. + pub fn removeFeatureSet(set: *Set, other_set: Set) void { + // TODO should be able to use binary not on @Vector type. + // https://github.com/ziglang/zig/issues/903 + for (set.ints) |*int, i| { + int.* &= ~other_set.ints[i]; + } + } + pub fn populateDependencies(set: *Set, all_features_list: []const Cpu.Feature) void { @setEvalBranchQuota(1000000); @@ -392,7 +641,7 @@ pub const Target = union(enum) { return cpu; } } - return error.UnknownCpu; + return error.UnknownCpuModel; } pub fn toElfMachine(arch: Arch) std.elf.EM { @@ -508,6 +757,67 @@ pub const Target = union(enum) { }; } + pub fn ptrBitWidth(arch: Arch) u32 { + switch (arch) { + .avr, + .msp430, + => return 16, + + .arc, + .arm, + .armeb, + .hexagon, + .le32, + .mips, + .mipsel, + .powerpc, + .r600, + .riscv32, + .sparc, + .sparcel, + .tce, + .tcele, + .thumb, + .thumbeb, + .i386, + .xcore, + .nvptx, + .amdil, + .hsail, + .spir, + .kalimba, + .shave, + .lanai, + .wasm32, + .renderscript32, + .aarch64_32, + => return 32, + + .aarch64, + .aarch64_be, + .mips64, + .mips64el, + .powerpc64, + .powerpc64le, + .riscv64, + .x86_64, + .nvptx64, + .le64, + .amdil64, + .hsail64, + .spir64, + .wasm64, + .renderscript64, + .amdgcn, + .bpfel, + .bpfeb, + .sparcv9, + .s390x, + .ve, + => return 64, + } + } + /// Returns a name that matches the lib/std/target/* directory name. pub fn genericName(arch: Arch) []const u8 { return switch (arch) { @@ -575,16 +885,6 @@ pub const Target = union(enum) { else => &[0]*const Model{}, }; } - - pub fn parse(text: []const u8) !Arch { - const info = @typeInfo(Arch); - inline for (info.Enum.fields) |field| { - if (mem.eql(u8, text, field.name)) { - return @as(Arch, @field(Arch, field.name)); - } - } - return error.UnknownArchitecture; - } }; pub const Model = struct { @@ -601,525 +901,172 @@ pub const Target = union(enum) { .features = features, }; } + + pub fn baseline(arch: Arch) *const Model { + const S = struct { + const generic_model = Model{ + .name = "generic", + .llvm_name = null, + .features = Cpu.Feature.Set.empty, + }; + }; + return switch (arch) { + .arm, .armeb, .thumb, .thumbeb => &arm.cpu.baseline, + .aarch64, .aarch64_be, .aarch64_32 => &aarch64.cpu.generic, + .avr => &avr.cpu.avr1, + .bpfel, .bpfeb => &bpf.cpu.generic, + .hexagon => &hexagon.cpu.generic, + .mips, .mipsel => &mips.cpu.mips32, + .mips64, .mips64el => &mips.cpu.mips64, + .msp430 => &msp430.cpu.generic, + .powerpc, .powerpc64, .powerpc64le => &powerpc.cpu.generic, + .amdgcn => &amdgpu.cpu.generic, + .riscv32 => &riscv.cpu.baseline_rv32, + .riscv64 => &riscv.cpu.baseline_rv64, + .sparc, .sparcv9, .sparcel => &sparc.cpu.generic, + .s390x => &systemz.cpu.generic, + .i386 => &x86.cpu.pentium4, + .x86_64 => &x86.cpu.x86_64, + .nvptx, .nvptx64 => &nvptx.cpu.sm_20, + .wasm32, .wasm64 => &wasm.cpu.generic, + + else => &S.generic_model, + }; + } }; /// The "default" set of CPU features for cross-compiling. A conservative set /// of features that is expected to be supported on most available hardware. pub fn baseline(arch: Arch) Cpu { - const S = struct { - const generic_model = Model{ - .name = "generic", - .llvm_name = null, - .features = Cpu.Feature.Set.empty, - }; - }; - const model = switch (arch) { - .arm, .armeb, .thumb, .thumbeb => &arm.cpu.baseline, - .aarch64, .aarch64_be, .aarch64_32 => &aarch64.cpu.generic, - .avr => &avr.cpu.avr1, - .bpfel, .bpfeb => &bpf.cpu.generic, - .hexagon => &hexagon.cpu.generic, - .mips, .mipsel => &mips.cpu.mips32, - .mips64, .mips64el => &mips.cpu.mips64, - .msp430 => &msp430.cpu.generic, - .powerpc, .powerpc64, .powerpc64le => &powerpc.cpu.generic, - .amdgcn => &amdgpu.cpu.generic, - .riscv32 => &riscv.cpu.baseline_rv32, - .riscv64 => &riscv.cpu.baseline_rv64, - .sparc, .sparcv9, .sparcel => &sparc.cpu.generic, - .s390x => &systemz.cpu.generic, - .i386 => &x86.cpu.pentium4, - .x86_64 => &x86.cpu.x86_64, - .nvptx, .nvptx64 => &nvptx.cpu.sm_20, - .wasm32, .wasm64 => &wasm.cpu.generic, - - else => &S.generic_model, - }; - return model.toCpu(arch); + return Model.baseline(arch).toCpu(arch); } }; pub const current = Target{ - .Cross = Cross{ - .cpu = builtin.cpu, - .os = builtin.os, - .abi = builtin.abi, - }, + .cpu = builtin.cpu, + .os = builtin.os, + .abi = builtin.abi, }; pub const stack_align = 16; - pub fn zigTriple(self: Target, allocator: *mem.Allocator) ![]u8 { - return std.fmt.allocPrint(allocator, "{}-{}-{}", .{ - @tagName(self.getArch()), - @tagName(self.getOs()), - @tagName(self.getAbi()), - }); + pub fn zigTriple(self: Target, allocator: *mem.Allocator) ![:0]u8 { + return std.zig.CrossTarget.fromTarget(self).zigTriple(allocator); } - /// Returned slice must be freed by the caller. - pub fn vcpkgTriplet(allocator: *mem.Allocator, target: Target, linkage: std.build.VcpkgLinkage) ![]const u8 { - const arch = switch (target.getArch()) { - .i386 => "x86", - .x86_64 => "x64", + pub fn linuxTripleSimple(allocator: *mem.Allocator, cpu_arch: Cpu.Arch, os_tag: Os.Tag, abi: Abi) ![:0]u8 { + return std.fmt.allocPrint0(allocator, "{}-{}-{}", .{ @tagName(cpu_arch), @tagName(os_tag), @tagName(abi) }); + } - .arm, - .armeb, - .thumb, - .thumbeb, - .aarch64_32, - => "arm", + pub fn linuxTriple(self: Target, allocator: *mem.Allocator) ![:0]u8 { + return linuxTripleSimple(allocator, self.cpu.arch, self.os.tag, self.abi); + } - .aarch64, - .aarch64_be, - => "arm64", + pub fn oFileExt(self: Target) [:0]const u8 { + return self.abi.oFileExt(); + } - else => return error.VcpkgNoSuchArchitecture, - }; - - const os = switch (target.getOs()) { - .windows => "windows", - .linux => "linux", - .macosx => "macos", - else => return error.VcpkgNoSuchOs, - }; - - if (linkage == .Static) { - return try mem.join(allocator, "-", &[_][]const u8{ arch, os, "static" }); - } else { - return try mem.join(allocator, "-", &[_][]const u8{ arch, os }); + pub fn exeFileExtSimple(cpu_arch: Cpu.Arch, os_tag: Os.Tag) [:0]const u8 { + switch (os_tag) { + .windows => return ".exe", + .uefi => return ".efi", + else => if (cpu_arch.isWasm()) { + return ".wasm"; + } else { + return ""; + }, } } - pub fn allocDescription(self: Target, allocator: *mem.Allocator) ![]u8 { - // TODO is there anything else worthy of the description that is not - // already captured in the triple? - return self.zigTriple(allocator); + pub fn exeFileExt(self: Target) [:0]const u8 { + return exeFileExtSimple(self.cpu.arch, self.os.tag); } - pub fn zigTripleNoSubArch(self: Target, allocator: *mem.Allocator) ![]u8 { - return std.fmt.allocPrint(allocator, "{}-{}-{}", .{ - @tagName(self.getArch()), - @tagName(self.getOs()), - @tagName(self.getAbi()), - }); - } - - pub fn linuxTriple(self: Target, allocator: *mem.Allocator) ![]u8 { - return std.fmt.allocPrint(allocator, "{}-{}-{}", .{ - @tagName(self.getArch()), - @tagName(self.getOs()), - @tagName(self.getAbi()), - }); - } - - pub const ParseOptions = struct { - /// This is sometimes called a "triple". It looks roughly like this: - /// riscv64-linux-gnu - /// The fields are, respectively: - /// * CPU Architecture - /// * Operating System - /// * C ABI (optional) - arch_os_abi: []const u8, - - /// Looks like "name+a+b-c-d+e", where "name" is a CPU Model name, "a", "b", and "e" - /// are examples of CPU features to add to the set, and "c" and "d" are examples of CPU features - /// to remove from the set. - cpu_features: []const u8 = "baseline", - - /// If this is provided, the function will populate some information about parsing failures, - /// so that user-friendly error messages can be delivered. - diagnostics: ?*Diagnostics = null, - - pub const Diagnostics = struct { - /// If the architecture was determined, this will be populated. - arch: ?Cpu.Arch = null, - - /// If the OS was determined, this will be populated. - os: ?Os = null, - - /// If the ABI was determined, this will be populated. - abi: ?Abi = null, - - /// If the CPU name was determined, this will be populated. - cpu_name: ?[]const u8 = null, - - /// If error.UnknownCpuFeature is returned, this will be populated. - unknown_feature_name: ?[]const u8 = null, - }; - }; - - pub fn parse(args: ParseOptions) !Target { - var dummy_diags: ParseOptions.Diagnostics = undefined; - var diags = args.diagnostics orelse &dummy_diags; - - var it = mem.separate(args.arch_os_abi, "-"); - const arch_name = it.next() orelse return error.MissingArchitecture; - const arch = try Cpu.Arch.parse(arch_name); - diags.arch = arch; - - const os_name = it.next() orelse return error.MissingOperatingSystem; - const os = try Os.parse(os_name); - diags.os = os; - - const abi_name = it.next(); - const abi = if (abi_name) |n| try Abi.parse(n) else Abi.default(arch, os); - diags.abi = abi; - - if (it.next() != null) return error.UnexpectedExtraField; - - const all_features = arch.allFeaturesList(); - var index: usize = 0; - while (index < args.cpu_features.len and - args.cpu_features[index] != '+' and - args.cpu_features[index] != '-') - { - index += 1; - } - const cpu_name = args.cpu_features[0..index]; - diags.cpu_name = cpu_name; - - const cpu: Cpu = if (mem.eql(u8, cpu_name, "baseline")) Cpu.baseline(arch) else blk: { - const cpu_model = try arch.parseCpuModel(cpu_name); - - var set = cpu_model.features; - while (index < args.cpu_features.len) { - const op = args.cpu_features[index]; - index += 1; - const start = index; - while (index < args.cpu_features.len and - args.cpu_features[index] != '+' and - args.cpu_features[index] != '-') - { - index += 1; - } - const feature_name = args.cpu_features[start..index]; - for (all_features) |feature, feat_index_usize| { - const feat_index = @intCast(Cpu.Feature.Set.Index, feat_index_usize); - if (mem.eql(u8, feature_name, feature.name)) { - switch (op) { - '+' => set.addFeature(feat_index), - '-' => set.removeFeature(feat_index), - else => unreachable, - } - break; - } - } else { - diags.unknown_feature_name = feature_name; - return error.UnknownCpuFeature; - } - } - set.populateDependencies(all_features); - break :blk .{ - .arch = arch, - .model = cpu_model, - .features = set, - }; - }; - var cross = Cross{ - .cpu = cpu, - .os = os, - .abi = abi, - }; - return Target{ .Cross = cross }; - } - - pub fn oFileExt(self: Target) []const u8 { - return switch (self.getAbi()) { - .msvc => ".obj", - else => ".o", - }; - } - - pub fn exeFileExt(self: Target) []const u8 { - if (self.isWindows()) { - return ".exe"; - } else if (self.isUefi()) { - return ".efi"; - } else if (self.isWasm()) { - return ".wasm"; - } else { - return ""; - } - } - - pub fn staticLibSuffix(self: Target) []const u8 { - if (self.isWasm()) { + pub fn staticLibSuffix_cpu_arch_abi(cpu_arch: Cpu.Arch, abi: Abi) [:0]const u8 { + if (cpu_arch.isWasm()) { return ".wasm"; } - switch (self.getAbi()) { + switch (abi) { .msvc => return ".lib", else => return ".a", } } - pub fn dynamicLibSuffix(self: Target) []const u8 { - if (self.isDarwin()) { - return ".dylib"; - } - switch (self.getOs()) { - .windows => return ".dll", - else => return ".so", - } + pub fn staticLibSuffix(self: Target) [:0]const u8 { + return staticLibSuffix_cpu_arch_abi(self.cpu.arch, self.abi); } - pub fn libPrefix(self: Target) []const u8 { - if (self.isWasm()) { + pub fn dynamicLibSuffix(self: Target) [:0]const u8 { + return self.os.tag.dynamicLibSuffix(); + } + + pub fn libPrefix_cpu_arch_abi(cpu_arch: Cpu.Arch, abi: Abi) [:0]const u8 { + if (cpu_arch.isWasm()) { return ""; } - switch (self.getAbi()) { + switch (abi) { .msvc => return "", else => return "lib", } } - pub fn getOs(self: Target) Os { - return switch (self) { - .Native => builtin.os, - .Cross => |t| t.os, - }; + pub fn libPrefix(self: Target) [:0]const u8 { + return libPrefix_cpu_arch_abi(self.cpu.arch, self.abi); } - pub fn getCpu(self: Target) Cpu { - return switch (self) { - .Native => builtin.cpu, - .Cross => |cross| cross.cpu, - }; - } - - pub fn getArch(self: Target) Cpu.Arch { - return self.getCpu().arch; - } - - pub fn getAbi(self: Target) Abi { - switch (self) { - .Native => return builtin.abi, - .Cross => |t| return t.abi, + pub fn getObjectFormatSimple(os_tag: Os.Tag, cpu_arch: Cpu.Arch) ObjectFormat { + if (os_tag == .windows or os_tag == .uefi) { + return .coff; + } else if (os_tag.isDarwin()) { + return .macho; } + if (cpu_arch.isWasm()) { + return .wasm; + } + return .elf; } pub fn getObjectFormat(self: Target) ObjectFormat { - switch (self) { - .Native => return @import("builtin").object_format, - .Cross => blk: { - if (self.isWindows() or self.isUefi()) { - return .coff; - } else if (self.isDarwin()) { - return .macho; - } - if (self.isWasm()) { - return .wasm; - } - return .elf; - }, - } + return getObjectFormatSimple(self.os.tag, self.cpu.arch); } pub fn isMinGW(self: Target) bool { - return self.isWindows() and self.isGnu(); + return self.os.tag == .windows and self.isGnu(); } pub fn isGnu(self: Target) bool { - return switch (self.getAbi()) { - .gnu, .gnuabin32, .gnuabi64, .gnueabi, .gnueabihf, .gnux32 => true, - else => false, - }; + return self.abi.isGnu(); } pub fn isMusl(self: Target) bool { - return switch (self.getAbi()) { - .musl, .musleabi, .musleabihf => true, - else => false, - }; - } - - pub fn isDarwin(self: Target) bool { - return switch (self.getOs()) { - .ios, .macosx, .watchos, .tvos => true, - else => false, - }; - } - - pub fn isWindows(self: Target) bool { - return switch (self.getOs()) { - .windows => true, - else => false, - }; - } - - pub fn isLinux(self: Target) bool { - return switch (self.getOs()) { - .linux => true, - else => false, - }; + return self.abi.isMusl(); } pub fn isAndroid(self: Target) bool { - return switch (self.getAbi()) { + return switch (self.abi) { .android => true, else => false, }; } - pub fn isDragonFlyBSD(self: Target) bool { - return switch (self.getOs()) { - .dragonfly => true, - else => false, - }; - } - - pub fn isUefi(self: Target) bool { - return switch (self.getOs()) { - .uefi => true, - else => false, - }; - } - pub fn isWasm(self: Target) bool { - return switch (self.getArch()) { - .wasm32, .wasm64 => true, - else => false, - }; + return self.cpu.arch.isWasm(); } - pub fn isFreeBSD(self: Target) bool { - return switch (self.getOs()) { - .freebsd => true, - else => false, - }; + pub fn isDarwin(self: Target) bool { + return self.os.tag.isDarwin(); } - pub fn isNetBSD(self: Target) bool { - return switch (self.getOs()) { - .netbsd => true, - else => false, - }; + pub fn isGnuLibC_os_tag_abi(os_tag: Os.Tag, abi: Abi) bool { + return os_tag == .linux and abi.isGnu(); } - pub fn wantSharedLibSymLinks(self: Target) bool { - return !self.isWindows(); - } - - pub fn osRequiresLibC(self: Target) bool { - return self.isDarwin() or self.isFreeBSD() or self.isNetBSD(); - } - - pub fn getArchPtrBitWidth(self: Target) u32 { - switch (self.getArch()) { - .avr, - .msp430, - => return 16, - - .arc, - .arm, - .armeb, - .hexagon, - .le32, - .mips, - .mipsel, - .powerpc, - .r600, - .riscv32, - .sparc, - .sparcel, - .tce, - .tcele, - .thumb, - .thumbeb, - .i386, - .xcore, - .nvptx, - .amdil, - .hsail, - .spir, - .kalimba, - .shave, - .lanai, - .wasm32, - .renderscript32, - .aarch64_32, - => return 32, - - .aarch64, - .aarch64_be, - .mips64, - .mips64el, - .powerpc64, - .powerpc64le, - .riscv64, - .x86_64, - .nvptx64, - .le64, - .amdil64, - .hsail64, - .spir64, - .wasm64, - .renderscript64, - .amdgcn, - .bpfel, - .bpfeb, - .sparcv9, - .s390x, - .ve, - => return 64, - } + pub fn isGnuLibC(self: Target) bool { + return isGnuLibC_os_tag_abi(self.os.tag, self.abi); } pub fn supportsNewStackCall(self: Target) bool { - return !self.isWasm(); - } - - pub const Executor = union(enum) { - native, - qemu: []const u8, - wine: []const u8, - wasmtime: []const u8, - unavailable, - }; - - pub fn getExternalExecutor(self: Target) Executor { - if (@as(@TagType(Target), self) == .Native) return .native; - - // If the target OS matches the host OS, we can use QEMU to emulate a foreign architecture. - if (self.getOs() == builtin.os) { - return switch (self.getArch()) { - .aarch64 => Executor{ .qemu = "qemu-aarch64" }, - .aarch64_be => Executor{ .qemu = "qemu-aarch64_be" }, - .arm => Executor{ .qemu = "qemu-arm" }, - .armeb => Executor{ .qemu = "qemu-armeb" }, - .i386 => Executor{ .qemu = "qemu-i386" }, - .mips => Executor{ .qemu = "qemu-mips" }, - .mipsel => Executor{ .qemu = "qemu-mipsel" }, - .mips64 => Executor{ .qemu = "qemu-mips64" }, - .mips64el => Executor{ .qemu = "qemu-mips64el" }, - .powerpc => Executor{ .qemu = "qemu-ppc" }, - .powerpc64 => Executor{ .qemu = "qemu-ppc64" }, - .powerpc64le => Executor{ .qemu = "qemu-ppc64le" }, - .riscv32 => Executor{ .qemu = "qemu-riscv32" }, - .riscv64 => Executor{ .qemu = "qemu-riscv64" }, - .s390x => Executor{ .qemu = "qemu-s390x" }, - .sparc => Executor{ .qemu = "qemu-sparc" }, - .x86_64 => Executor{ .qemu = "qemu-x86_64" }, - else => return .unavailable, - }; - } - - if (self.isWindows()) { - switch (self.getArchPtrBitWidth()) { - 32 => return Executor{ .wine = "wine" }, - 64 => return Executor{ .wine = "wine64" }, - else => return .unavailable, - } - } - - if (self.getOs() == .wasi) { - switch (self.getArchPtrBitWidth()) { - 32 => return Executor{ .wasmtime = "wasmtime" }, - else => return .unavailable, - } - } - - return .unavailable; + return !self.cpu.arch.isWasm(); } pub const FloatAbi = enum { @@ -1129,7 +1076,7 @@ pub const Target = union(enum) { }; pub fn getFloatAbi(self: Target) FloatAbi { - return switch (self.getAbi()) { + return switch (self.abi) { .gnueabihf, .eabihf, .musleabihf, @@ -1139,13 +1086,10 @@ pub const Target = union(enum) { } pub fn hasDynamicLinker(self: Target) bool { - switch (self.getArch()) { - .wasm32, - .wasm64, - => return false, - else => {}, + if (self.cpu.arch.isWasm()) { + return false; } - switch (self.getOs()) { + switch (self.os.tag) { .freestanding, .ios, .tvos, @@ -1160,65 +1104,93 @@ pub const Target = union(enum) { } } - /// Caller owns returned memory. - pub fn getStandardDynamicLinkerPath( - self: Target, - allocator: *mem.Allocator, - ) error{ - OutOfMemory, - UnknownDynamicLinkerPath, - TargetHasNoDynamicLinker, - }![:0]u8 { - const a = allocator; + pub const DynamicLinker = struct { + /// Contains the memory used to store the dynamic linker path. This field should + /// not be used directly. See `get` and `set`. This field exists so that this API requires no allocator. + buffer: [255]u8 = undefined, + + /// Used to construct the dynamic linker path. This field should not be used + /// directly. See `get` and `set`. + max_byte: ?u8 = null, + + /// Asserts that the length is less than or equal to 255 bytes. + pub fn init(dl_or_null: ?[]const u8) DynamicLinker { + var result: DynamicLinker = undefined; + result.set(dl_or_null); + return result; + } + + /// The returned memory has the same lifetime as the `DynamicLinker`. + pub fn get(self: *const DynamicLinker) ?[]const u8 { + const m: usize = self.max_byte orelse return null; + return self.buffer[0 .. m + 1]; + } + + /// Asserts that the length is less than or equal to 255 bytes. + pub fn set(self: *DynamicLinker, dl_or_null: ?[]const u8) void { + if (dl_or_null) |dl| { + mem.copy(u8, &self.buffer, dl); + self.max_byte = @intCast(u8, dl.len - 1); + } else { + self.max_byte = null; + } + } + }; + + pub fn standardDynamicLinkerPath(self: Target) DynamicLinker { + var result: DynamicLinker = .{}; + const S = struct { + fn print(r: *DynamicLinker, comptime fmt: []const u8, args: var) DynamicLinker { + r.max_byte = @intCast(u8, (std.fmt.bufPrint(&r.buffer, fmt, args) catch unreachable).len - 1); + return r.*; + } + fn copy(r: *DynamicLinker, s: []const u8) DynamicLinker { + mem.copy(u8, &r.buffer, s); + r.max_byte = @intCast(u8, s.len - 1); + return r.*; + } + }; + const print = S.print; + const copy = S.copy; + if (self.isAndroid()) { - return mem.dupeZ(a, u8, if (self.getArchPtrBitWidth() == 64) - "/system/bin/linker64" - else - "/system/bin/linker"); + const suffix = if (self.cpu.arch.ptrBitWidth() == 64) "64" else ""; + return print(&result, "/system/bin/linker{}", .{suffix}); } if (self.isMusl()) { - var result = try std.Buffer.init(allocator, "/lib/ld-musl-"); - defer result.deinit(); - - var is_arm = false; - switch (self.getArch()) { - .arm, .thumb => { - try result.append("arm"); - is_arm = true; - }, - .armeb, .thumbeb => { - try result.append("armeb"); - is_arm = true; - }, - else => |arch| try result.append(@tagName(arch)), - } - if (is_arm and self.getFloatAbi() == .hard) { - try result.append("hf"); - } - try result.append(".so.1"); - return result.toOwnedSlice(); + const is_arm = switch (self.cpu.arch) { + .arm, .armeb, .thumb, .thumbeb => true, + else => false, + }; + const arch_part = switch (self.cpu.arch) { + .arm, .thumb => "arm", + .armeb, .thumbeb => "armeb", + else => |arch| @tagName(arch), + }; + const arch_suffix = if (is_arm and self.getFloatAbi() == .hard) "hf" else ""; + return print(&result, "/lib/ld-musl-{}{}.so.1", .{ arch_part, arch_suffix }); } - switch (self.getOs()) { - .freebsd => return mem.dupeZ(a, u8, "/libexec/ld-elf.so.1"), - .netbsd => return mem.dupeZ(a, u8, "/libexec/ld.elf_so"), - .dragonfly => return mem.dupeZ(a, u8, "/libexec/ld-elf.so.2"), - .linux => switch (self.getArch()) { + switch (self.os.tag) { + .freebsd => return copy(&result, "/libexec/ld-elf.so.1"), + .netbsd => return copy(&result, "/libexec/ld.elf_so"), + .dragonfly => return copy(&result, "/libexec/ld-elf.so.2"), + .linux => switch (self.cpu.arch) { .i386, .sparc, .sparcel, - => return mem.dupeZ(a, u8, "/lib/ld-linux.so.2"), + => return copy(&result, "/lib/ld-linux.so.2"), - .aarch64 => return mem.dupeZ(a, u8, "/lib/ld-linux-aarch64.so.1"), - .aarch64_be => return mem.dupeZ(a, u8, "/lib/ld-linux-aarch64_be.so.1"), - .aarch64_32 => return mem.dupeZ(a, u8, "/lib/ld-linux-aarch64_32.so.1"), + .aarch64 => return copy(&result, "/lib/ld-linux-aarch64.so.1"), + .aarch64_be => return copy(&result, "/lib/ld-linux-aarch64_be.so.1"), + .aarch64_32 => return copy(&result, "/lib/ld-linux-aarch64_32.so.1"), .arm, .armeb, .thumb, .thumbeb, - => return mem.dupeZ(a, u8, switch (self.getFloatAbi()) { + => return copy(&result, switch (self.getFloatAbi()) { .hard => "/lib/ld-linux-armhf.so.3", else => "/lib/ld-linux.so.3", }), @@ -1227,28 +1199,43 @@ pub const Target = union(enum) { .mipsel, .mips64, .mips64el, - => return error.UnknownDynamicLinkerPath, + => { + const lib_suffix = switch (self.abi) { + .gnuabin32, .gnux32 => "32", + .gnuabi64 => "64", + else => "", + }; + const is_nan_2008 = mips.featureSetHas(self.cpu.features, .nan2008); + const loader = if (is_nan_2008) "ld-linux-mipsn8.so.1" else "ld.so.1"; + return print(&result, "/lib{}/{}", .{ lib_suffix, loader }); + }, - .powerpc => return mem.dupeZ(a, u8, "/lib/ld.so.1"), - .powerpc64, .powerpc64le => return mem.dupeZ(a, u8, "/lib64/ld64.so.2"), - .s390x => return mem.dupeZ(a, u8, "/lib64/ld64.so.1"), - .sparcv9 => return mem.dupeZ(a, u8, "/lib64/ld-linux.so.2"), - .x86_64 => return mem.dupeZ(a, u8, switch (self.getAbi()) { + .powerpc => return copy(&result, "/lib/ld.so.1"), + .powerpc64, .powerpc64le => return copy(&result, "/lib64/ld64.so.2"), + .s390x => return copy(&result, "/lib64/ld64.so.1"), + .sparcv9 => return copy(&result, "/lib64/ld-linux.so.2"), + .x86_64 => return copy(&result, switch (self.abi) { .gnux32 => "/libx32/ld-linux-x32.so.2", else => "/lib64/ld-linux-x86-64.so.2", }), - .riscv32 => return mem.dupeZ(a, u8, "/lib/ld-linux-riscv32-ilp32.so.1"), - .riscv64 => return mem.dupeZ(a, u8, "/lib/ld-linux-riscv64-lp64.so.1"), + .riscv32 => return copy(&result, "/lib/ld-linux-riscv32-ilp32.so.1"), + .riscv64 => return copy(&result, "/lib/ld-linux-riscv64-lp64.so.1"), + // Architectures in this list have been verified as not having a standard + // dynamic linker path. .wasm32, .wasm64, - => return error.TargetHasNoDynamicLinker, - - .arc, - .avr, .bpfel, .bpfeb, + .nvptx, + .nvptx64, + => return result, + + // TODO go over each item in this list and either move it to the above list, or + // implement the standard dynamic linker path code for it. + .arc, + .avr, .hexagon, .msp430, .r600, @@ -1256,8 +1243,6 @@ pub const Target = union(enum) { .tce, .tcele, .xcore, - .nvptx, - .nvptx64, .le32, .le64, .amdil, @@ -1272,9 +1257,11 @@ pub const Target = union(enum) { .renderscript32, .renderscript64, .ve, - => return error.UnknownDynamicLinkerPath, + => return result, }, + // Operating systems in this list have been verified as not having a standard + // dynamic linker path. .freestanding, .ios, .tvos, @@ -1283,40 +1270,36 @@ pub const Target = union(enum) { .uefi, .windows, .emscripten, + .wasi, .other, - => return error.TargetHasNoDynamicLinker, + => return result, - else => return error.UnknownDynamicLinkerPath, + // TODO go over each item in this list and either move it to the above list, or + // implement the standard dynamic linker path code for it. + .ananas, + .cloudabi, + .fuchsia, + .kfreebsd, + .lv2, + .openbsd, + .solaris, + .haiku, + .minix, + .rtems, + .nacl, + .cnk, + .aix, + .cuda, + .nvcl, + .amdhsa, + .ps4, + .elfiamcu, + .mesa3d, + .contiki, + .amdpal, + .hermit, + .hurd, + => return result, } } }; - -test "Target.parse" { - { - const target = (try Target.parse(.{ - .arch_os_abi = "x86_64-linux-gnu", - .cpu_features = "x86_64-sse-sse2-avx-cx8", - })).Cross; - - std.testing.expect(target.os == .linux); - std.testing.expect(target.abi == .gnu); - std.testing.expect(target.cpu.arch == .x86_64); - std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .sse)); - std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .avx)); - std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .cx8)); - std.testing.expect(Target.x86.featureSetHas(target.cpu.features, .cmov)); - std.testing.expect(Target.x86.featureSetHas(target.cpu.features, .fxsr)); - } - { - const target = (try Target.parse(.{ - .arch_os_abi = "arm-linux-musleabihf", - .cpu_features = "generic+v8a", - })).Cross; - - std.testing.expect(target.os == .linux); - std.testing.expect(target.abi == .musleabihf); - std.testing.expect(target.cpu.arch == .arm); - std.testing.expect(target.cpu.model == &Target.arm.cpu.generic); - std.testing.expect(Target.arm.featureSetHas(target.cpu.features, .v8a)); - } -} diff --git a/lib/std/target/arm.zig b/lib/std/target/arm.zig index 6f0d7916c9..23a45851ec 100644 --- a/lib/std/target/arm.zig +++ b/lib/std/target/arm.zig @@ -1510,7 +1510,7 @@ pub const cpu = struct { .name = "baseline", .llvm_name = "generic", .features = featureSet(&[_]Feature{ - .v6m, + .v7a, }), }; pub const cortex_a12 = CpuModel{ diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 348f651a88..4f527ba700 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -1,5 +1,3 @@ -const builtin = @import("builtin"); -const TypeId = builtin.TypeId; const std = @import("std.zig"); pub const LeakCountAllocator = @import("testing/leak_count_allocator.zig").LeakCountAllocator; @@ -65,16 +63,12 @@ pub fn expectEqual(expected: var, actual: @TypeOf(expected)) void { .Pointer => |pointer| { switch (pointer.size) { - builtin.TypeInfo.Pointer.Size.One, - builtin.TypeInfo.Pointer.Size.Many, - builtin.TypeInfo.Pointer.Size.C, - => { + .One, .Many, .C => { if (actual != expected) { std.debug.panic("expected {*}, found {*}", .{ expected, actual }); } }, - - builtin.TypeInfo.Pointer.Size.Slice => { + .Slice => { if (actual.ptr != expected.ptr) { std.debug.panic("expected slice ptr {}, found {}", .{ expected.ptr, actual.ptr }); } diff --git a/lib/std/thread.zig b/lib/std/thread.zig index fcc71ae5a5..b2f8a44a47 100644 --- a/lib/std/thread.zig +++ b/lib/std/thread.zig @@ -1,5 +1,5 @@ -const builtin = @import("builtin"); const std = @import("std.zig"); +const builtin = std.builtin; const os = std.os; const mem = std.mem; const windows = std.os.windows; @@ -9,14 +9,14 @@ const assert = std.debug.assert; pub const Thread = struct { data: Data, - pub const use_pthreads = builtin.os != .windows and builtin.link_libc; + pub const use_pthreads = std.Target.current.os.tag != .windows and builtin.link_libc; /// Represents a kernel thread handle. /// May be an integer or a pointer depending on the platform. /// On Linux and POSIX, this is the same as Id. pub const Handle = if (use_pthreads) c.pthread_t - else switch (builtin.os) { + else switch (std.Target.current.os.tag) { .linux => i32, .windows => windows.HANDLE, else => void, @@ -25,7 +25,7 @@ pub const Thread = struct { /// Represents a unique ID per thread. /// May be an integer or pointer depending on the platform. /// On Linux and POSIX, this is the same as Handle. - pub const Id = switch (builtin.os) { + pub const Id = switch (std.Target.current.os.tag) { .windows => windows.DWORD, else => Handle, }; @@ -35,7 +35,7 @@ pub const Thread = struct { handle: Thread.Handle, memory: []align(mem.page_size) u8, } - else switch (builtin.os) { + else switch (std.Target.current.os.tag) { .linux => struct { handle: Thread.Handle, memory: []align(mem.page_size) u8, @@ -55,7 +55,7 @@ pub const Thread = struct { if (use_pthreads) { return c.pthread_self(); } else - return switch (builtin.os) { + return switch (std.Target.current.os.tag) { .linux => os.linux.gettid(), .windows => windows.kernel32.GetCurrentThreadId(), else => @compileError("Unsupported OS"), @@ -83,7 +83,7 @@ pub const Thread = struct { else => unreachable, } os.munmap(self.data.memory); - } else switch (builtin.os) { + } else switch (std.Target.current.os.tag) { .linux => { while (true) { const pid_value = @atomicLoad(i32, &self.data.handle, .SeqCst); @@ -150,7 +150,7 @@ pub const Thread = struct { const Context = @TypeOf(context); comptime assert(@typeInfo(@TypeOf(startFn)).Fn.args[0].arg_type.? == Context); - if (builtin.os == builtin.Os.windows) { + if (std.Target.current.os.tag == .windows) { const WinThread = struct { const OuterContext = struct { thread: Thread, @@ -309,16 +309,16 @@ pub const Thread = struct { os.EINVAL => unreachable, else => return os.unexpectedErrno(@intCast(usize, err)), } - } else if (builtin.os == .linux) { + } else if (std.Target.current.os.tag == .linux) { var flags: u32 = os.CLONE_VM | os.CLONE_FS | os.CLONE_FILES | os.CLONE_SIGHAND | os.CLONE_THREAD | os.CLONE_SYSVSEM | os.CLONE_PARENT_SETTID | os.CLONE_CHILD_CLEARTID | os.CLONE_DETACHED; var newtls: usize = undefined; // This structure is only needed when targeting i386 - var user_desc: if (builtin.arch == .i386) os.linux.user_desc else void = undefined; + var user_desc: if (std.Target.current.cpu.arch == .i386) os.linux.user_desc else void = undefined; if (os.linux.tls.tls_image) |tls_img| { - if (builtin.arch == .i386) { + if (std.Target.current.cpu.arch == .i386) { user_desc = os.linux.user_desc{ .entry_number = tls_img.gdt_entry_number, .base_addr = os.linux.tls.copyTLS(mmap_addr + tls_start_offset), @@ -362,27 +362,24 @@ pub const Thread = struct { } pub const CpuCountError = error{ - OutOfMemory, PermissionDenied, SystemResources, Unexpected, }; pub fn cpuCount() CpuCountError!usize { - if (builtin.os == .linux) { + if (std.Target.current.os.tag == .linux) { const cpu_set = try os.sched_getaffinity(0); return @as(usize, os.CPU_COUNT(cpu_set)); // TODO should not need this usize cast } - if (builtin.os == .windows) { - var system_info: windows.SYSTEM_INFO = undefined; - windows.kernel32.GetSystemInfo(&system_info); - return @intCast(usize, system_info.dwNumberOfProcessors); + if (std.Target.current.os.tag == .windows) { + return os.windows.peb().NumberOfProcessors; } var count: c_int = undefined; var count_len: usize = @sizeOf(c_int); const name = if (comptime std.Target.current.isDarwin()) "hw.logicalcpu" else "hw.ncpu"; os.sysctlbynameC(name, &count, &count_len, null, 0) catch |err| switch (err) { - error.NameTooLong => unreachable, + error.NameTooLong, error.UnknownName => unreachable, else => |e| return e, }; return @intCast(usize, count); diff --git a/lib/std/time.zig b/lib/std/time.zig index 63d3ecce18..4112fb7bda 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -1,5 +1,5 @@ -const builtin = @import("builtin"); const std = @import("std.zig"); +const builtin = std.builtin; const assert = std.debug.assert; const testing = std.testing; const os = std.os; @@ -7,10 +7,12 @@ const math = std.math; pub const epoch = @import("time/epoch.zig"); +const is_windows = std.Target.current.os.tag == .windows; + /// Spurious wakeups are possible and no precision of timing is guaranteed. /// TODO integrate with evented I/O pub fn sleep(nanoseconds: u64) void { - if (builtin.os == .windows) { + if (is_windows) { const ns_per_ms = ns_per_s / ms_per_s; const big_ms_from_ns = nanoseconds / ns_per_ms; const ms = math.cast(os.windows.DWORD, big_ms_from_ns) catch math.maxInt(os.windows.DWORD); @@ -31,7 +33,7 @@ pub fn timestamp() u64 { /// Get the posix timestamp, UTC, in milliseconds /// TODO audit this function. is it possible to return an error? pub fn milliTimestamp() u64 { - if (builtin.os == .windows) { + if (is_windows) { //FileTime has a granularity of 100 nanoseconds // and uses the NTFS/Windows epoch var ft: os.windows.FILETIME = undefined; @@ -42,7 +44,7 @@ pub fn milliTimestamp() u64 { const ft64 = (@as(u64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime; return @divFloor(ft64, hns_per_ms) - -epoch_adj; } - if (builtin.os == .wasi and !builtin.link_libc) { + if (builtin.os.tag == .wasi and !builtin.link_libc) { var ns: os.wasi.timestamp_t = undefined; // TODO: Verify that precision is ignored @@ -102,7 +104,7 @@ pub const Timer = struct { ///if we used resolution's value when performing the /// performance counter calc on windows/darwin, it would /// be less precise - frequency: switch (builtin.os) { + frequency: switch (builtin.os.tag) { .windows => u64, .macosx, .ios, .tvos, .watchos => os.darwin.mach_timebase_info_data, else => void, @@ -127,7 +129,7 @@ pub const Timer = struct { pub fn start() Error!Timer { var self: Timer = undefined; - if (builtin.os == .windows) { + if (is_windows) { self.frequency = os.windows.QueryPerformanceFrequency(); self.resolution = @divFloor(ns_per_s, self.frequency); self.start_time = os.windows.QueryPerformanceCounter(); @@ -172,7 +174,7 @@ pub const Timer = struct { } fn clockNative() u64 { - if (builtin.os == .windows) { + if (is_windows) { return os.windows.QueryPerformanceCounter(); } if (comptime std.Target.current.isDarwin()) { @@ -184,7 +186,7 @@ pub const Timer = struct { } fn nativeDurationToNanos(self: Timer, duration: u64) u64 { - if (builtin.os == .windows) { + if (is_windows) { return @divFloor(duration * ns_per_s, self.frequency); } if (comptime std.Target.current.isDarwin()) { diff --git a/lib/std/valgrind.zig b/lib/std/valgrind.zig index fe1eb65b7a..38c4a491e0 100644 --- a/lib/std/valgrind.zig +++ b/lib/std/valgrind.zig @@ -8,7 +8,7 @@ pub fn doClientRequest(default: usize, request: usize, a1: usize, a2: usize, a3: } switch (builtin.arch) { - builtin.Arch.i386 => { + .i386 => { return asm volatile ( \\ roll $3, %%edi ; roll $13, %%edi \\ roll $29, %%edi ; roll $19, %%edi @@ -19,7 +19,7 @@ pub fn doClientRequest(default: usize, request: usize, a1: usize, a2: usize, a3: : "cc", "memory" ); }, - builtin.Arch.x86_64 => { + .x86_64 => { return asm volatile ( \\ rolq $3, %%rdi ; rolq $13, %%rdi \\ rolq $61, %%rdi ; rolq $51, %%rdi diff --git a/lib/std/zig.zig b/lib/std/zig.zig index d76ed9dfd2..81f34b09c9 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -6,11 +6,8 @@ pub const parseStringLiteral = @import("zig/parse_string_literal.zig").parseStri pub const render = @import("zig/render.zig").render; pub const ast = @import("zig/ast.zig"); pub const system = @import("zig/system.zig"); +pub const CrossTarget = @import("zig/cross_target.zig").CrossTarget; -test "std.zig tests" { - _ = @import("zig/ast.zig"); - _ = @import("zig/parse.zig"); - _ = @import("zig/render.zig"); - _ = @import("zig/tokenizer.zig"); - _ = @import("zig/parse_string_literal.zig"); +test "" { + @import("std").meta.refAllDecls(@This()); } diff --git a/lib/std/zig/cross_target.zig b/lib/std/zig/cross_target.zig new file mode 100644 index 0000000000..38ce6549f3 --- /dev/null +++ b/lib/std/zig/cross_target.zig @@ -0,0 +1,839 @@ +const std = @import("../std.zig"); +const assert = std.debug.assert; +const Target = std.Target; +const mem = std.mem; + +/// Contains all the same data as `Target`, additionally introducing the concept of "the native target". +/// The purpose of this abstraction is to provide meaningful and unsurprising defaults. +/// This struct does reference any resources and it is copyable. +pub const CrossTarget = struct { + /// `null` means native. + cpu_arch: ?Target.Cpu.Arch = null, + + cpu_model: CpuModel = CpuModel.determined_by_cpu_arch, + + /// Sparse set of CPU features to add to the set from `cpu_model`. + cpu_features_add: Target.Cpu.Feature.Set = Target.Cpu.Feature.Set.empty, + + /// Sparse set of CPU features to remove from the set from `cpu_model`. + cpu_features_sub: Target.Cpu.Feature.Set = Target.Cpu.Feature.Set.empty, + + /// `null` means native. + os_tag: ?Target.Os.Tag = null, + + /// `null` means the default version range for `os_tag`. If `os_tag` is `null` (native) + /// then `null` for this field means native. + os_version_min: ?OsVersion = null, + + /// When cross compiling, `null` means default (latest known OS version). + /// When `os_tag` is native, `null` means equal to the native OS version. + os_version_max: ?OsVersion = null, + + /// `null` means default when cross compiling, or native when os_tag is native. + /// If `isGnuLibC()` is `false`, this must be `null` and is ignored. + glibc_version: ?SemVer = null, + + /// `null` means the native C ABI, if `os_tag` is native, otherwise it means the default C ABI. + abi: ?Target.Abi = null, + + /// When `os_tag` is `null`, then `null` means native. Otherwise it means the standard path + /// based on the `os_tag`. + dynamic_linker: DynamicLinker = DynamicLinker{}, + + pub const CpuModel = union(enum) { + /// Always native + native, + + /// Always baseline + baseline, + + /// If CPU Architecture is native, then the CPU model will be native. Otherwise, + /// it will be baseline. + determined_by_cpu_arch, + + explicit: *const Target.Cpu.Model, + }; + + pub const OsVersion = union(enum) { + none: void, + semver: SemVer, + windows: Target.Os.WindowsVersion, + }; + + pub const SemVer = std.builtin.Version; + + pub const DynamicLinker = Target.DynamicLinker; + + pub fn fromTarget(target: Target) CrossTarget { + var result: CrossTarget = .{ + .cpu_arch = target.cpu.arch, + .cpu_model = .{ .explicit = target.cpu.model }, + .os_tag = target.os.tag, + .os_version_min = undefined, + .os_version_max = undefined, + .abi = target.abi, + .glibc_version = if (target.isGnuLibC()) + target.os.version_range.linux.glibc + else + null, + }; + result.updateOsVersionRange(target.os); + + const all_features = target.cpu.arch.allFeaturesList(); + var cpu_model_set = target.cpu.model.features; + cpu_model_set.populateDependencies(all_features); + { + // The "add" set is the full set with the CPU Model set removed. + const add_set = &result.cpu_features_add; + add_set.* = target.cpu.features; + add_set.removeFeatureSet(cpu_model_set); + } + { + // The "sub" set is the features that are on in CPU Model set and off in the full set. + const sub_set = &result.cpu_features_sub; + sub_set.* = cpu_model_set; + sub_set.removeFeatureSet(target.cpu.features); + } + return result; + } + + fn updateOsVersionRange(self: *CrossTarget, os: Target.Os) void { + switch (os.tag) { + .freestanding, + .ananas, + .cloudabi, + .dragonfly, + .fuchsia, + .kfreebsd, + .lv2, + .solaris, + .haiku, + .minix, + .rtems, + .nacl, + .cnk, + .aix, + .cuda, + .nvcl, + .amdhsa, + .ps4, + .elfiamcu, + .mesa3d, + .contiki, + .amdpal, + .hermit, + .hurd, + .wasi, + .emscripten, + .uefi, + .other, + => { + self.os_version_min = .{ .none = {} }; + self.os_version_max = .{ .none = {} }; + }, + + .freebsd, + .macosx, + .ios, + .netbsd, + .openbsd, + .tvos, + .watchos, + => { + self.os_version_min = .{ .semver = os.version_range.semver.min }; + self.os_version_max = .{ .semver = os.version_range.semver.max }; + }, + + .linux => { + self.os_version_min = .{ .semver = os.version_range.linux.range.min }; + self.os_version_max = .{ .semver = os.version_range.linux.range.max }; + }, + + .windows => { + self.os_version_min = .{ .windows = os.version_range.windows.min }; + self.os_version_max = .{ .windows = os.version_range.windows.max }; + }, + } + } + + /// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. + pub fn toTarget(self: CrossTarget) Target { + return .{ + .cpu = self.getCpu(), + .os = self.getOs(), + .abi = self.getAbi(), + }; + } + + pub const ParseOptions = struct { + /// This is sometimes called a "triple". It looks roughly like this: + /// riscv64-linux-musl + /// The fields are, respectively: + /// * CPU Architecture + /// * Operating System (and optional version range) + /// * C ABI (optional, with optional glibc version) + /// The string "native" can be used for CPU architecture as well as Operating System. + /// If the CPU Architecture is specified as "native", then the Operating System and C ABI may be omitted. + arch_os_abi: []const u8 = "native", + + /// Looks like "name+a+b-c-d+e", where "name" is a CPU Model name, "a", "b", and "e" + /// are examples of CPU features to add to the set, and "c" and "d" are examples of CPU features + /// to remove from the set. + /// The following special strings are recognized for CPU Model name: + /// * "baseline" - The "default" set of CPU features for cross-compiling. A conservative set + /// of features that is expected to be supported on most available hardware. + /// * "native" - The native CPU model is to be detected when compiling. + /// If this field is not provided (`null`), then the value will depend on the + /// parsed CPU Architecture. If native, then this will be "native". Otherwise, it will be "baseline". + cpu_features: ?[]const u8 = null, + + /// Absolute path to dynamic linker, to override the default, which is either a natively + /// detected path, or a standard path. + dynamic_linker: ?[]const u8 = null, + + /// If this is provided, the function will populate some information about parsing failures, + /// so that user-friendly error messages can be delivered. + diagnostics: ?*Diagnostics = null, + + pub const Diagnostics = struct { + /// If the architecture was determined, this will be populated. + arch: ?Target.Cpu.Arch = null, + + /// If the OS name was determined, this will be populated. + os_name: ?[]const u8 = null, + + /// If the OS tag was determined, this will be populated. + os_tag: ?Target.Os.Tag = null, + + /// If the ABI was determined, this will be populated. + abi: ?Target.Abi = null, + + /// If the CPU name was determined, this will be populated. + cpu_name: ?[]const u8 = null, + + /// If error.UnknownCpuFeature is returned, this will be populated. + unknown_feature_name: ?[]const u8 = null, + }; + }; + + pub fn parse(args: ParseOptions) !CrossTarget { + var dummy_diags: ParseOptions.Diagnostics = undefined; + const diags = args.diagnostics orelse &dummy_diags; + + var result: CrossTarget = .{ + .dynamic_linker = DynamicLinker.init(args.dynamic_linker), + }; + + var it = mem.separate(args.arch_os_abi, "-"); + const arch_name = it.next().?; + const arch_is_native = mem.eql(u8, arch_name, "native"); + if (!arch_is_native) { + result.cpu_arch = std.meta.stringToEnum(Target.Cpu.Arch, arch_name) orelse + return error.UnknownArchitecture; + } + const arch = result.getCpuArch(); + diags.arch = arch; + + if (it.next()) |os_text| { + try parseOs(&result, diags, os_text); + } else if (!arch_is_native) { + return error.MissingOperatingSystem; + } + + const opt_abi_text = it.next(); + if (opt_abi_text) |abi_text| { + var abi_it = mem.separate(abi_text, "."); + const abi = std.meta.stringToEnum(Target.Abi, abi_it.next().?) orelse + return error.UnknownApplicationBinaryInterface; + result.abi = abi; + diags.abi = abi; + + const abi_ver_text = abi_it.rest(); + if (abi_it.next() != null) { + if (result.isGnuLibC()) { + result.glibc_version = SemVer.parse(abi_ver_text) catch |err| switch (err) { + error.Overflow => return error.InvalidAbiVersion, + error.InvalidCharacter => return error.InvalidAbiVersion, + error.InvalidVersion => return error.InvalidAbiVersion, + }; + } else { + return error.InvalidAbiVersion; + } + } + } + + if (it.next() != null) return error.UnexpectedExtraField; + + if (args.cpu_features) |cpu_features| { + const all_features = arch.allFeaturesList(); + var index: usize = 0; + while (index < cpu_features.len and + cpu_features[index] != '+' and + cpu_features[index] != '-') + { + index += 1; + } + const cpu_name = cpu_features[0..index]; + diags.cpu_name = cpu_name; + + const add_set = &result.cpu_features_add; + const sub_set = &result.cpu_features_sub; + if (mem.eql(u8, cpu_name, "native")) { + result.cpu_model = .native; + } else if (mem.eql(u8, cpu_name, "baseline")) { + result.cpu_model = .baseline; + } else { + result.cpu_model = .{ .explicit = try arch.parseCpuModel(cpu_name) }; + } + + while (index < cpu_features.len) { + const op = cpu_features[index]; + const set = switch (op) { + '+' => add_set, + '-' => sub_set, + else => unreachable, + }; + index += 1; + const start = index; + while (index < cpu_features.len and + cpu_features[index] != '+' and + cpu_features[index] != '-') + { + index += 1; + } + const feature_name = cpu_features[start..index]; + for (all_features) |feature, feat_index_usize| { + const feat_index = @intCast(Target.Cpu.Feature.Set.Index, feat_index_usize); + if (mem.eql(u8, feature_name, feature.name)) { + set.addFeature(feat_index); + break; + } + } else { + diags.unknown_feature_name = feature_name; + return error.UnknownCpuFeature; + } + } + } + + return result; + } + + /// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. + pub fn getCpu(self: CrossTarget) Target.Cpu { + switch (self.cpu_model) { + .native => { + // This works when doing `zig build` because Zig generates a build executable using + // native CPU model & features. However this will not be accurate otherwise, and + // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`. + return Target.current.cpu; + }, + .baseline => { + var adjusted_baseline = Target.Cpu.baseline(self.getCpuArch()); + self.updateCpuFeatures(&adjusted_baseline.features); + return adjusted_baseline; + }, + .determined_by_cpu_arch => if (self.cpu_arch == null) { + // This works when doing `zig build` because Zig generates a build executable using + // native CPU model & features. However this will not be accurate otherwise, and + // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`. + return Target.current.cpu; + } else { + var adjusted_baseline = Target.Cpu.baseline(self.getCpuArch()); + self.updateCpuFeatures(&adjusted_baseline.features); + return adjusted_baseline; + }, + .explicit => |model| { + var adjusted_model = model.toCpu(self.getCpuArch()); + self.updateCpuFeatures(&adjusted_model.features); + return adjusted_model; + }, + } + } + + pub fn getCpuArch(self: CrossTarget) Target.Cpu.Arch { + return self.cpu_arch orelse Target.current.cpu.arch; + } + + pub fn getCpuModel(self: CrossTarget) *const Target.Cpu.Model { + if (self.cpu_model) |cpu_model| return cpu_model; + return self.getCpu().model; + } + + pub fn getCpuFeatures(self: CrossTarget) Target.Cpu.Feature.Set { + return self.getCpu().features; + } + + /// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. + pub fn getOs(self: CrossTarget) Target.Os { + // `Target.current.os` works when doing `zig build` because Zig generates a build executable using + // native OS version range. However this will not be accurate otherwise, and + // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`. + var adjusted_os = if (self.os_tag) |os_tag| Target.Os.defaultVersionRange(os_tag) else Target.current.os; + + if (self.os_version_min) |min| switch (min) { + .none => {}, + .semver => |semver| switch (self.getOsTag()) { + .linux => adjusted_os.version_range.linux.range.min = semver, + else => adjusted_os.version_range.semver.min = semver, + }, + .windows => |win_ver| adjusted_os.version_range.windows.min = win_ver, + }; + + if (self.os_version_max) |max| switch (max) { + .none => {}, + .semver => |semver| switch (self.getOsTag()) { + .linux => adjusted_os.version_range.linux.range.max = semver, + else => adjusted_os.version_range.semver.max = semver, + }, + .windows => |win_ver| adjusted_os.version_range.windows.max = win_ver, + }; + + if (self.glibc_version) |glibc| { + assert(self.isGnuLibC()); + adjusted_os.version_range.linux.glibc = glibc; + } + + return adjusted_os; + } + + pub fn getOsTag(self: CrossTarget) Target.Os.Tag { + return self.os_tag orelse Target.current.os.tag; + } + + /// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. + pub fn getOsVersionMin(self: CrossTarget) OsVersion { + if (self.os_version_min) |version_min| return version_min; + var tmp: CrossTarget = undefined; + tmp.updateOsVersionRange(self.getOs()); + return tmp.os_version_min.?; + } + + /// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. + pub fn getOsVersionMax(self: CrossTarget) OsVersion { + if (self.os_version_max) |version_max| return version_max; + var tmp: CrossTarget = undefined; + tmp.updateOsVersionRange(self.getOs()); + return tmp.os_version_max.?; + } + + /// TODO deprecated, use `std.zig.system.NativeTargetInfo.detect`. + pub fn getAbi(self: CrossTarget) Target.Abi { + if (self.abi) |abi| return abi; + + if (self.os_tag == null) { + // This works when doing `zig build` because Zig generates a build executable using + // native CPU model & features. However this will not be accurate otherwise, and + // will need to be integrated with `std.zig.system.NativeTargetInfo.detect`. + return Target.current.abi; + } + + return Target.Abi.default(self.getCpuArch(), self.getOs()); + } + + pub fn isFreeBSD(self: CrossTarget) bool { + return self.getOsTag() == .freebsd; + } + + pub fn isDarwin(self: CrossTarget) bool { + return self.getOsTag().isDarwin(); + } + + pub fn isNetBSD(self: CrossTarget) bool { + return self.getOsTag() == .netbsd; + } + + pub fn isUefi(self: CrossTarget) bool { + return self.getOsTag() == .uefi; + } + + pub fn isDragonFlyBSD(self: CrossTarget) bool { + return self.getOsTag() == .dragonfly; + } + + pub fn isLinux(self: CrossTarget) bool { + return self.getOsTag() == .linux; + } + + pub fn isWindows(self: CrossTarget) bool { + return self.getOsTag() == .windows; + } + + pub fn oFileExt(self: CrossTarget) [:0]const u8 { + return self.getAbi().oFileExt(); + } + + pub fn exeFileExt(self: CrossTarget) [:0]const u8 { + return Target.exeFileExtSimple(self.getCpuArch(), self.getOsTag()); + } + + pub fn staticLibSuffix(self: CrossTarget) [:0]const u8 { + return Target.staticLibSuffix_cpu_arch_abi(self.getCpuArch(), self.getAbi()); + } + + pub fn dynamicLibSuffix(self: CrossTarget) [:0]const u8 { + return self.getOsTag().dynamicLibSuffix(); + } + + pub fn libPrefix(self: CrossTarget) [:0]const u8 { + return Target.libPrefix_cpu_arch_abi(self.getCpuArch(), self.getAbi()); + } + + pub fn isNative(self: CrossTarget) bool { + return self.cpu_arch == null and + (self.cpu_model == .native or self.cpu_model == .determined_by_cpu_arch) and + self.cpu_features_sub.isEmpty() and self.cpu_features_add.isEmpty() and + self.os_tag == null and self.os_version_min == null and self.os_version_max == null and + self.abi == null and self.dynamic_linker.get() == null and self.glibc_version == null; + } + + pub fn zigTriple(self: CrossTarget, allocator: *mem.Allocator) error{OutOfMemory}![:0]u8 { + if (self.isNative()) { + return mem.dupeZ(allocator, u8, "native"); + } + + const arch_name = if (self.cpu_arch) |arch| @tagName(arch) else "native"; + const os_name = if (self.os_tag) |os_tag| @tagName(os_tag) else "native"; + + var result = try std.Buffer.allocPrint(allocator, "{}-{}", .{ arch_name, os_name }); + defer result.deinit(); + + // The zig target syntax does not allow specifying a max os version with no min, so + // if either are present, we need the min. + if (self.os_version_min != null or self.os_version_max != null) { + switch (self.getOsVersionMin()) { + .none => {}, + .semver => |v| try result.print(".{}", .{v}), + .windows => |v| try result.print(".{}", .{@tagName(v)}), + } + } + if (self.os_version_max) |max| { + switch (max) { + .none => {}, + .semver => |v| try result.print("...{}", .{v}), + .windows => |v| try result.print("...{}", .{@tagName(v)}), + } + } + + if (self.glibc_version) |v| { + try result.print("-{}.{}", .{ @tagName(self.getAbi()), v }); + } else if (self.abi) |abi| { + try result.print("-{}", .{@tagName(abi)}); + } + + return result.toOwnedSlice(); + } + + pub fn allocDescription(self: CrossTarget, allocator: *mem.Allocator) ![:0]u8 { + // TODO is there anything else worthy of the description that is not + // already captured in the triple? + return self.zigTriple(allocator); + } + + pub fn linuxTriple(self: CrossTarget, allocator: *mem.Allocator) ![:0]u8 { + return Target.linuxTripleSimple(allocator, self.getCpuArch(), self.getOsTag(), self.getAbi()); + } + + pub fn wantSharedLibSymLinks(self: CrossTarget) bool { + return self.getOsTag() != .windows; + } + + pub const VcpkgLinkage = std.builtin.LinkMode; + + /// Returned slice must be freed by the caller. + pub fn vcpkgTriplet(self: CrossTarget, allocator: *mem.Allocator, linkage: VcpkgLinkage) ![:0]u8 { + const arch = switch (self.getCpuArch()) { + .i386 => "x86", + .x86_64 => "x64", + + .arm, + .armeb, + .thumb, + .thumbeb, + .aarch64_32, + => "arm", + + .aarch64, + .aarch64_be, + => "arm64", + + else => return error.UnsupportedVcpkgArchitecture, + }; + + const os = switch (self.getOsTag()) { + .windows => "windows", + .linux => "linux", + .macosx => "macos", + else => return error.UnsupportedVcpkgOperatingSystem, + }; + + const static_suffix = switch (linkage) { + .Static => "-static", + .Dynamic => "", + }; + + return std.fmt.allocPrint0(allocator, "{}-{}{}", .{ arch, os, static_suffix }); + } + + pub const Executor = union(enum) { + native, + qemu: []const u8, + wine: []const u8, + wasmtime: []const u8, + unavailable, + }; + + /// Note that even a `CrossTarget` which returns `false` for `isNative` could still be natively executed. + /// For example `-target arm-native` running on an aarch64 host. + pub fn getExternalExecutor(self: CrossTarget) Executor { + const cpu_arch = self.getCpuArch(); + const os_tag = self.getOsTag(); + const os_match = os_tag == Target.current.os.tag; + + // If the OS and CPU arch match, the binary can be considered native. + if (os_match and cpu_arch == Target.current.cpu.arch) { + // However, we also need to verify that the dynamic linker path is valid. + // TODO Until that is implemented, we prevent returning `.native` when the OS is non-native. + if (self.os_tag == null) { + return .native; + } + } + + // If the OS matches, we can use QEMU to emulate a foreign architecture. + if (os_match) { + return switch (cpu_arch) { + .aarch64 => Executor{ .qemu = "qemu-aarch64" }, + .aarch64_be => Executor{ .qemu = "qemu-aarch64_be" }, + .arm => Executor{ .qemu = "qemu-arm" }, + .armeb => Executor{ .qemu = "qemu-armeb" }, + .i386 => Executor{ .qemu = "qemu-i386" }, + .mips => Executor{ .qemu = "qemu-mips" }, + .mipsel => Executor{ .qemu = "qemu-mipsel" }, + .mips64 => Executor{ .qemu = "qemu-mips64" }, + .mips64el => Executor{ .qemu = "qemu-mips64el" }, + .powerpc => Executor{ .qemu = "qemu-ppc" }, + .powerpc64 => Executor{ .qemu = "qemu-ppc64" }, + .powerpc64le => Executor{ .qemu = "qemu-ppc64le" }, + .riscv32 => Executor{ .qemu = "qemu-riscv32" }, + .riscv64 => Executor{ .qemu = "qemu-riscv64" }, + .s390x => Executor{ .qemu = "qemu-s390x" }, + .sparc => Executor{ .qemu = "qemu-sparc" }, + .x86_64 => Executor{ .qemu = "qemu-x86_64" }, + else => return .unavailable, + }; + } + + switch (os_tag) { + .windows => switch (cpu_arch.ptrBitWidth()) { + 32 => return Executor{ .wine = "wine" }, + 64 => return Executor{ .wine = "wine64" }, + else => return .unavailable, + }, + .wasi => switch (cpu_arch.ptrBitWidth()) { + 32 => return Executor{ .wasmtime = "wasmtime" }, + else => return .unavailable, + }, + else => return .unavailable, + } + } + + pub fn isGnuLibC(self: CrossTarget) bool { + return Target.isGnuLibC_os_tag_abi(self.getOsTag(), self.getAbi()); + } + + pub fn setGnuLibCVersion(self: *CrossTarget, major: u32, minor: u32, patch: u32) void { + assert(self.isGnuLibC()); + self.glibc_version = SemVer{ .major = major, .minor = minor, .patch = patch }; + } + + pub fn getObjectFormat(self: CrossTarget) ObjectFormat { + return Target.getObjectFormatSimple(self.getOsTag(), self.getCpuArch()); + } + + fn updateCpuFeatures(self: CrossTarget, set: *Target.Cpu.Feature.Set) void { + set.removeFeatureSet(self.cpu_features_sub); + set.addFeatureSet(self.cpu_features_add); + set.populateDependencies(self.getCpuArch().allFeaturesList()); + set.removeFeatureSet(self.cpu_features_sub); + } + + fn parseOs(result: *CrossTarget, diags: *ParseOptions.Diagnostics, text: []const u8) !void { + var it = mem.separate(text, "."); + const os_name = it.next().?; + diags.os_name = os_name; + const os_is_native = mem.eql(u8, os_name, "native"); + if (!os_is_native) { + result.os_tag = std.meta.stringToEnum(Target.Os.Tag, os_name) orelse + return error.UnknownOperatingSystem; + } + const tag = result.getOsTag(); + diags.os_tag = tag; + + const version_text = it.rest(); + if (it.next() == null) return; + + switch (tag) { + .freestanding, + .ananas, + .cloudabi, + .dragonfly, + .fuchsia, + .ios, + .kfreebsd, + .lv2, + .solaris, + .haiku, + .minix, + .rtems, + .nacl, + .cnk, + .aix, + .cuda, + .nvcl, + .amdhsa, + .ps4, + .elfiamcu, + .tvos, + .watchos, + .mesa3d, + .contiki, + .amdpal, + .hermit, + .hurd, + .wasi, + .emscripten, + .uefi, + .other, + => return error.InvalidOperatingSystemVersion, + + .freebsd, + .macosx, + .netbsd, + .openbsd, + .linux, + => { + var range_it = mem.separate(version_text, "..."); + + const min_text = range_it.next().?; + const min_ver = SemVer.parse(min_text) catch |err| switch (err) { + error.Overflow => return error.InvalidOperatingSystemVersion, + error.InvalidCharacter => return error.InvalidOperatingSystemVersion, + error.InvalidVersion => return error.InvalidOperatingSystemVersion, + }; + result.os_version_min = .{ .semver = min_ver }; + + const max_text = range_it.next() orelse return; + const max_ver = SemVer.parse(max_text) catch |err| switch (err) { + error.Overflow => return error.InvalidOperatingSystemVersion, + error.InvalidCharacter => return error.InvalidOperatingSystemVersion, + error.InvalidVersion => return error.InvalidOperatingSystemVersion, + }; + result.os_version_max = .{ .semver = max_ver }; + }, + + .windows => { + var range_it = mem.separate(version_text, "..."); + + const min_text = range_it.next().?; + const min_ver = std.meta.stringToEnum(Target.Os.WindowsVersion, min_text) orelse + return error.InvalidOperatingSystemVersion; + result.os_version_min = .{ .windows = min_ver }; + + const max_text = range_it.next() orelse return; + const max_ver = std.meta.stringToEnum(Target.Os.WindowsVersion, max_text) orelse + return error.InvalidOperatingSystemVersion; + result.os_version_max = .{ .windows = max_ver }; + }, + } + } +}; + +test "CrossTarget.parse" { + if (Target.current.isGnuLibC()) { + var cross_target = try CrossTarget.parse(.{}); + cross_target.setGnuLibCVersion(2, 1, 1); + + const text = try cross_target.zigTriple(std.testing.allocator); + defer std.testing.allocator.free(text); + std.testing.expectEqualSlices(u8, "native-native-gnu.2.1.1", text); + } + { + const cross_target = try CrossTarget.parse(.{ + .arch_os_abi = "aarch64-linux", + .cpu_features = "native", + }); + + std.testing.expect(cross_target.cpu_arch.? == .aarch64); + std.testing.expect(cross_target.cpu_model == .native); + } + { + const cross_target = try CrossTarget.parse(.{ .arch_os_abi = "native" }); + + std.testing.expect(cross_target.cpu_arch == null); + std.testing.expect(cross_target.isNative()); + + const text = try cross_target.zigTriple(std.testing.allocator); + defer std.testing.allocator.free(text); + std.testing.expectEqualSlices(u8, "native", text); + } + { + const cross_target = try CrossTarget.parse(.{ + .arch_os_abi = "x86_64-linux-gnu", + .cpu_features = "x86_64-sse-sse2-avx-cx8", + }); + const target = cross_target.toTarget(); + + std.testing.expect(target.os.tag == .linux); + std.testing.expect(target.abi == .gnu); + std.testing.expect(target.cpu.arch == .x86_64); + std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .sse)); + std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .avx)); + std.testing.expect(!Target.x86.featureSetHas(target.cpu.features, .cx8)); + std.testing.expect(Target.x86.featureSetHas(target.cpu.features, .cmov)); + std.testing.expect(Target.x86.featureSetHas(target.cpu.features, .fxsr)); + + const text = try cross_target.zigTriple(std.testing.allocator); + defer std.testing.allocator.free(text); + std.testing.expectEqualSlices(u8, "x86_64-linux-gnu", text); + } + { + const cross_target = try CrossTarget.parse(.{ + .arch_os_abi = "arm-linux-musleabihf", + .cpu_features = "generic+v8a", + }); + const target = cross_target.toTarget(); + + std.testing.expect(target.os.tag == .linux); + std.testing.expect(target.abi == .musleabihf); + std.testing.expect(target.cpu.arch == .arm); + std.testing.expect(target.cpu.model == &Target.arm.cpu.generic); + std.testing.expect(Target.arm.featureSetHas(target.cpu.features, .v8a)); + + const text = try cross_target.zigTriple(std.testing.allocator); + defer std.testing.allocator.free(text); + std.testing.expectEqualSlices(u8, "arm-linux-musleabihf", text); + } + { + const cross_target = try CrossTarget.parse(.{ + .arch_os_abi = "aarch64-linux.3.10...4.4.1-gnu.2.27", + .cpu_features = "generic+v8a", + }); + const target = cross_target.toTarget(); + + std.testing.expect(target.cpu.arch == .aarch64); + std.testing.expect(target.os.tag == .linux); + std.testing.expect(target.os.version_range.linux.range.min.major == 3); + std.testing.expect(target.os.version_range.linux.range.min.minor == 10); + std.testing.expect(target.os.version_range.linux.range.min.patch == 0); + std.testing.expect(target.os.version_range.linux.range.max.major == 4); + std.testing.expect(target.os.version_range.linux.range.max.minor == 4); + std.testing.expect(target.os.version_range.linux.range.max.patch == 1); + std.testing.expect(target.os.version_range.linux.glibc.major == 2); + std.testing.expect(target.os.version_range.linux.glibc.minor == 27); + std.testing.expect(target.os.version_range.linux.glibc.patch == 0); + std.testing.expect(target.abi == .gnu); + + const text = try cross_target.zigTriple(std.testing.allocator); + defer std.testing.allocator.free(text); + std.testing.expectEqualSlices(u8, "aarch64-linux.3.10...4.4.1-gnu.2.27", text); + } +} diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 3931e362c6..38a90f4c60 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -1,11 +1,15 @@ const std = @import("../std.zig"); +const elf = std.elf; const mem = std.mem; +const fs = std.fs; const Allocator = std.mem.Allocator; const ArrayList = std.ArrayList; const assert = std.debug.assert; const process = std.process; +const Target = std.Target; +const CrossTarget = std.zig.CrossTarget; -const is_windows = std.Target.current.isWindows(); +const is_windows = Target.current.os.tag == .windows; pub const NativePaths = struct { include_dirs: ArrayList([:0]u8), @@ -77,7 +81,7 @@ pub const NativePaths = struct { } if (!is_windows) { - const triple = try std.Target.current.linuxTriple(allocator); + const triple = try Target.current.linuxTriple(allocator); // TODO: $ ld --verbose | grep SEARCH_DIR // the output contains some paths that end with lib64, maybe include them too? @@ -161,3 +165,691 @@ pub const NativePaths = struct { try array.append(item); } }; + +pub const NativeTargetInfo = struct { + target: Target, + + dynamic_linker: DynamicLinker = DynamicLinker{}, + + pub const DynamicLinker = Target.DynamicLinker; + + pub const DetectError = error{ + OutOfMemory, + FileSystem, + SystemResources, + SymLinkLoop, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + DeviceBusy, + }; + + /// Given a `CrossTarget`, which specifies in detail which parts of the target should be detected + /// natively, which should be standard or default, and which are provided explicitly, this function + /// resolves the native components by detecting the native system, and then resolves standard/default parts + /// relative to that. + /// Any resources this function allocates are released before returning, and so there is no + /// deinitialization method. + /// TODO Remove the Allocator requirement from this function. + pub fn detect(allocator: *Allocator, cross_target: CrossTarget) DetectError!NativeTargetInfo { + const cpu = switch (cross_target.cpu_model) { + .native => detectNativeCpuAndFeatures(cross_target), + .baseline => baselineCpuAndFeatures(cross_target), + .determined_by_cpu_arch => if (cross_target.cpu_arch == null) + detectNativeCpuAndFeatures(cross_target) + else + baselineCpuAndFeatures(cross_target), + .explicit => |model| blk: { + var adjusted_model = model.toCpu(cross_target.getCpuArch()); + cross_target.updateCpuFeatures(&adjusted_model.features); + break :blk adjusted_model; + }, + }; + + var os = Target.Os.defaultVersionRange(cross_target.getOsTag()); + if (cross_target.os_tag == null) { + switch (Target.current.os.tag) { + .linux => { + const uts = std.os.uname(); + const release = mem.toSliceConst(u8, @ptrCast([*:0]const u8, &uts.release)); + if (std.builtin.Version.parse(release)) |ver| { + os.version_range.linux.range.min = ver; + os.version_range.linux.range.max = ver; + } else |err| switch (err) { + error.Overflow => {}, + error.InvalidCharacter => {}, + error.InvalidVersion => {}, + } + }, + .windows => { + var version_info: std.os.windows.RTL_OSVERSIONINFOW = undefined; + version_info.dwOSVersionInfoSize = @sizeOf(@TypeOf(version_info)); + + switch (std.os.windows.ntdll.RtlGetVersion(&version_info)) { + .SUCCESS => {}, + else => unreachable, + } + + // Starting from the system infos build a NTDDI-like version + // constant whose format is: + // B0 B1 B2 B3 + // `---` `` ``--> Sub-version (Starting from Windows 10 onwards) + // \ `--> Service pack (Always zero in the constants defined) + // `--> OS version (Major & minor) + const os_ver: u16 = // + @intCast(u16, version_info.dwMajorVersion & 0xff) << 8 | + @intCast(u16, version_info.dwMinorVersion & 0xff); + const sp_ver: u8 = 0; + const sub_ver: u8 = if (os_ver >= 0x0A00) subver: { + // There's no other way to obtain this info beside + // checking the build number against a known set of + // values + const known_build_numbers = [_]u32{ + 10240, 10586, 14393, 15063, 16299, 17134, 17763, + 18362, 18363, + }; + var last_idx: usize = 0; + for (known_build_numbers) |build, i| { + if (version_info.dwBuildNumber >= build) + last_idx = i; + } + break :subver @truncate(u8, last_idx); + } else 0; + + const version: u32 = @as(u32, os_ver) << 16 | @as(u32, sp_ver) << 8 | sub_ver; + + os.version_range.windows.max = @intToEnum(Target.Os.WindowsVersion, version); + os.version_range.windows.min = @intToEnum(Target.Os.WindowsVersion, version); + }, + .macosx => { + var product_version: [32]u8 = undefined; + var size: usize = product_version.len; + + // The osproductversion sysctl was introduced first with + // High Sierra, thankfully that's also the baseline that Zig + // supports + std.os.sysctlbynameC( + "kern.osproductversion", + &product_version, + &size, + null, + 0, + ) catch |err| switch (err) { + error.UnknownName => unreachable, + else => unreachable, + }; + + const string_version = product_version[0 .. size - 1 :0]; + if (std.builtin.Version.parse(string_version)) |ver| { + os.version_range.semver.min = ver; + os.version_range.semver.max = ver; + } else |err| switch (err) { + error.Overflow => {}, + error.InvalidCharacter => {}, + error.InvalidVersion => {}, + } + }, + .freebsd => { + // TODO Detect native operating system version. + }, + else => {}, + } + } + + if (cross_target.os_version_min) |min| switch (min) { + .none => {}, + .semver => |semver| switch (cross_target.getOsTag()) { + .linux => os.version_range.linux.range.min = semver, + else => os.version_range.semver.min = semver, + }, + .windows => |win_ver| os.version_range.windows.min = win_ver, + }; + + if (cross_target.os_version_max) |max| switch (max) { + .none => {}, + .semver => |semver| switch (cross_target.getOsTag()) { + .linux => os.version_range.linux.range.max = semver, + else => os.version_range.semver.max = semver, + }, + .windows => |win_ver| os.version_range.windows.max = win_ver, + }; + + if (cross_target.glibc_version) |glibc| { + assert(cross_target.isGnuLibC()); + os.version_range.linux.glibc = glibc; + } + + return detectAbiAndDynamicLinker(allocator, cpu, os, cross_target); + } + + /// First we attempt to use the executable's own binary. If it is dynamically + /// linked, then it should answer both the C ABI question and the dynamic linker question. + /// If it is statically linked, then we try /usr/bin/env. If that does not provide the answer, then + /// we fall back to the defaults. + /// TODO Remove the Allocator requirement from this function. + fn detectAbiAndDynamicLinker( + allocator: *Allocator, + cpu: Target.Cpu, + os: Target.Os, + cross_target: CrossTarget, + ) DetectError!NativeTargetInfo { + const native_target_has_ld = comptime Target.current.hasDynamicLinker(); + const is_linux = Target.current.os.tag == .linux; + const have_all_info = cross_target.dynamic_linker.get() != null and + cross_target.abi != null and (!is_linux or cross_target.abi.?.isGnu()); + const os_is_non_native = cross_target.os_tag != null; + if (!native_target_has_ld or have_all_info or os_is_non_native) { + return defaultAbiAndDynamicLinker(cpu, os, cross_target); + } + // The current target's ABI cannot be relied on for this. For example, we may build the zig + // compiler for target riscv64-linux-musl and provide a tarball for users to download. + // A user could then run that zig compiler on riscv64-linux-gnu. This use case is well-defined + // and supported by Zig. But that means that we must detect the system ABI here rather than + // relying on `Target.current`. + const all_abis = comptime blk: { + assert(@enumToInt(Target.Abi.none) == 0); + const fields = std.meta.fields(Target.Abi)[1..]; + var array: [fields.len]Target.Abi = undefined; + inline for (fields) |field, i| { + array[i] = @field(Target.Abi, field.name); + } + break :blk array; + }; + var ld_info_list_buffer: [all_abis.len]LdInfo = undefined; + var ld_info_list_len: usize = 0; + + for (all_abis) |abi| { + // This may be a nonsensical parameter. We detect this with error.UnknownDynamicLinkerPath and + // skip adding it to `ld_info_list`. + const target: Target = .{ + .cpu = cpu, + .os = os, + .abi = abi, + }; + const ld = target.standardDynamicLinkerPath(); + if (ld.get() == null) continue; + + ld_info_list_buffer[ld_info_list_len] = .{ + .ld = ld, + .abi = abi, + }; + ld_info_list_len += 1; + } + const ld_info_list = ld_info_list_buffer[0..ld_info_list_len]; + + if (cross_target.dynamic_linker.get()) |explicit_ld| { + const explicit_ld_basename = fs.path.basename(explicit_ld); + for (ld_info_list) |ld_info| { + const standard_ld_basename = fs.path.basename(ld_info.ld.get().?); + } + } + + // Best case scenario: the executable is dynamically linked, and we can iterate + // over our own shared objects and find a dynamic linker. + self_exe: { + const lib_paths = try std.process.getSelfExeSharedLibPaths(allocator); + defer allocator.free(lib_paths); + + var found_ld_info: LdInfo = undefined; + var found_ld_path: [:0]const u8 = undefined; + + // Look for dynamic linker. + // This is O(N^M) but typical case here is N=2 and M=10. + find_ld: for (lib_paths) |lib_path| { + for (ld_info_list) |ld_info| { + const standard_ld_basename = fs.path.basename(ld_info.ld.get().?); + if (std.mem.endsWith(u8, lib_path, standard_ld_basename)) { + found_ld_info = ld_info; + found_ld_path = lib_path; + break :find_ld; + } + } + } else break :self_exe; + + // Look for glibc version. + var os_adjusted = os; + if (Target.current.os.tag == .linux and found_ld_info.abi.isGnu() and + cross_target.glibc_version == null) + { + for (lib_paths) |lib_path| { + if (std.mem.endsWith(u8, lib_path, glibc_so_basename)) { + os_adjusted.version_range.linux.glibc = glibcVerFromSO(lib_path) catch |err| switch (err) { + error.UnrecognizedGnuLibCFileName => continue, + error.InvalidGnuLibCVersion => continue, + error.GnuLibCVersionUnavailable => continue, + else => |e| return e, + }; + break; + } + } + } + + var result: NativeTargetInfo = .{ + .target = .{ + .cpu = cpu, + .os = os_adjusted, + .abi = cross_target.abi orelse found_ld_info.abi, + }, + .dynamic_linker = if (cross_target.dynamic_linker.get() == null) + DynamicLinker.init(found_ld_path) + else + cross_target.dynamic_linker, + }; + return result; + } + + const env_file = std.fs.openFileAbsoluteC("/usr/bin/env", .{}) catch |err| switch (err) { + error.NoSpaceLeft => unreachable, + error.NameTooLong => unreachable, + error.PathAlreadyExists => unreachable, + error.SharingViolation => unreachable, + error.InvalidUtf8 => unreachable, + error.BadPathName => unreachable, + error.PipeBusy => unreachable, + + error.IsDir, + error.NotDir, + error.AccessDenied, + error.NoDevice, + error.FileNotFound, + error.FileTooBig, + error.Unexpected, + => return defaultAbiAndDynamicLinker(cpu, os, cross_target), + + else => |e| return e, + }; + defer env_file.close(); + + // If Zig is statically linked, such as via distributed binary static builds, the above + // trick won't work. The next thing we fall back to is the same thing, but for /usr/bin/env. + // Since that path is hard-coded into the shebang line of many portable scripts, it's a + // reasonably reliable path to check for. + return abiAndDynamicLinkerFromFile(env_file, cpu, os, ld_info_list, cross_target) catch |err| switch (err) { + error.FileSystem, + error.SystemResources, + error.SymLinkLoop, + error.ProcessFdQuotaExceeded, + error.SystemFdQuotaExceeded, + => |e| return e, + + error.UnableToReadElfFile, + error.InvalidElfClass, + error.InvalidElfVersion, + error.InvalidElfEndian, + error.InvalidElfFile, + error.InvalidElfMagic, + error.Unexpected, + error.UnexpectedEndOfFile, + error.NameTooLong, + // Finally, we fall back on the standard path. + => defaultAbiAndDynamicLinker(cpu, os, cross_target), + }; + } + + const glibc_so_basename = "libc.so.6"; + + fn glibcVerFromSO(so_path: [:0]const u8) !std.builtin.Version { + var link_buf: [std.os.PATH_MAX]u8 = undefined; + const link_name = std.os.readlinkC(so_path.ptr, &link_buf) catch |err| switch (err) { + error.AccessDenied => return error.GnuLibCVersionUnavailable, + error.FileSystem => return error.FileSystem, + error.SymLinkLoop => return error.SymLinkLoop, + error.NameTooLong => unreachable, + error.FileNotFound => return error.GnuLibCVersionUnavailable, + error.SystemResources => return error.SystemResources, + error.NotDir => return error.GnuLibCVersionUnavailable, + error.Unexpected => return error.GnuLibCVersionUnavailable, + }; + return glibcVerFromLinkName(link_name); + } + + fn glibcVerFromLinkName(link_name: []const u8) !std.builtin.Version { + // example: "libc-2.3.4.so" + // example: "libc-2.27.so" + const prefix = "libc-"; + const suffix = ".so"; + if (!mem.startsWith(u8, link_name, prefix) or !mem.endsWith(u8, link_name, suffix)) { + return error.UnrecognizedGnuLibCFileName; + } + // chop off "libc-" and ".so" + const link_name_chopped = link_name[prefix.len .. link_name.len - suffix.len]; + return std.builtin.Version.parse(link_name_chopped) catch |err| switch (err) { + error.Overflow => return error.InvalidGnuLibCVersion, + error.InvalidCharacter => return error.InvalidGnuLibCVersion, + error.InvalidVersion => return error.InvalidGnuLibCVersion, + }; + } + + pub const AbiAndDynamicLinkerFromFileError = error{ + FileSystem, + SystemResources, + SymLinkLoop, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + UnableToReadElfFile, + InvalidElfClass, + InvalidElfVersion, + InvalidElfEndian, + InvalidElfFile, + InvalidElfMagic, + Unexpected, + UnexpectedEndOfFile, + NameTooLong, + }; + + pub fn abiAndDynamicLinkerFromFile( + file: fs.File, + cpu: Target.Cpu, + os: Target.Os, + ld_info_list: []const LdInfo, + cross_target: CrossTarget, + ) AbiAndDynamicLinkerFromFileError!NativeTargetInfo { + var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined; + _ = try preadFull(file, &hdr_buf, 0, hdr_buf.len); + const hdr32 = @ptrCast(*elf.Elf32_Ehdr, &hdr_buf); + const hdr64 = @ptrCast(*elf.Elf64_Ehdr, &hdr_buf); + if (!mem.eql(u8, hdr32.e_ident[0..4], "\x7fELF")) return error.InvalidElfMagic; + const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI_DATA]) { + elf.ELFDATA2LSB => .Little, + elf.ELFDATA2MSB => .Big, + else => return error.InvalidElfEndian, + }; + const need_bswap = elf_endian != std.builtin.endian; + if (hdr32.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; + + const is_64 = switch (hdr32.e_ident[elf.EI_CLASS]) { + elf.ELFCLASS32 => false, + elf.ELFCLASS64 => true, + else => return error.InvalidElfClass, + }; + var phoff = elfInt(is_64, need_bswap, hdr32.e_phoff, hdr64.e_phoff); + const phentsize = elfInt(is_64, need_bswap, hdr32.e_phentsize, hdr64.e_phentsize); + const phnum = elfInt(is_64, need_bswap, hdr32.e_phnum, hdr64.e_phnum); + + var result: NativeTargetInfo = .{ + .target = .{ + .cpu = cpu, + .os = os, + .abi = cross_target.abi orelse Target.Abi.default(cpu.arch, os), + }, + .dynamic_linker = cross_target.dynamic_linker, + }; + var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC + const look_for_ld = cross_target.dynamic_linker.get() == null; + + var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined; + if (phentsize > @sizeOf(elf.Elf64_Phdr)) return error.InvalidElfFile; + + var ph_i: u16 = 0; + while (ph_i < phnum) { + // Reserve some bytes so that we can deref the 64-bit struct fields + // even when the ELF file is 32-bits. + const ph_reserve: usize = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr); + const ph_read_byte_len = try preadFull(file, ph_buf[0 .. ph_buf.len - ph_reserve], phoff, phentsize); + var ph_buf_i: usize = 0; + while (ph_buf_i < ph_read_byte_len and ph_i < phnum) : ({ + ph_i += 1; + phoff += phentsize; + ph_buf_i += phentsize; + }) { + const ph32 = @ptrCast(*elf.Elf32_Phdr, @alignCast(@alignOf(elf.Elf32_Phdr), &ph_buf[ph_buf_i])); + const ph64 = @ptrCast(*elf.Elf64_Phdr, @alignCast(@alignOf(elf.Elf64_Phdr), &ph_buf[ph_buf_i])); + const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type); + switch (p_type) { + elf.PT_INTERP => if (look_for_ld) { + const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); + const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); + if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong; + _ = try preadFull(file, result.dynamic_linker.buffer[0..p_filesz], p_offset, p_filesz); + // PT_INTERP includes a null byte in p_filesz. + const len = p_filesz - 1; + // dynamic_linker.max_byte is "max", not "len". + // We know it will fit in u8 because we check against dynamic_linker.buffer.len above. + result.dynamic_linker.max_byte = @intCast(u8, len - 1); + + // Use it to determine ABI. + const full_ld_path = result.dynamic_linker.buffer[0..len]; + for (ld_info_list) |ld_info| { + const standard_ld_basename = fs.path.basename(ld_info.ld.get().?); + if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) { + result.target.abi = ld_info.abi; + break; + } + } + }, + // We only need this for detecting glibc version. + elf.PT_DYNAMIC => if (Target.current.os.tag == .linux and result.target.isGnuLibC() and + cross_target.glibc_version == null) + { + var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); + const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); + const dyn_size: u64 = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn); + const dyn_num = p_filesz / dyn_size; + var dyn_buf: [16 * @sizeOf(elf.Elf64_Dyn)]u8 align(@alignOf(elf.Elf64_Dyn)) = undefined; + var dyn_i: usize = 0; + dyn: while (dyn_i < dyn_num) { + // Reserve some bytes so that we can deref the 64-bit struct fields + // even when the ELF file is 32-bits. + const dyn_reserve: usize = @sizeOf(elf.Elf64_Dyn) - @sizeOf(elf.Elf32_Dyn); + const dyn_read_byte_len = try preadFull( + file, + dyn_buf[0 .. dyn_buf.len - dyn_reserve], + dyn_off, + dyn_size, + ); + var dyn_buf_i: usize = 0; + while (dyn_buf_i < dyn_read_byte_len and dyn_i < dyn_num) : ({ + dyn_i += 1; + dyn_off += dyn_size; + dyn_buf_i += dyn_size; + }) { + const dyn32 = @ptrCast( + *elf.Elf32_Dyn, + @alignCast(@alignOf(elf.Elf32_Dyn), &dyn_buf[dyn_buf_i]), + ); + const dyn64 = @ptrCast( + *elf.Elf64_Dyn, + @alignCast(@alignOf(elf.Elf64_Dyn), &dyn_buf[dyn_buf_i]), + ); + const tag = elfInt(is_64, need_bswap, dyn32.d_tag, dyn64.d_tag); + const val = elfInt(is_64, need_bswap, dyn32.d_val, dyn64.d_val); + if (tag == elf.DT_RUNPATH) { + rpath_offset = val; + break :dyn; + } + } + } + }, + else => continue, + } + } + } + + if (Target.current.os.tag == .linux and result.target.isGnuLibC() and cross_target.glibc_version == null) { + if (rpath_offset) |rpoff| { + const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); + + var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff); + const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize); + const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx); + + var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined; + if (sh_buf.len < shentsize) return error.InvalidElfFile; + + _ = try preadFull(file, &sh_buf, str_section_off, shentsize); + const shstr32 = @ptrCast(*elf.Elf32_Shdr, @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf)); + const shstr64 = @ptrCast(*elf.Elf64_Shdr, @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf)); + const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset); + const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size); + var strtab_buf: [4096:0]u8 = undefined; + const shstrtab_len = std.math.min(shstrtab_size, strtab_buf.len); + const shstrtab_read_len = try preadFull(file, &strtab_buf, shstrtab_off, shstrtab_len); + const shstrtab = strtab_buf[0..shstrtab_read_len]; + + const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum); + var sh_i: u16 = 0; + const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) { + // Reserve some bytes so that we can deref the 64-bit struct fields + // even when the ELF file is 32-bits. + const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr); + const sh_read_byte_len = try preadFull( + file, + sh_buf[0 .. sh_buf.len - sh_reserve], + shoff, + shentsize, + ); + var sh_buf_i: usize = 0; + while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({ + sh_i += 1; + shoff += shentsize; + sh_buf_i += shentsize; + }) { + const sh32 = @ptrCast( + *elf.Elf32_Shdr, + @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf[sh_buf_i]), + ); + const sh64 = @ptrCast( + *elf.Elf64_Shdr, + @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf[sh_buf_i]), + ); + const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name); + // TODO this pointer cast should not be necessary + const sh_name = mem.toSliceConst(u8, @ptrCast([*:0]u8, shstrtab[sh_name_off..].ptr)); + if (mem.eql(u8, sh_name, ".dynstr")) { + break :find_dyn_str .{ + .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset), + .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size), + }; + } + } + } else null; + + if (dynstr) |ds| { + const strtab_len = std.math.min(ds.size, strtab_buf.len); + const strtab_read_len = try preadFull(file, &strtab_buf, ds.offset, shstrtab_len); + const strtab = strtab_buf[0..strtab_read_len]; + // TODO this pointer cast should not be necessary + const rpath_list = mem.toSliceConst(u8, @ptrCast([*:0]u8, strtab[rpoff..].ptr)); + var it = mem.tokenize(rpath_list, ":"); + while (it.next()) |rpath| { + var dir = fs.cwd().openDirList(rpath) catch |err| switch (err) { + error.NameTooLong => unreachable, + error.InvalidUtf8 => unreachable, + error.BadPathName => unreachable, + error.DeviceBusy => unreachable, + + error.FileNotFound, + error.NotDir, + error.AccessDenied, + error.NoDevice, + => continue, + + error.ProcessFdQuotaExceeded, + error.SystemFdQuotaExceeded, + error.SystemResources, + error.SymLinkLoop, + error.Unexpected, + => |e| return e, + }; + defer dir.close(); + + var link_buf: [std.os.PATH_MAX]u8 = undefined; + const link_name = std.os.readlinkatC( + dir.fd, + glibc_so_basename, + &link_buf, + ) catch |err| switch (err) { + error.NameTooLong => unreachable, + + error.AccessDenied, + error.FileNotFound, + error.NotDir, + => continue, + + error.SystemResources, + error.FileSystem, + error.SymLinkLoop, + error.Unexpected, + => |e| return e, + }; + result.target.os.version_range.linux.glibc = glibcVerFromLinkName( + link_name, + ) catch |err| switch (err) { + error.UnrecognizedGnuLibCFileName, + error.InvalidGnuLibCVersion, + => continue, + }; + break; + } + } + } + } + + return result; + } + + fn preadFull(file: fs.File, buf: []u8, offset: u64, min_read_len: usize) !usize { + var i: u64 = 0; + while (i < min_read_len) { + const len = file.pread(buf[i .. buf.len - i], offset + i) catch |err| switch (err) { + error.OperationAborted => unreachable, // Windows-only + error.WouldBlock => unreachable, // Did not request blocking mode + error.SystemResources => return error.SystemResources, + error.IsDir => return error.UnableToReadElfFile, + error.BrokenPipe => return error.UnableToReadElfFile, + error.ConnectionResetByPeer => return error.UnableToReadElfFile, + error.Unexpected => return error.Unexpected, + error.InputOutput => return error.FileSystem, + }; + if (len == 0) return error.UnexpectedEndOfFile; + i += len; + } + return i; + } + + fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, cross_target: CrossTarget) !NativeTargetInfo { + const target: Target = .{ + .cpu = cpu, + .os = os, + .abi = cross_target.abi orelse Target.Abi.default(cpu.arch, os), + }; + return NativeTargetInfo{ + .target = target, + .dynamic_linker = if (cross_target.dynamic_linker.get() == null) + target.standardDynamicLinkerPath() + else + cross_target.dynamic_linker, + }; + } + + pub const LdInfo = struct { + ld: DynamicLinker, + abi: Target.Abi, + }; + + fn elfInt(is_64: bool, need_bswap: bool, int_32: var, int_64: var) @TypeOf(int_64) { + if (is_64) { + if (need_bswap) { + return @byteSwap(@TypeOf(int_64), int_64); + } else { + return int_64; + } + } else { + if (need_bswap) { + return @byteSwap(@TypeOf(int_32), int_32); + } else { + return int_32; + } + } + } + + fn detectNativeCpuAndFeatures(cross_target: CrossTarget) Target.Cpu { + // TODO Detect native CPU model & features. Until that is implemented we use baseline. + return baselineCpuAndFeatures(cross_target); + } + + fn baselineCpuAndFeatures(cross_target: CrossTarget) Target.Cpu { + var adjusted_baseline = Target.Cpu.baseline(cross_target.getCpuArch()); + cross_target.updateCpuFeatures(&adjusted_baseline.features); + return adjusted_baseline; + } +}; diff --git a/src-self-hosted/c_int.zig b/src-self-hosted/c_int.zig index 2a840372b9..1ee27c7596 100644 --- a/src-self-hosted/c_int.zig +++ b/src-self-hosted/c_int.zig @@ -70,7 +70,7 @@ pub const CInt = struct { pub fn sizeInBits(cint: CInt, self: Target) u32 { const arch = self.getArch(); - switch (self.getOs()) { + switch (self.os.tag) { .freestanding, .other => switch (self.getArch()) { .msp430 => switch (cint.id) { .Short, diff --git a/src-self-hosted/clang.zig b/src-self-hosted/clang.zig index 08a10f8378..8a30fb99f3 100644 --- a/src-self-hosted/clang.zig +++ b/src-self-hosted/clang.zig @@ -1072,7 +1072,7 @@ pub const struct_ZigClangExprEvalResult = extern struct { pub const struct_ZigClangAPValue = extern struct { Kind: ZigClangAPValueKind, - Data: if (builtin.os == .windows and builtin.abi == .msvc) [52]u8 else [68]u8, + Data: if (builtin.os.tag == .windows and builtin.abi == .msvc) [52]u8 else [68]u8, }; pub extern fn ZigClangVarDecl_getTypeSourceInfo_getType(self: *const struct_ZigClangVarDecl) struct_ZigClangQualType; diff --git a/src-self-hosted/introspect.zig b/src-self-hosted/introspect.zig index 11838e7e63..c7f5690cc3 100644 --- a/src-self-hosted/introspect.zig +++ b/src-self-hosted/introspect.zig @@ -1,4 +1,4 @@ -// Introspection and determination of system libraries needed by zig. +//! Introspection and determination of system libraries needed by zig. const std = @import("std"); const mem = std.mem; @@ -6,14 +6,6 @@ const fs = std.fs; const warn = std.debug.warn; -pub fn detectDynamicLinker(allocator: *mem.Allocator, target: std.Target) ![:0]u8 { - if (target == .Native) { - return @import("libc_installation.zig").detectNativeDynamicLinker(allocator); - } else { - return target.getStandardDynamicLinkerPath(allocator); - } -} - /// Caller must free result pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![]u8 { const test_zig_dir = try fs.path.join(allocator, &[_][]const u8{ test_path, "lib", "zig" }); diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 576294a7d7..2e65962d41 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -1803,7 +1803,7 @@ pub const Builder = struct { // Look at the params and ref() other instructions inline for (@typeInfo(I.Params).Struct.fields) |f| { - switch (f.fiedl_type) { + switch (f.field_type) { *Inst => @field(inst.params, f.name).ref(self), *BasicBlock => @field(inst.params, f.name).ref(self), ?*Inst => if (@field(inst.params, f.name)) |other| other.ref(self), diff --git a/src-self-hosted/libc_installation.zig b/src-self-hosted/libc_installation.zig index 85740240ab..41def38126 100644 --- a/src-self-hosted/libc_installation.zig +++ b/src-self-hosted/libc_installation.zig @@ -7,11 +7,7 @@ const Allocator = std.mem.Allocator; const Batch = std.event.Batch; const is_darwin = Target.current.isDarwin(); -const is_windows = Target.current.isWindows(); -const is_freebsd = Target.current.isFreeBSD(); -const is_netbsd = Target.current.isNetBSD(); -const is_linux = Target.current.isLinux(); -const is_dragonfly = Target.current.isDragonFlyBSD(); +const is_windows = Target.current.os.tag == .windows; const is_gnu = Target.current.isGnu(); usingnamespace @import("windows_sdk.zig"); @@ -99,27 +95,27 @@ pub const LibCInstallation = struct { return error.ParseError; } if (self.crt_dir == null and !is_darwin) { - try stderr.print("crt_dir may not be empty for {}\n", .{@tagName(Target.current.getOs())}); + try stderr.print("crt_dir may not be empty for {}\n", .{@tagName(Target.current.os.tag)}); return error.ParseError; } if (self.static_crt_dir == null and is_windows and is_gnu) { try stderr.print("static_crt_dir may not be empty for {}-{}\n", .{ - @tagName(Target.current.getOs()), - @tagName(Target.current.getAbi()), + @tagName(Target.current.os.tag), + @tagName(Target.current.abi), }); return error.ParseError; } if (self.msvc_lib_dir == null and is_windows and !is_gnu) { try stderr.print("msvc_lib_dir may not be empty for {}-{}\n", .{ - @tagName(Target.current.getOs()), - @tagName(Target.current.getAbi()), + @tagName(Target.current.os.tag), + @tagName(Target.current.abi), }); return error.ParseError; } if (self.kernel32_lib_dir == null and is_windows and !is_gnu) { try stderr.print("kernel32_lib_dir may not be empty for {}-{}\n", .{ - @tagName(Target.current.getOs()), - @tagName(Target.current.getAbi()), + @tagName(Target.current.os.tag), + @tagName(Target.current.abi), }); return error.ParseError; } @@ -216,10 +212,10 @@ pub const LibCInstallation = struct { var batch = Batch(FindError!void, 2, .auto_async).init(); errdefer batch.wait() catch {}; batch.add(&async self.findNativeIncludeDirPosix(args)); - if (is_freebsd or is_netbsd) { - self.crt_dir = try std.mem.dupeZ(args.allocator, u8, "/usr/lib"); - } else if (is_linux or is_dragonfly) { - batch.add(&async self.findNativeCrtDirPosix(args)); + switch (Target.current.os.tag) { + .freebsd, .netbsd => self.crt_dir = try std.mem.dupeZ(args.allocator, u8, "/usr/lib"), + .linux, .dragonfly => batch.add(&async self.findNativeCrtDirPosix(args)), + else => {}, } break :blk batch.wait(); }; @@ -616,104 +612,6 @@ fn printVerboseInvocation( } } -/// Caller owns returned memory. -pub fn detectNativeDynamicLinker(allocator: *Allocator) error{ - OutOfMemory, - TargetHasNoDynamicLinker, - UnknownDynamicLinkerPath, -}![:0]u8 { - if (!comptime Target.current.hasDynamicLinker()) { - return error.TargetHasNoDynamicLinker; - } - - // The current target's ABI cannot be relied on for this. For example, we may build the zig - // compiler for target riscv64-linux-musl and provide a tarball for users to download. - // A user could then run that zig compiler on riscv64-linux-gnu. This use case is well-defined - // and supported by Zig. But that means that we must detect the system ABI here rather than - // relying on `std.Target.current`. - - const LdInfo = struct { - ld_path: []u8, - abi: Target.Abi, - }; - var ld_info_list = std.ArrayList(LdInfo).init(allocator); - defer { - for (ld_info_list.toSlice()) |ld_info| allocator.free(ld_info.ld_path); - ld_info_list.deinit(); - } - - const all_abis = comptime blk: { - const fields = std.meta.fields(Target.Abi); - var array: [fields.len]Target.Abi = undefined; - inline for (fields) |field, i| { - array[i] = @field(Target.Abi, field.name); - } - break :blk array; - }; - for (all_abis) |abi| { - // This may be a nonsensical parameter. We detect this with error.UnknownDynamicLinkerPath and - // skip adding it to `ld_info_list`. - const target: Target = .{ - .Cross = .{ - .cpu = Target.Cpu.baseline(Target.current.getArch()), - .os = Target.current.getOs(), - .abi = abi, - }, - }; - const standard_ld_path = target.getStandardDynamicLinkerPath(allocator) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.UnknownDynamicLinkerPath, error.TargetHasNoDynamicLinker => continue, - }; - errdefer allocator.free(standard_ld_path); - try ld_info_list.append(.{ - .ld_path = standard_ld_path, - .abi = abi, - }); - } - - // Best case scenario: the zig compiler is dynamically linked, and we can iterate - // over our own shared objects and find a dynamic linker. - { - const lib_paths = try std.process.getSelfExeSharedLibPaths(allocator); - defer allocator.free(lib_paths); - - // This is O(N^M) but typical case here is N=2 and M=10. - for (lib_paths) |lib_path| { - for (ld_info_list.toSlice()) |ld_info| { - const standard_ld_basename = fs.path.basename(ld_info.ld_path); - if (std.mem.endsWith(u8, lib_path, standard_ld_basename)) { - return std.mem.dupeZ(allocator, u8, lib_path); - } - } - } - } - - // If Zig is statically linked, such as via distributed binary static builds, the above - // trick won't work. What are we left with? Try to run the system C compiler and get - // it to tell us the dynamic linker path. - // TODO: instead of this, look at the shared libs of /usr/bin/env. - for (ld_info_list.toSlice()) |ld_info| { - const standard_ld_basename = fs.path.basename(ld_info.ld_path); - - const full_ld_path = ccPrintFileName(.{ - .allocator = allocator, - .search_basename = standard_ld_basename, - .want_dirname = .full_path, - }) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.LibCRuntimeNotFound, - error.CCompilerExitCode, - error.CCompilerCrashed, - error.UnableToSpawnCCompiler, - => continue, - }; - return full_ld_path; - } - - // Finally, we fall back on the standard path. - return Target.current.getStandardDynamicLinkerPath(allocator); -} - const Search = struct { path: []const u8, version: []const u8, diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 0a29da4778..1efa15574a 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -515,7 +515,7 @@ const DarwinPlatform = struct { break :blk ver; }, .None => blk: { - assert(comp.target.getOs() == .macosx); + assert(comp.target.os.tag == .macosx); result.kind = .MacOS; break :blk "10.14"; }, @@ -534,7 +534,7 @@ const DarwinPlatform = struct { } if (result.kind == .IPhoneOS) { - switch (comp.target.getArch()) { + switch (comp.target.cpu.arch) { .i386, .x86_64, => result.kind = .IPhoneOSSimulator, diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index fbfd1e2642..4446a974d4 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -79,9 +79,9 @@ pub fn main() !void { } else if (mem.eql(u8, cmd, "libc")) { return cmdLibC(allocator, cmd_args); } else if (mem.eql(u8, cmd, "targets")) { - // TODO figure out the current target rather than using the target that was specified when - // compiling the compiler - return @import("print_targets.zig").cmdTargets(allocator, cmd_args, stdout, Target.current); + const info = try std.zig.system.NativeTargetInfo.detect(allocator); + defer info.deinit(allocator); + return @import("print_targets.zig").cmdTargets(allocator, cmd_args, stdout, info.target); } else if (mem.eql(u8, cmd, "version")) { return cmdVersion(allocator, cmd_args); } else if (mem.eql(u8, cmd, "zen")) { @@ -792,7 +792,7 @@ async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtErro } fn cmdVersion(allocator: *Allocator, args: []const []const u8) !void { - try stdout.print("{}\n", .{std.mem.toSliceConst(u8, c.ZIG_VERSION_STRING)}); + try stdout.print("{}\n", .{c.ZIG_VERSION_STRING}); } fn cmdHelp(allocator: *Allocator, args: []const []const u8) !void { @@ -863,12 +863,12 @@ fn cmdInternalBuildInfo(allocator: *Allocator, args: []const []const u8) !void { \\ZIG_DIA_GUIDS_LIB {} \\ , .{ - std.mem.toSliceConst(u8, c.ZIG_CMAKE_BINARY_DIR), - std.mem.toSliceConst(u8, c.ZIG_CXX_COMPILER), - std.mem.toSliceConst(u8, c.ZIG_LLD_INCLUDE_PATH), - std.mem.toSliceConst(u8, c.ZIG_LLD_LIBRARIES), - std.mem.toSliceConst(u8, c.ZIG_LLVM_CONFIG_EXE), - std.mem.toSliceConst(u8, c.ZIG_DIA_GUIDS_LIB), + c.ZIG_CMAKE_BINARY_DIR, + c.ZIG_CXX_COMPILER, + c.ZIG_LLD_INCLUDE_PATH, + c.ZIG_LLD_LIBRARIES, + c.ZIG_LLVM_CONFIG_EXE, + c.ZIG_DIA_GUIDS_LIB, }); } diff --git a/src-self-hosted/print_targets.zig b/src-self-hosted/print_targets.zig index 16f1891164..be024a2a04 100644 --- a/src-self-hosted/print_targets.zig +++ b/src-self-hosted/print_targets.zig @@ -124,7 +124,7 @@ pub fn cmdTargets( try jws.objectField("os"); try jws.beginArray(); - inline for (@typeInfo(Target.Os).Enum.fields) |field| { + inline for (@typeInfo(Target.Os.Tag).Enum.fields) |field| { try jws.arrayElem(); try jws.emitString(field.name); } @@ -201,16 +201,16 @@ pub fn cmdTargets( try jws.objectField("cpu"); try jws.beginObject(); try jws.objectField("arch"); - try jws.emitString(@tagName(native_target.getArch())); + try jws.emitString(@tagName(native_target.cpu.arch)); try jws.objectField("name"); - const cpu = native_target.getCpu(); + const cpu = native_target.cpu; try jws.emitString(cpu.model.name); { try jws.objectField("features"); try jws.beginArray(); - for (native_target.getArch().allFeaturesList()) |feature, i_usize| { + for (native_target.cpu.arch.allFeaturesList()) |feature, i_usize| { const index = @intCast(Target.Cpu.Feature.Set.Index, i_usize); if (cpu.features.isEnabled(index)) { try jws.arrayElem(); @@ -222,9 +222,9 @@ pub fn cmdTargets( try jws.endObject(); } try jws.objectField("os"); - try jws.emitString(@tagName(native_target.getOs())); + try jws.emitString(@tagName(native_target.os.tag)); try jws.objectField("abi"); - try jws.emitString(@tagName(native_target.getAbi())); + try jws.emitString(@tagName(native_target.abi)); // TODO implement native glibc version detection in self-hosted try jws.endObject(); diff --git a/src-self-hosted/stage2.zig b/src-self-hosted/stage2.zig index dacdb86bcd..a8aa10d91e 100644 --- a/src-self-hosted/stage2.zig +++ b/src-self-hosted/stage2.zig @@ -10,6 +10,7 @@ const Allocator = mem.Allocator; const ArrayList = std.ArrayList; const Buffer = std.Buffer; const Target = std.Target; +const CrossTarget = std.zig.CrossTarget; const self_hosted_main = @import("main.zig"); const errmsg = @import("errmsg.zig"); const DepTokenizer = @import("dep_tokenizer.zig").Tokenizer; @@ -87,7 +88,7 @@ const Error = extern enum { NotLazy, IsAsync, ImportOutsidePkgPath, - UnknownCpu, + UnknownCpuModel, UnknownCpuFeature, InvalidCpuFeatures, InvalidLlvmCpuFeaturesFormat, @@ -110,6 +111,8 @@ const Error = extern enum { WindowsSdkNotFound, UnknownDynamicLinkerPath, TargetHasNoDynamicLinker, + InvalidAbiVersion, + InvalidOperatingSystemVersion, }; const FILE = std.c.FILE; @@ -632,13 +635,9 @@ export fn stage2_cmd_targets(zig_triple: [*:0]const u8) c_int { } fn cmdTargets(zig_triple: [*:0]const u8) !void { - var target = try Target.parse(.{ .arch_os_abi = mem.toSliceConst(u8, zig_triple) }); - target.Cross.cpu = blk: { - const llvm = @import("llvm.zig"); - const llvm_cpu_name = llvm.GetHostCPUName(); - const llvm_cpu_features = llvm.GetNativeFeatures(); - break :blk try detectNativeCpuWithLLVM(target.getArch(), llvm_cpu_name, llvm_cpu_features); - }; + var cross_target = try CrossTarget.parse(.{ .arch_os_abi = mem.toSliceConst(u8, zig_triple) }); + var dynamic_linker: ?[*:0]u8 = null; + const target = try crossTargetToTarget(cross_target, &dynamic_linker); return @import("print_targets.zig").cmdTargets( std.heap.c_allocator, &[0][]u8{}, @@ -652,16 +651,24 @@ export fn stage2_target_parse( target: *Stage2Target, zig_triple: ?[*:0]const u8, mcpu: ?[*:0]const u8, + dynamic_linker: ?[*:0]const u8, ) Error { - stage2TargetParse(target, zig_triple, mcpu) catch |err| switch (err) { + stage2TargetParse(target, zig_triple, mcpu, dynamic_linker) catch |err| switch (err) { error.OutOfMemory => return .OutOfMemory, error.UnknownArchitecture => return .UnknownArchitecture, error.UnknownOperatingSystem => return .UnknownOperatingSystem, error.UnknownApplicationBinaryInterface => return .UnknownApplicationBinaryInterface, error.MissingOperatingSystem => return .MissingOperatingSystem, - error.MissingArchitecture => return .MissingArchitecture, error.InvalidLlvmCpuFeaturesFormat => return .InvalidLlvmCpuFeaturesFormat, error.UnexpectedExtraField => return .SemanticAnalyzeFail, + error.InvalidAbiVersion => return .InvalidAbiVersion, + error.InvalidOperatingSystemVersion => return .InvalidOperatingSystemVersion, + error.FileSystem => return .FileSystem, + error.SymLinkLoop => return .SymLinkLoop, + error.SystemResources => return .SystemResources, + error.ProcessFdQuotaExceeded => return .ProcessFdQuotaExceeded, + error.SystemFdQuotaExceeded => return .SystemFdQuotaExceeded, + error.DeviceBusy => return .DeviceBusy, }; return .None; } @@ -670,17 +677,20 @@ fn stage2TargetParse( stage1_target: *Stage2Target, zig_triple_oz: ?[*:0]const u8, mcpu_oz: ?[*:0]const u8, + dynamic_linker_oz: ?[*:0]const u8, ) !void { - const target: Target = if (zig_triple_oz) |zig_triple_z| blk: { + const target: CrossTarget = if (zig_triple_oz) |zig_triple_z| blk: { const zig_triple = mem.toSliceConst(u8, zig_triple_z); - const mcpu = if (mcpu_oz) |mcpu_z| mem.toSliceConst(u8, mcpu_z) else "baseline"; - var diags: std.Target.ParseOptions.Diagnostics = .{}; - break :blk Target.parse(.{ + const mcpu = if (mcpu_oz) |mcpu_z| mem.toSliceConst(u8, mcpu_z) else null; + const dynamic_linker = if (dynamic_linker_oz) |dl_z| mem.toSliceConst(u8, dl_z) else null; + var diags: CrossTarget.ParseOptions.Diagnostics = .{}; + break :blk CrossTarget.parse(.{ .arch_os_abi = zig_triple, .cpu_features = mcpu, + .dynamic_linker = dynamic_linker, .diagnostics = &diags, }) catch |err| switch (err) { - error.UnknownCpu => { + error.UnknownCpuModel => { std.debug.warn("Unknown CPU: '{}'\nAvailable CPUs for architecture '{}':\n", .{ diags.cpu_name.?, @tagName(diags.arch.?), @@ -706,73 +716,11 @@ fn stage2TargetParse( }, else => |e| return e, }; - } else Target.Native; + } else .{}; try stage1_target.fromTarget(target); } -fn initStage1TargetCpuFeatures(stage1_target: *Stage2Target, cpu: Target.Cpu) !void { - const allocator = std.heap.c_allocator; - const cache_hash = try std.fmt.allocPrint0(allocator, "{}\n{}", .{ - cpu.model.name, - cpu.features.asBytes(), - }); - errdefer allocator.free(cache_hash); - - const generic_arch_name = cpu.arch.genericName(); - var builtin_str_buffer = try std.Buffer.allocPrint(allocator, - \\Cpu{{ - \\ .arch = .{}, - \\ .model = &Target.{}.cpu.{}, - \\ .features = Target.{}.featureSet(&[_]Target.{}.Feature{{ - \\ - , .{ - @tagName(cpu.arch), - generic_arch_name, - cpu.model.name, - generic_arch_name, - generic_arch_name, - }); - defer builtin_str_buffer.deinit(); - - var llvm_features_buffer = try std.Buffer.initSize(allocator, 0); - defer llvm_features_buffer.deinit(); - - for (cpu.arch.allFeaturesList()) |feature, index_usize| { - const index = @intCast(Target.Cpu.Feature.Set.Index, index_usize); - const is_enabled = cpu.features.isEnabled(index); - - if (feature.llvm_name) |llvm_name| { - const plus_or_minus = "-+"[@boolToInt(is_enabled)]; - try llvm_features_buffer.appendByte(plus_or_minus); - try llvm_features_buffer.append(llvm_name); - try llvm_features_buffer.append(","); - } - - if (is_enabled) { - // TODO some kind of "zig identifier escape" function rather than - // unconditionally using @"" syntax - try builtin_str_buffer.append(" .@\""); - try builtin_str_buffer.append(feature.name); - try builtin_str_buffer.append("\",\n"); - } - } - - try builtin_str_buffer.append( - \\ }), - \\}; - \\ - ); - - assert(mem.endsWith(u8, llvm_features_buffer.toSliceConst(), ",")); - llvm_features_buffer.shrink(llvm_features_buffer.len() - 1); - - stage1_target.llvm_cpu_name = if (cpu.model.llvm_name) |s| s.ptr else null; - stage1_target.llvm_cpu_features = llvm_features_buffer.toOwnedSlice().ptr; - stage1_target.builtin_str = builtin_str_buffer.toOwnedSlice().ptr; - stage1_target.cache_hash = cache_hash.ptr; -} - // ABI warning const Stage2LibCInstallation = extern struct { include_dir: [*:0]const u8, @@ -948,15 +896,18 @@ const Stage2Target = extern struct { is_native: bool, - glibc_version: ?*Stage2GLibCVersion, // null means default + glibc_or_darwin_version: ?*Stage2SemVer, llvm_cpu_name: ?[*:0]const u8, llvm_cpu_features: ?[*:0]const u8, - builtin_str: ?[*:0]const u8, + cpu_builtin_str: ?[*:0]const u8, cache_hash: ?[*:0]const u8, + os_builtin_str: ?[*:0]const u8, - fn toTarget(in_target: Stage2Target) Target { - if (in_target.is_native) return .Native; + dynamic_linker: ?[*:0]const u8, + + fn toTarget(in_target: Stage2Target) CrossTarget { + if (in_target.is_native) return .{}; const in_arch = in_target.arch - 1; // skip over ZigLLVM_UnknownArch const in_os = in_target.os; @@ -965,66 +916,270 @@ const Stage2Target = extern struct { return .{ .Cross = .{ .cpu = Target.Cpu.baseline(enumInt(Target.Cpu.Arch, in_arch)), - .os = enumInt(Target.Os, in_os), + .os = Target.Os.defaultVersionRange(enumInt(Target.Os.Tag, in_os)), .abi = enumInt(Target.Abi, in_abi), }, }; } - fn fromTarget(self: *Stage2Target, target: Target) !void { - const cpu = switch (target) { - .Native => blk: { - // TODO self-host CPU model and feature detection instead of relying on LLVM - const llvm = @import("llvm.zig"); - const llvm_cpu_name = llvm.GetHostCPUName(); - const llvm_cpu_features = llvm.GetNativeFeatures(); - break :blk try detectNativeCpuWithLLVM(target.getArch(), llvm_cpu_name, llvm_cpu_features); - }, - .Cross => target.getCpu(), + fn fromTarget(self: *Stage2Target, cross_target: CrossTarget) !void { + const allocator = std.heap.c_allocator; + + var dynamic_linker: ?[*:0]u8 = null; + const target = try crossTargetToTarget(cross_target, &dynamic_linker); + + var cache_hash = try std.Buffer.allocPrint(allocator, "{}\n{}\n", .{ + target.cpu.model.name, + target.cpu.features.asBytes(), + }); + defer cache_hash.deinit(); + + const generic_arch_name = target.cpu.arch.genericName(); + var cpu_builtin_str_buffer = try std.Buffer.allocPrint(allocator, + \\Cpu{{ + \\ .arch = .{}, + \\ .model = &Target.{}.cpu.{}, + \\ .features = Target.{}.featureSet(&[_]Target.{}.Feature{{ + \\ + , .{ + @tagName(target.cpu.arch), + generic_arch_name, + target.cpu.model.name, + generic_arch_name, + generic_arch_name, + }); + defer cpu_builtin_str_buffer.deinit(); + + var llvm_features_buffer = try std.Buffer.initSize(allocator, 0); + defer llvm_features_buffer.deinit(); + + for (target.cpu.arch.allFeaturesList()) |feature, index_usize| { + const index = @intCast(Target.Cpu.Feature.Set.Index, index_usize); + const is_enabled = target.cpu.features.isEnabled(index); + + if (feature.llvm_name) |llvm_name| { + const plus_or_minus = "-+"[@boolToInt(is_enabled)]; + try llvm_features_buffer.appendByte(plus_or_minus); + try llvm_features_buffer.append(llvm_name); + try llvm_features_buffer.append(","); + } + + if (is_enabled) { + // TODO some kind of "zig identifier escape" function rather than + // unconditionally using @"" syntax + try cpu_builtin_str_buffer.append(" .@\""); + try cpu_builtin_str_buffer.append(feature.name); + try cpu_builtin_str_buffer.append("\",\n"); + } + } + + try cpu_builtin_str_buffer.append( + \\ }), + \\}; + \\ + ); + + assert(mem.endsWith(u8, llvm_features_buffer.toSliceConst(), ",")); + llvm_features_buffer.shrink(llvm_features_buffer.len() - 1); + + var os_builtin_str_buffer = try std.Buffer.allocPrint(allocator, + \\Os{{ + \\ .tag = .{}, + \\ .version_range = .{{ + , .{@tagName(target.os.tag)}); + defer os_builtin_str_buffer.deinit(); + + // We'll re-use the OS version range builtin string for the cache hash. + const os_builtin_str_ver_start_index = os_builtin_str_buffer.len(); + + @setEvalBranchQuota(2000); + switch (target.os.tag) { + .freestanding, + .ananas, + .cloudabi, + .dragonfly, + .fuchsia, + .ios, + .kfreebsd, + .lv2, + .solaris, + .haiku, + .minix, + .rtems, + .nacl, + .cnk, + .aix, + .cuda, + .nvcl, + .amdhsa, + .ps4, + .elfiamcu, + .tvos, + .watchos, + .mesa3d, + .contiki, + .amdpal, + .hermit, + .hurd, + .wasi, + .emscripten, + .uefi, + .other, + => try os_builtin_str_buffer.append(" .none = {} }\n"), + + .freebsd, + .macosx, + .netbsd, + .openbsd, + => try os_builtin_str_buffer.print( + \\ .semver = .{{ + \\ .min = .{{ + \\ .major = {}, + \\ .minor = {}, + \\ .patch = {}, + \\ }}, + \\ .max = .{{ + \\ .major = {}, + \\ .minor = {}, + \\ .patch = {}, + \\ }}, + \\ }}}}, + \\ + , .{ + target.os.version_range.semver.min.major, + target.os.version_range.semver.min.minor, + target.os.version_range.semver.min.patch, + + target.os.version_range.semver.max.major, + target.os.version_range.semver.max.minor, + target.os.version_range.semver.max.patch, + }), + + .linux => try os_builtin_str_buffer.print( + \\ .linux = .{{ + \\ .range = .{{ + \\ .min = .{{ + \\ .major = {}, + \\ .minor = {}, + \\ .patch = {}, + \\ }}, + \\ .max = .{{ + \\ .major = {}, + \\ .minor = {}, + \\ .patch = {}, + \\ }}, + \\ }}, + \\ .glibc = .{{ + \\ .major = {}, + \\ .minor = {}, + \\ .patch = {}, + \\ }}, + \\ }}}}, + \\ + , .{ + target.os.version_range.linux.range.min.major, + target.os.version_range.linux.range.min.minor, + target.os.version_range.linux.range.min.patch, + + target.os.version_range.linux.range.max.major, + target.os.version_range.linux.range.max.minor, + target.os.version_range.linux.range.max.patch, + + target.os.version_range.linux.glibc.major, + target.os.version_range.linux.glibc.minor, + target.os.version_range.linux.glibc.patch, + }), + + .windows => try os_builtin_str_buffer.print( + \\ .windows = .{{ + \\ .min = .{}, + \\ .max = .{}, + \\ }}}}, + \\ + , .{ + @tagName(target.os.version_range.windows.min), + @tagName(target.os.version_range.windows.max), + }), + } + try os_builtin_str_buffer.append("};\n"); + + try cache_hash.append( + os_builtin_str_buffer.toSlice()[os_builtin_str_ver_start_index..os_builtin_str_buffer.len()], + ); + + const glibc_or_darwin_version = blk: { + if (target.isGnuLibC()) { + const stage1_glibc = try std.heap.c_allocator.create(Stage2SemVer); + const stage2_glibc = target.os.version_range.linux.glibc; + stage1_glibc.* = .{ + .major = stage2_glibc.major, + .minor = stage2_glibc.minor, + .patch = stage2_glibc.patch, + }; + break :blk stage1_glibc; + } else if (target.isDarwin()) { + const stage1_semver = try std.heap.c_allocator.create(Stage2SemVer); + const stage2_semver = target.os.version_range.semver.min; + stage1_semver.* = .{ + .major = stage2_semver.major, + .minor = stage2_semver.minor, + .patch = stage2_semver.patch, + }; + break :blk stage1_semver; + } else { + break :blk null; + } }; + self.* = .{ - .arch = @enumToInt(target.getArch()) + 1, // skip over ZigLLVM_UnknownArch + .arch = @enumToInt(target.cpu.arch) + 1, // skip over ZigLLVM_UnknownArch .vendor = 0, - .os = @enumToInt(target.getOs()), - .abi = @enumToInt(target.getAbi()), - .llvm_cpu_name = null, - .llvm_cpu_features = null, - .builtin_str = null, - .cache_hash = null, - .is_native = target == .Native, - .glibc_version = null, + .os = @enumToInt(target.os.tag), + .abi = @enumToInt(target.abi), + .llvm_cpu_name = if (target.cpu.model.llvm_name) |s| s.ptr else null, + .llvm_cpu_features = llvm_features_buffer.toOwnedSlice().ptr, + .cpu_builtin_str = cpu_builtin_str_buffer.toOwnedSlice().ptr, + .os_builtin_str = os_builtin_str_buffer.toOwnedSlice().ptr, + .cache_hash = cache_hash.toOwnedSlice().ptr, + .is_native = cross_target.isNative(), + .glibc_or_darwin_version = glibc_or_darwin_version, + .dynamic_linker = dynamic_linker, }; - try initStage1TargetCpuFeatures(self, cpu); } }; -// ABI warning -const Stage2GLibCVersion = extern struct { - major: u32, - minor: u32, - patch: u32, -}; - -// ABI warning -export fn stage2_detect_dynamic_linker(in_target: *const Stage2Target, out_ptr: *[*:0]u8, out_len: *usize) Error { - const target = in_target.toTarget(); - const result = @import("introspect.zig").detectDynamicLinker( - std.heap.c_allocator, - target, - ) catch |err| switch (err) { - error.OutOfMemory => return .OutOfMemory, - error.UnknownDynamicLinkerPath => return .UnknownDynamicLinkerPath, - error.TargetHasNoDynamicLinker => return .TargetHasNoDynamicLinker, - }; - out_ptr.* = result.ptr; - out_len.* = result.len; - return .None; -} - fn enumInt(comptime Enum: type, int: c_int) Enum { return @intToEnum(Enum, @intCast(@TagType(Enum), int)); } +fn crossTargetToTarget(cross_target: CrossTarget, dynamic_linker_ptr: *?[*:0]u8) !Target { + var info = try std.zig.system.NativeTargetInfo.detect(std.heap.c_allocator, cross_target); + if (cross_target.cpu_arch == null or cross_target.cpu_model == .native) { + // TODO We want to just use detected_info.target but implementing + // CPU model & feature detection is todo so here we rely on LLVM. + const llvm = @import("llvm.zig"); + const llvm_cpu_name = llvm.GetHostCPUName(); + const llvm_cpu_features = llvm.GetNativeFeatures(); + const arch = std.Target.current.cpu.arch; + info.target.cpu = try detectNativeCpuWithLLVM(arch, llvm_cpu_name, llvm_cpu_features); + cross_target.updateCpuFeatures(&info.target.cpu.features); + info.target.cpu.arch = cross_target.getCpuArch(); + } + if (info.dynamic_linker.get()) |dl| { + dynamic_linker_ptr.* = try mem.dupeZ(std.heap.c_allocator, u8, dl); + } else { + dynamic_linker_ptr.* = null; + } + return info.target; +} + +// ABI warning +const Stage2SemVer = extern struct { + major: u32, + minor: u32, + patch: u32, +}; + // ABI warning const Stage2NativePaths = extern struct { include_dirs_ptr: [*][*:0]u8, diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig index 7ba4188786..92ae0f2877 100644 --- a/src-self-hosted/translate_c.zig +++ b/src-self-hosted/translate_c.zig @@ -4850,7 +4850,7 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void { } const begin_c = ZigClangSourceManager_getCharacterData(c.source_manager, begin_loc); - const slice = begin_c[0..mem.len(u8, begin_c)]; + const slice = begin_c[0..mem.len(begin_c)]; tok_list.shrink(0); var tokenizer = std.c.Tokenizer{ diff --git a/src-self-hosted/util.zig b/src-self-hosted/util.zig index 04c5420d26..2a7bf4d9cc 100644 --- a/src-self-hosted/util.zig +++ b/src-self-hosted/util.zig @@ -34,25 +34,3 @@ pub fn initializeAllTargets() void { llvm.InitializeAllAsmPrinters(); llvm.InitializeAllAsmParsers(); } - -pub fn getTriple(allocator: *std.mem.Allocator, self: std.Target) !std.Buffer { - var result = try std.Buffer.initSize(allocator, 0); - errdefer result.deinit(); - - // LLVM WebAssembly output support requires the target to be activated at - // build type with -DCMAKE_LLVM_EXPIERMENTAL_TARGETS_TO_BUILD=WebAssembly. - // - // LLVM determines the output format based on the abi suffix, - // defaulting to an object based on the architecture. The default format in - // LLVM 6 sets the wasm arch output incorrectly to ELF. We need to - // explicitly set this ourself in order for it to work. - // - // This is fixed in LLVM 7 and you will be able to get wasm output by - // using the target triple `wasm32-unknown-unknown-unknown`. - const env_name = if (self.isWasm()) "wasm" else @tagName(self.getAbi()); - - var out = &std.io.BufferOutStream.init(&result).stream; - try out.print("{}-unknown-{}-{}", .{ @tagName(self.getArch()), @tagName(self.getOs()), env_name }); - - return result; -} diff --git a/src/all_types.hpp b/src/all_types.hpp index c4989df1e9..620b95dccd 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -2249,14 +2249,11 @@ struct CodeGen { bool test_is_evented; CodeModel code_model; - Buf *mmacosx_version_min; - Buf *mios_version_min; Buf *root_out_name; Buf *test_filter; Buf *test_name_prefix; Buf *zig_lib_dir; Buf *zig_std_dir; - Buf *dynamic_linker_path; Buf *version_script_path; const char **llvm_argv; @@ -3266,7 +3263,6 @@ struct IrInstSrcContainerInitList { struct IrInstSrcContainerInitFieldsField { Buf *name; AstNode *source_node; - TypeStructField *type_struct_field; IrInstSrc *result_loc; }; diff --git a/src/analyze.cpp b/src/analyze.cpp index 95b2c77129..c9b45164f2 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -1131,18 +1131,26 @@ Error type_val_resolve_zero_bits(CodeGen *g, ZigValue *type_val, ZigType *parent Error err; if (type_val->special != ConstValSpecialLazy) { assert(type_val->special == ConstValSpecialStatic); - if ((type_val->data.x_type->id == ZigTypeIdStruct && - type_val->data.x_type->data.structure.resolve_loop_flag_zero_bits) || - (type_val->data.x_type->id == ZigTypeIdUnion && - type_val->data.x_type->data.unionation.resolve_loop_flag_zero_bits) || - type_val->data.x_type->id == ZigTypeIdPointer) + + // Self-referencing types via pointers are allowed and have non-zero size + ZigType *ty = type_val->data.x_type; + while (ty->id == ZigTypeIdPointer && + !ty->data.pointer.resolve_loop_flag_zero_bits) + { + ty = ty->data.pointer.child_type; + } + + if ((ty->id == ZigTypeIdStruct && ty->data.structure.resolve_loop_flag_zero_bits) || + (ty->id == ZigTypeIdUnion && ty->data.unionation.resolve_loop_flag_zero_bits) || + (ty->id == ZigTypeIdPointer && ty->data.pointer.resolve_loop_flag_zero_bits)) { - // Does a struct/union which contains a pointer field to itself have bits? Yes. *is_zero_bits = false; return ErrorNone; } + if ((err = type_resolve(g, type_val->data.x_type, ResolveStatusZeroBitsKnown))) return err; + *is_zero_bits = (type_val->data.x_type->abi_size == 0); return ErrorNone; } @@ -3955,7 +3963,7 @@ static void resolve_decl_var(CodeGen *g, TldVar *tld_var, bool allow_lazy) { // TODO more validation for types that can't be used for export/extern variables ZigType *implicit_type = nullptr; - if (explicit_type != nullptr && explicit_type->id == ZigTypeIdInvalid) { + if (explicit_type != nullptr && type_is_invalid(explicit_type)) { implicit_type = explicit_type; } else if (var_decl->expr) { init_value = analyze_const_value(g, tld_var->base.parent_scope, var_decl->expr, explicit_type, @@ -4763,38 +4771,41 @@ static void analyze_fn_ir(CodeGen *g, ZigFn *fn, AstNode *return_type_node) { if (return_err_set_type->data.error_set.infer_fn != nullptr && return_err_set_type->data.error_set.incomplete) { - ZigType *inferred_err_set_type; + // The inferred error set type is null if the function doesn't + // return any error + ZigType *inferred_err_set_type = nullptr; + if (fn->src_implicit_return_type->id == ZigTypeIdErrorSet) { inferred_err_set_type = fn->src_implicit_return_type; } else if (fn->src_implicit_return_type->id == ZigTypeIdErrorUnion) { inferred_err_set_type = fn->src_implicit_return_type->data.error_union.err_set_type; - } else { - add_node_error(g, return_type_node, - buf_sprintf("function with inferred error set must return at least one possible error")); - fn->anal_state = FnAnalStateInvalid; - return; } - if (inferred_err_set_type->data.error_set.infer_fn != nullptr && - inferred_err_set_type->data.error_set.incomplete) - { - if (!resolve_inferred_error_set(g, inferred_err_set_type, return_type_node)) { - fn->anal_state = FnAnalStateInvalid; - return; - } - } - - return_err_set_type->data.error_set.incomplete = false; - if (type_is_global_error_set(inferred_err_set_type)) { - return_err_set_type->data.error_set.err_count = UINT32_MAX; - } else { - return_err_set_type->data.error_set.err_count = inferred_err_set_type->data.error_set.err_count; - if (inferred_err_set_type->data.error_set.err_count > 0) { - return_err_set_type->data.error_set.errors = heap::c_allocator.allocate(inferred_err_set_type->data.error_set.err_count); - for (uint32_t i = 0; i < inferred_err_set_type->data.error_set.err_count; i += 1) { - return_err_set_type->data.error_set.errors[i] = inferred_err_set_type->data.error_set.errors[i]; + if (inferred_err_set_type != nullptr) { + if (inferred_err_set_type->data.error_set.infer_fn != nullptr && + inferred_err_set_type->data.error_set.incomplete) + { + if (!resolve_inferred_error_set(g, inferred_err_set_type, return_type_node)) { + fn->anal_state = FnAnalStateInvalid; + return; } } + + return_err_set_type->data.error_set.incomplete = false; + if (type_is_global_error_set(inferred_err_set_type)) { + return_err_set_type->data.error_set.err_count = UINT32_MAX; + } else { + return_err_set_type->data.error_set.err_count = inferred_err_set_type->data.error_set.err_count; + if (inferred_err_set_type->data.error_set.err_count > 0) { + return_err_set_type->data.error_set.errors = heap::c_allocator.allocate(inferred_err_set_type->data.error_set.err_count); + for (uint32_t i = 0; i < inferred_err_set_type->data.error_set.err_count; i += 1) { + return_err_set_type->data.error_set.errors[i] = inferred_err_set_type->data.error_set.errors[i]; + } + } + } + } else { + return_err_set_type->data.error_set.incomplete = false; + return_err_set_type->data.error_set.err_count = 0; } } } @@ -5390,6 +5401,8 @@ bool generic_fn_type_id_eql(GenericFnTypeId *a, GenericFnTypeId *b) { static bool can_mutate_comptime_var_state(ZigValue *value) { assert(value != nullptr); + if (value->special == ConstValSpecialUndef) + return false; switch (value->type->id) { case ZigTypeIdInvalid: zig_unreachable(); @@ -5418,6 +5431,8 @@ static bool can_mutate_comptime_var_state(ZigValue *value) { return value->data.x_ptr.mut == ConstPtrMutComptimeVar; case ZigTypeIdArray: + if (value->special == ConstValSpecialUndef) + return false; if (value->type->data.array.len == 0) return false; switch (value->data.x_array.special) { @@ -6690,8 +6705,16 @@ bool const_values_equal_ptr(ZigValue *a, ZigValue *b) { } static bool const_values_equal_array(CodeGen *g, ZigValue *a, ZigValue *b, size_t len) { - assert(a->data.x_array.special != ConstArraySpecialUndef); - assert(b->data.x_array.special != ConstArraySpecialUndef); + if (a->data.x_array.special == ConstArraySpecialUndef && + b->data.x_array.special == ConstArraySpecialUndef) + { + return true; + } + if (a->data.x_array.special == ConstArraySpecialUndef || + b->data.x_array.special == ConstArraySpecialUndef) + { + return false; + } if (a->data.x_array.special == ConstArraySpecialBuf && b->data.x_array.special == ConstArraySpecialBuf) { @@ -6713,8 +6736,6 @@ static bool const_values_equal_array(CodeGen *g, ZigValue *a, ZigValue *b, size_ bool const_values_equal(CodeGen *g, ZigValue *a, ZigValue *b) { if (a->type->id != b->type->id) return false; - assert(a->special == ConstValSpecialStatic); - assert(b->special == ConstValSpecialStatic); if (a->type == b->type) { switch (type_has_one_possible_value(g, a->type)) { case OnePossibleValueInvalid: @@ -6725,6 +6746,11 @@ bool const_values_equal(CodeGen *g, ZigValue *a, ZigValue *b) { return true; } } + if (a->special == ConstValSpecialUndef || b->special == ConstValSpecialUndef) { + return a->special == b->special; + } + assert(a->special == ConstValSpecialStatic); + assert(b->special == ConstValSpecialStatic); switch (a->type->id) { case ZigTypeIdOpaque: zig_unreachable(); @@ -8708,7 +8734,6 @@ static void resolve_llvm_types_optional(CodeGen *g, ZigType *type, ResolveStatus if (ResolveStatusLLVMFwdDecl >= wanted_resolve_status) return; } - LLVMTypeRef child_llvm_type = get_llvm_type(g, child_type); ZigLLVMDIType *child_llvm_di_type = get_llvm_di_type(g, child_type); if (type->data.maybe.resolve_status >= wanted_resolve_status) return; @@ -8718,35 +8743,28 @@ static void resolve_llvm_types_optional(CodeGen *g, ZigType *type, ResolveStatus }; LLVMStructSetBody(type->llvm_type, elem_types, 2, false); - uint64_t val_debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, child_llvm_type); - uint64_t val_debug_align_in_bits = 8*LLVMABISizeOfType(g->target_data_ref, child_llvm_type); - uint64_t val_offset_in_bits = 8*LLVMOffsetOfElement(g->target_data_ref, type->llvm_type, 0); + uint64_t val_offset_in_bits = 8*LLVMOffsetOfElement(g->target_data_ref, type->llvm_type, maybe_child_index); + uint64_t maybe_offset_in_bits = 8*LLVMOffsetOfElement(g->target_data_ref, type->llvm_type, maybe_null_index); - uint64_t maybe_debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, bool_llvm_type); - uint64_t maybe_debug_align_in_bits = 8*LLVMABISizeOfType(g->target_data_ref, bool_llvm_type); - uint64_t maybe_offset_in_bits = 8*LLVMOffsetOfElement(g->target_data_ref, type->llvm_type, 1); - - uint64_t debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, type->llvm_type); - uint64_t debug_align_in_bits = 8*LLVMABISizeOfType(g->target_data_ref, type->llvm_type); - - ZigLLVMDIType *di_element_types[] = { + ZigLLVMDIType *di_element_types[2]; + di_element_types[maybe_child_index] = ZigLLVMCreateDebugMemberType(g->dbuilder, ZigLLVMTypeToScope(type->llvm_di_type), "val", di_file, line, - val_debug_size_in_bits, - val_debug_align_in_bits, + 8 * child_type->abi_size, + 8 * child_type->abi_align, val_offset_in_bits, - ZigLLVM_DIFlags_Zero, child_llvm_di_type), + ZigLLVM_DIFlags_Zero, child_llvm_di_type); + di_element_types[maybe_null_index] = ZigLLVMCreateDebugMemberType(g->dbuilder, ZigLLVMTypeToScope(type->llvm_di_type), "maybe", di_file, line, - maybe_debug_size_in_bits, - maybe_debug_align_in_bits, + 8*g->builtin_types.entry_bool->abi_size, + 8*g->builtin_types.entry_bool->abi_align, maybe_offset_in_bits, - ZigLLVM_DIFlags_Zero, bool_llvm_di_type), - }; + ZigLLVM_DIFlags_Zero, bool_llvm_di_type); ZigLLVMDIType *replacement_di_type = ZigLLVMCreateDebugStructType(g->dbuilder, compile_unit_scope, buf_ptr(&type->name), - di_file, line, debug_size_in_bits, debug_align_in_bits, ZigLLVM_DIFlags_Zero, + di_file, line, 8 * type->abi_size, 8 * type->abi_align, ZigLLVM_DIFlags_Zero, nullptr, di_element_types, 2, 0, nullptr, ""); ZigLLVMReplaceTemporary(g->dbuilder, type->llvm_di_type, replacement_di_type); @@ -9387,13 +9405,24 @@ void copy_const_val(CodeGen *g, ZigValue *dest, ZigValue *src) { dest->data.x_struct.fields[i]->parent.data.p_struct.field_index = i; } } else if (dest->type->id == ZigTypeIdArray) { - if (dest->data.x_array.special == ConstArraySpecialNone) { - dest->data.x_array.data.s_none.elements = g->pass1_arena->allocate(dest->type->data.array.len); - for (uint64_t i = 0; i < dest->type->data.array.len; i += 1) { - copy_const_val(g, &dest->data.x_array.data.s_none.elements[i], &src->data.x_array.data.s_none.elements[i]); - dest->data.x_array.data.s_none.elements[i].parent.id = ConstParentIdArray; - dest->data.x_array.data.s_none.elements[i].parent.data.p_array.array_val = dest; - dest->data.x_array.data.s_none.elements[i].parent.data.p_array.elem_index = i; + switch (dest->data.x_array.special) { + case ConstArraySpecialNone: { + dest->data.x_array.data.s_none.elements = g->pass1_arena->allocate(dest->type->data.array.len); + for (uint64_t i = 0; i < dest->type->data.array.len; i += 1) { + copy_const_val(g, &dest->data.x_array.data.s_none.elements[i], &src->data.x_array.data.s_none.elements[i]); + dest->data.x_array.data.s_none.elements[i].parent.id = ConstParentIdArray; + dest->data.x_array.data.s_none.elements[i].parent.data.p_array.array_val = dest; + dest->data.x_array.data.s_none.elements[i].parent.data.p_array.elem_index = i; + } + break; + } + case ConstArraySpecialUndef: { + // Nothing to copy; the above memcpy did everything we needed. + break; + } + case ConstArraySpecialBuf: { + dest->data.x_array.data.s_buf = buf_create_from_buf(src->data.x_array.data.s_buf); + break; } } } else if (type_has_optional_repr(dest->type) && dest->data.x_optional != nullptr) { diff --git a/src/codegen.cpp b/src/codegen.cpp index dd55f3a665..55a9fa67ff 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -32,31 +32,6 @@ enum ResumeId { ResumeIdCall, }; -static void init_darwin_native(CodeGen *g) { - char *osx_target = getenv("MACOSX_DEPLOYMENT_TARGET"); - char *ios_target = getenv("IPHONEOS_DEPLOYMENT_TARGET"); - - // Allow conflicts among OSX and iOS, but choose the default platform. - if (osx_target && ios_target) { - if (g->zig_target->arch == ZigLLVM_arm || - g->zig_target->arch == ZigLLVM_aarch64 || - g->zig_target->arch == ZigLLVM_thumb) - { - osx_target = nullptr; - } else { - ios_target = nullptr; - } - } - - if (osx_target) { - g->mmacosx_version_min = buf_create_from_str(osx_target); - } else if (ios_target) { - g->mios_version_min = buf_create_from_str(ios_target); - } else if (g->zig_target->os != OsIOS) { - g->mmacosx_version_min = buf_create_from_str("10.14"); - } -} - static ZigPackage *new_package(const char *root_src_dir, const char *root_src_path, const char *pkg_path) { ZigPackage *entry = heap::c_allocator.create(); entry->package_table.init(4); @@ -160,14 +135,6 @@ void codegen_add_framework(CodeGen *g, const char *framework) { g->darwin_frameworks.append(buf_create_from_str(framework)); } -void codegen_set_mmacosx_version_min(CodeGen *g, Buf *mmacosx_version_min) { - g->mmacosx_version_min = mmacosx_version_min; -} - -void codegen_set_mios_version_min(CodeGen *g, Buf *mios_version_min) { - g->mios_version_min = mios_version_min; -} - void codegen_set_rdynamic(CodeGen *g, bool rdynamic) { g->linker_rdynamic = rdynamic; } @@ -972,7 +939,7 @@ static Buf *panic_msg_buf(PanicMsgId msg_id) { case PanicMsgIdExactDivisionRemainder: return buf_create_from_str("exact division produced remainder"); case PanicMsgIdUnwrapOptionalFail: - return buf_create_from_str("attempt to unwrap null"); + return buf_create_from_str("attempt to use null value"); case PanicMsgIdUnreachable: return buf_create_from_str("reached unreachable code"); case PanicMsgIdInvalidErrorCode: @@ -3325,11 +3292,24 @@ static LLVMValueRef ir_render_int_to_enum(CodeGen *g, IrExecutableGen *executabl LLVMBasicBlockRef ok_value_block = LLVMAppendBasicBlock(g->cur_fn_val, "OkValue"); size_t field_count = wanted_type->data.enumeration.src_field_count; LLVMValueRef switch_instr = LLVMBuildSwitch(g->builder, tag_int_value, bad_value_block, field_count); + + HashMap occupied_tag_values = {}; + occupied_tag_values.init(field_count); + for (size_t field_i = 0; field_i < field_count; field_i += 1) { + TypeEnumField *type_enum_field = &wanted_type->data.enumeration.fields[field_i]; + + Buf *name = type_enum_field->name; + auto entry = occupied_tag_values.put_unique(type_enum_field->value, name); + if (entry != nullptr) { + continue; + } + LLVMValueRef this_tag_int_value = bigint_to_llvm_const(get_llvm_type(g, tag_int_type), - &wanted_type->data.enumeration.fields[field_i].value); + &type_enum_field->value); LLVMAddCase(switch_instr, this_tag_int_value, ok_value_block); } + occupied_tag_values.deinit(); LLVMPositionBuilderAtEnd(g->builder, bad_value_block); gen_safety_crash(g, PanicMsgIdBadEnumValue); @@ -4466,7 +4446,7 @@ static LLVMValueRef ir_render_union_field_ptr(CodeGen *g, IrExecutableGen *execu if (!type_has_bits(field->type_entry)) { ZigType *tag_type = union_type->data.unionation.tag_type; - if (!instruction->initializing || !type_has_bits(tag_type)) + if (!instruction->initializing || tag_type == nullptr || !type_has_bits(tag_type)) return nullptr; // The field has no bits but we still have to change the discriminant @@ -5026,8 +5006,18 @@ static LLVMValueRef get_enum_tag_name_function(CodeGen *g, ZigType *enum_type) { LLVMConstNull(usize->llvm_type), }; + HashMap occupied_tag_values = {}; + occupied_tag_values.init(field_count); + for (size_t field_i = 0; field_i < field_count; field_i += 1) { - Buf *name = enum_type->data.enumeration.fields[field_i].name; + TypeEnumField *type_enum_field = &enum_type->data.enumeration.fields[field_i]; + + Buf *name = type_enum_field->name; + auto entry = occupied_tag_values.put_unique(type_enum_field->value, name); + if (entry != nullptr) { + continue; + } + LLVMValueRef str_init = LLVMConstString(buf_ptr(name), (unsigned)buf_len(name), true); LLVMValueRef str_global = LLVMAddGlobal(g->module, LLVMTypeOf(str_init), ""); LLVMSetInitializer(str_global, str_init); @@ -5057,6 +5047,7 @@ static LLVMValueRef get_enum_tag_name_function(CodeGen *g, ZigType *enum_type) { LLVMPositionBuilderAtEnd(g->builder, return_block); LLVMBuildRet(g->builder, slice_global); } + occupied_tag_values.deinit(); LLVMPositionBuilderAtEnd(g->builder, bad_value_block); if (g->build_mode == BuildModeDebug || g->build_mode == BuildModeSafeRelease) { @@ -5081,11 +5072,6 @@ static LLVMValueRef ir_render_enum_tag_name(CodeGen *g, IrExecutableGen *executa { ZigType *enum_type = instruction->target->value->type; assert(enum_type->id == ZigTypeIdEnum); - if (enum_type->data.enumeration.non_exhaustive) { - add_node_error(g, instruction->base.base.source_node, - buf_sprintf("TODO @tagName on non-exhaustive enum https://github.com/ziglang/zig/issues/3991")); - codegen_report_errors_and_exit(g); - } LLVMValueRef enum_name_function = get_enum_tag_name_function(g, enum_type); @@ -8518,25 +8504,24 @@ Buf *codegen_generate_builtin_source(CodeGen *g) { buf_appendf(contents, "pub const link_mode = LinkMode.%s;\n", link_type); buf_appendf(contents, "pub const is_test = %s;\n", bool_to_str(g->is_test_build)); buf_appendf(contents, "pub const single_threaded = %s;\n", bool_to_str(g->is_single_threaded)); - buf_appendf(contents, "pub const os = Os.%s;\n", cur_os); + buf_append_str(contents, "/// Deprecated: use `std.Target.cpu.arch`\n"); buf_appendf(contents, "pub const arch = Arch.%s;\n", cur_arch); buf_appendf(contents, "pub const abi = Abi.%s;\n", cur_abi); { buf_append_str(contents, "pub const cpu: Cpu = "); - if (g->zig_target->builtin_str != nullptr) { - buf_append_str(contents, g->zig_target->builtin_str); + if (g->zig_target->cpu_builtin_str != nullptr) { + buf_append_str(contents, g->zig_target->cpu_builtin_str); } else { - buf_append_str(contents, "Target.Cpu.baseline(arch);\n"); + buf_appendf(contents, "Target.Cpu.baseline(.%s);\n", cur_arch); } } - if (g->libc_link_lib != nullptr && g->zig_target->glibc_version != nullptr) { - buf_appendf(contents, - "pub const glibc_version: ?Version = Version{.major = %d, .minor = %d, .patch = %d};\n", - g->zig_target->glibc_version->major, - g->zig_target->glibc_version->minor, - g->zig_target->glibc_version->patch); - } else { - buf_appendf(contents, "pub const glibc_version: ?Version = null;\n"); + { + buf_append_str(contents, "pub const os = "); + if (g->zig_target->os_builtin_str != nullptr) { + buf_append_str(contents, g->zig_target->os_builtin_str); + } else { + buf_appendf(contents, "Target.Os.defaultVersionRange(.%s);\n", cur_os); + } } buf_appendf(contents, "pub const object_format = ObjectFormat.%s;\n", cur_obj_fmt); buf_appendf(contents, "pub const mode = %s;\n", build_mode_to_str(g->build_mode)); @@ -8631,10 +8616,10 @@ static Error define_builtin_compile_vars(CodeGen *g) { if (g->zig_target->cache_hash != nullptr) { cache_str(&cache_hash, g->zig_target->cache_hash); } - if (g->zig_target->glibc_version != nullptr) { - cache_int(&cache_hash, g->zig_target->glibc_version->major); - cache_int(&cache_hash, g->zig_target->glibc_version->minor); - cache_int(&cache_hash, g->zig_target->glibc_version->patch); + if (g->zig_target->glibc_or_darwin_version != nullptr) { + cache_int(&cache_hash, g->zig_target->glibc_or_darwin_version->major); + cache_int(&cache_hash, g->zig_target->glibc_or_darwin_version->minor); + cache_int(&cache_hash, g->zig_target->glibc_or_darwin_version->patch); } cache_bool(&cache_hash, g->have_err_ret_tracing); cache_bool(&cache_hash, g->libc_link_lib != nullptr); @@ -8841,28 +8826,6 @@ static void init(CodeGen *g) { } } -static void detect_dynamic_linker(CodeGen *g) { - Error err; - - if (g->dynamic_linker_path != nullptr) - return; - if (!g->have_dynamic_link) - return; - if (g->out_type == OutTypeObj || (g->out_type == OutTypeLib && !g->is_dynamic)) - return; - - char *dynamic_linker_ptr; - size_t dynamic_linker_len; - if ((err = stage2_detect_dynamic_linker(g->zig_target, &dynamic_linker_ptr, &dynamic_linker_len))) { - if (err == ErrorTargetHasNoDynamicLinker) return; - fprintf(stderr, "Unable to detect dynamic linker: %s\n", err_str(err)); - exit(1); - } - g->dynamic_linker_path = buf_create_from_mem(dynamic_linker_ptr, dynamic_linker_len); - // Skips heap::c_allocator because the memory is allocated by stage2 library. - free(dynamic_linker_ptr); -} - static void detect_libc(CodeGen *g) { Error err; @@ -10298,10 +10261,13 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) { if (g->zig_target->cache_hash != nullptr) { cache_str(ch, g->zig_target->cache_hash); } - if (g->zig_target->glibc_version != nullptr) { - cache_int(ch, g->zig_target->glibc_version->major); - cache_int(ch, g->zig_target->glibc_version->minor); - cache_int(ch, g->zig_target->glibc_version->patch); + if (g->zig_target->glibc_or_darwin_version != nullptr) { + cache_int(ch, g->zig_target->glibc_or_darwin_version->major); + cache_int(ch, g->zig_target->glibc_or_darwin_version->minor); + cache_int(ch, g->zig_target->glibc_or_darwin_version->patch); + } + if (g->zig_target->dynamic_linker != nullptr) { + cache_str(ch, g->zig_target->dynamic_linker); } cache_int(ch, detect_subsystem(g)); cache_bool(ch, g->strip_debug_symbols); @@ -10329,8 +10295,6 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) { cache_bool(ch, g->emit_bin); cache_bool(ch, g->emit_llvm_ir); cache_bool(ch, g->emit_asm); - cache_buf_opt(ch, g->mmacosx_version_min); - cache_buf_opt(ch, g->mios_version_min); cache_usize(ch, g->version_major); cache_usize(ch, g->version_minor); cache_usize(ch, g->version_patch); @@ -10345,7 +10309,6 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) { cache_str(ch, g->libc->msvc_lib_dir); cache_str(ch, g->libc->kernel32_lib_dir); } - cache_buf_opt(ch, g->dynamic_linker_path); cache_buf_opt(ch, g->version_script_path); // gen_c_objects appends objects to g->link_objects which we want to include in the hash @@ -10442,7 +10405,6 @@ void codegen_build_and_link(CodeGen *g) { g->have_err_ret_tracing = detect_err_ret_tracing(g); g->have_sanitize_c = detect_sanitize_c(g); detect_libc(g); - detect_dynamic_linker(g); Buf digest = BUF_INIT; if (g->enable_cache) { @@ -10639,7 +10601,6 @@ CodeGen *create_child_codegen(CodeGen *parent_gen, Buf *root_src_path, OutType o child_gen->verbose_cc = parent_gen->verbose_cc; child_gen->verbose_llvm_cpu_features = parent_gen->verbose_llvm_cpu_features; child_gen->llvm_argv = parent_gen->llvm_argv; - child_gen->dynamic_linker_path = parent_gen->dynamic_linker_path; codegen_set_strip(child_gen, parent_gen->strip_debug_symbols); child_gen->want_pic = parent_gen->have_pic ? WantPICEnabled : WantPICDisabled; @@ -10647,9 +10608,6 @@ CodeGen *create_child_codegen(CodeGen *parent_gen, Buf *root_src_path, OutType o codegen_set_errmsg_color(child_gen, parent_gen->err_color); - codegen_set_mmacosx_version_min(child_gen, parent_gen->mmacosx_version_min); - codegen_set_mios_version_min(child_gen, parent_gen->mios_version_min); - child_gen->enable_cache = true; return child_gen; @@ -10757,11 +10715,6 @@ CodeGen *codegen_create(Buf *main_pkg_path, Buf *root_src_path, const ZigTarget g->each_lib_rpath = false; } else { g->each_lib_rpath = true; - - if (target_os_is_darwin(g->zig_target->os)) { - init_darwin_native(g); - } - } if (target_os_requires_libc(g->zig_target->os)) { diff --git a/src/codegen.hpp b/src/codegen.hpp index 6329c59a5e..191da9a04b 100644 --- a/src/codegen.hpp +++ b/src/codegen.hpp @@ -35,8 +35,6 @@ LinkLib *codegen_add_link_lib(CodeGen *codegen, Buf *lib); void codegen_add_framework(CodeGen *codegen, const char *name); void codegen_add_rpath(CodeGen *codegen, const char *name); void codegen_set_rdynamic(CodeGen *g, bool rdynamic); -void codegen_set_mmacosx_version_min(CodeGen *g, Buf *mmacosx_version_min); -void codegen_set_mios_version_min(CodeGen *g, Buf *mios_version_min); void codegen_set_linker_script(CodeGen *g, const char *linker_script); void codegen_set_test_filter(CodeGen *g, Buf *filter); void codegen_set_test_name_prefix(CodeGen *g, Buf *prefix); diff --git a/src/compiler.cpp b/src/compiler.cpp index 31bac4ee24..cddecc2025 100644 --- a/src/compiler.cpp +++ b/src/compiler.cpp @@ -4,31 +4,6 @@ #include -Buf *get_self_libc_path(void) { - static Buf saved_libc_path = BUF_INIT; - static bool searched_for_libc = false; - - for (;;) { - if (saved_libc_path.list.length != 0) { - return &saved_libc_path; - } - if (searched_for_libc) - return nullptr; - ZigList lib_paths = {}; - Error err; - if ((err = os_self_exe_shared_libs(lib_paths))) - return nullptr; - for (size_t i = 0; i < lib_paths.length; i += 1) { - Buf *lib_path = lib_paths.at(i); - if (buf_ends_with_str(lib_path, "libc.so.6")) { - buf_init_from_buf(&saved_libc_path, lib_path); - return &saved_libc_path; - } - } - searched_for_libc = true; - } -} - Error get_compiler_id(Buf **result) { static Buf saved_compiler_id = BUF_INIT; diff --git a/src/compiler.hpp b/src/compiler.hpp index 4a1699b782..47841af5dc 100644 --- a/src/compiler.hpp +++ b/src/compiler.hpp @@ -12,7 +12,6 @@ #include "error.hpp" Error get_compiler_id(Buf **result); -Buf *get_self_libc_path(void); Buf *get_zig_lib_dir(void); Buf *get_zig_special_dir(Buf *zig_lib_dir); diff --git a/src/error.cpp b/src/error.cpp index 730c6e7193..2e92a98217 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -81,6 +81,8 @@ const char *err_str(Error err) { case ErrorWindowsSdkNotFound: return "Windows SDK not found"; case ErrorUnknownDynamicLinkerPath: return "unknown dynamic linker path"; case ErrorTargetHasNoDynamicLinker: return "target has no dynamic linker"; + case ErrorInvalidAbiVersion: return "invalid C ABI version"; + case ErrorInvalidOperatingSystemVersion: return "invalid operating system version"; } return "(invalid error)"; } diff --git a/src/glibc.cpp b/src/glibc.cpp index 849aac6c77..da5c1d5290 100644 --- a/src/glibc.cpp +++ b/src/glibc.cpp @@ -55,7 +55,7 @@ Error glibc_load_metadata(ZigGLibCAbi **out_result, Buf *zig_lib_dir, bool verbo Optional> opt_component = SplitIterator_next(&it); if (!opt_component.is_some) break; Buf *ver_buf = buf_create_from_slice(opt_component.value); - ZigGLibCVersion *this_ver = glibc_abi->all_versions.add_one(); + Stage2SemVer *this_ver = glibc_abi->all_versions.add_one(); if ((err = target_parse_glibc_version(this_ver, buf_ptr(ver_buf)))) { if (verbose) { fprintf(stderr, "Unable to parse glibc version '%s': %s\n", buf_ptr(ver_buf), err_str(err)); @@ -186,9 +186,9 @@ Error glibc_build_dummies_and_maps(CodeGen *g, const ZigGLibCAbi *glibc_abi, con cache_buf(cache_hash, compiler_id); cache_int(cache_hash, target->arch); cache_int(cache_hash, target->abi); - cache_int(cache_hash, target->glibc_version->major); - cache_int(cache_hash, target->glibc_version->minor); - cache_int(cache_hash, target->glibc_version->patch); + cache_int(cache_hash, target->glibc_or_darwin_version->major); + cache_int(cache_hash, target->glibc_or_darwin_version->minor); + cache_int(cache_hash, target->glibc_or_darwin_version->patch); Buf digest = BUF_INIT; buf_resize(&digest, 0); @@ -224,10 +224,10 @@ Error glibc_build_dummies_and_maps(CodeGen *g, const ZigGLibCAbi *glibc_abi, con uint8_t target_ver_index = 0; for (;target_ver_index < glibc_abi->all_versions.length; target_ver_index += 1) { - const ZigGLibCVersion *this_ver = &glibc_abi->all_versions.at(target_ver_index); - if (this_ver->major == target->glibc_version->major && - this_ver->minor == target->glibc_version->minor && - this_ver->patch == target->glibc_version->patch) + const Stage2SemVer *this_ver = &glibc_abi->all_versions.at(target_ver_index); + if (this_ver->major == target->glibc_or_darwin_version->major && + this_ver->minor == target->glibc_or_darwin_version->minor && + this_ver->patch == target->glibc_or_darwin_version->patch) { break; } @@ -235,9 +235,9 @@ Error glibc_build_dummies_and_maps(CodeGen *g, const ZigGLibCAbi *glibc_abi, con if (target_ver_index == glibc_abi->all_versions.length) { if (verbose) { fprintf(stderr, "Unrecognized glibc version: %d.%d.%d\n", - target->glibc_version->major, - target->glibc_version->minor, - target->glibc_version->patch); + target->glibc_or_darwin_version->major, + target->glibc_or_darwin_version->minor, + target->glibc_or_darwin_version->patch); } return ErrorUnknownABI; } @@ -246,7 +246,7 @@ Error glibc_build_dummies_and_maps(CodeGen *g, const ZigGLibCAbi *glibc_abi, con Buf *map_contents = buf_alloc(); for (uint8_t ver_i = 0; ver_i < glibc_abi->all_versions.length; ver_i += 1) { - const ZigGLibCVersion *ver = &glibc_abi->all_versions.at(ver_i); + const Stage2SemVer *ver = &glibc_abi->all_versions.at(ver_i); if (ver->patch == 0) { buf_appendf(map_contents, "GLIBC_%d.%d { };\n", ver->major, ver->minor); } else { @@ -294,7 +294,7 @@ Error glibc_build_dummies_and_maps(CodeGen *g, const ZigGLibCAbi *glibc_abi, con uint8_t ver_index = ver_list->versions[ver_i]; Buf *stub_name; - const ZigGLibCVersion *ver = &glibc_abi->all_versions.at(ver_index); + const Stage2SemVer *ver = &glibc_abi->all_versions.at(ver_index); const char *sym_name = buf_ptr(libc_fn->name); if (ver->patch == 0) { stub_name = buf_sprintf("%s_%d_%d", sym_name, ver->major, ver->minor); @@ -362,43 +362,6 @@ bool eql_glibc_target(const ZigTarget *a, const ZigTarget *b) { a->abi == b->abi; } -#ifdef ZIG_OS_LINUX -#include -Error glibc_detect_native_version(ZigGLibCVersion *glibc_ver) { - Buf *self_libc_path = get_self_libc_path(); - if (self_libc_path == nullptr) { - // TODO There is still more we could do to detect the native glibc version. For example, - // we could look at the ELF file of `/usr/bin/env`, find `libc.so.6`, and then `readlink` - // to find out the glibc version. This is relevant for the static zig builds distributed - // on the download page, since the above detection based on zig's own dynamic linking - // will not work. - - return ErrorUnknownABI; - } - Buf *link_name = buf_alloc(); - buf_resize(link_name, 4096); - ssize_t amt = readlink(buf_ptr(self_libc_path), buf_ptr(link_name), buf_len(link_name)); - if (amt == -1) { - return ErrorUnknownABI; - } - buf_resize(link_name, amt); - if (!buf_starts_with_str(link_name, "libc-") || !buf_ends_with_str(link_name, ".so")) { - return ErrorUnknownABI; - } - // example: "libc-2.3.4.so" - // example: "libc-2.27.so" - buf_resize(link_name, buf_len(link_name) - 3); // chop off ".so" - glibc_ver->major = 2; - glibc_ver->minor = 0; - glibc_ver->patch = 0; - return target_parse_glibc_version(glibc_ver, buf_ptr(link_name) + 5); -} -#else -Error glibc_detect_native_version(ZigGLibCVersion *glibc_ver) { - return ErrorUnknownABI; -} -#endif - size_t glibc_lib_count(void) { return array_length(glibc_libs); } diff --git a/src/glibc.hpp b/src/glibc.hpp index 42c2099371..c04dcb4629 100644 --- a/src/glibc.hpp +++ b/src/glibc.hpp @@ -32,7 +32,7 @@ struct ZigGLibCAbi { Buf *abi_txt_path; Buf *vers_txt_path; Buf *fns_txt_path; - ZigList all_versions; + ZigList all_versions; ZigList all_functions; // The value is a pointer to all_functions.length items and each item is an index // into all_functions. @@ -43,9 +43,6 @@ Error glibc_load_metadata(ZigGLibCAbi **out_result, Buf *zig_lib_dir, bool verbo Error glibc_build_dummies_and_maps(CodeGen *codegen, const ZigGLibCAbi *glibc_abi, const ZigTarget *target, Buf **out_dir, bool verbose, Stage2ProgressNode *progress_node); -// returns ErrorUnknownABI when glibc is not the native libc -Error glibc_detect_native_version(ZigGLibCVersion *glibc_ver); - size_t glibc_lib_count(void); const ZigGLibCLib *glibc_lib_enum(size_t index); const ZigGLibCLib *glibc_lib_find(const char *name); diff --git a/src/ir.cpp b/src/ir.cpp index 0e276a8cc7..a2cc68b3ca 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -12555,13 +12555,22 @@ static IrInstGen *ir_resolve_ptr_of_array_to_slice(IrAnalyze *ira, IrInst* sourc { Error err; - if ((err = type_resolve(ira->codegen, array_ptr->value->type->data.pointer.child_type, - ResolveStatusAlignmentKnown))) - { + assert(array_ptr->value->type->id == ZigTypeIdPointer); + + if ((err = type_resolve(ira->codegen, array_ptr->value->type, ResolveStatusAlignmentKnown))) { return ira->codegen->invalid_inst_gen; } - wanted_type = adjust_slice_align(ira->codegen, wanted_type, get_ptr_align(ira->codegen, array_ptr->value->type)); + assert(array_ptr->value->type->data.pointer.child_type->id == ZigTypeIdArray); + + const size_t array_len = array_ptr->value->type->data.pointer.child_type->data.array.len; + + // A zero-sized array can always be casted irregardless of the destination + // alignment + if (array_len != 0) { + wanted_type = adjust_slice_align(ira->codegen, wanted_type, + get_ptr_align(ira->codegen, array_ptr->value->type)); + } if (instr_is_comptime(array_ptr)) { ZigValue *array_ptr_val = ir_resolve_const(ira, array_ptr, UndefBad); @@ -14833,19 +14842,19 @@ static IrInstGen *ir_analyze_cast(IrAnalyze *ira, IrInst *source_instr, // cast from inferred struct type to array, union, or struct if (is_anon_container(actual_type)) { - AstNode *decl_node = actual_type->data.structure.decl_node; - ir_assert(decl_node->type == NodeTypeContainerInitExpr, source_instr); - ContainerInitKind init_kind = decl_node->data.container_init_expr.kind; - uint32_t field_count = actual_type->data.structure.src_field_count; - if (wanted_type->id == ZigTypeIdArray && (init_kind == ContainerInitKindArray || field_count == 0) && + const bool is_array_init = + actual_type->data.structure.special == StructSpecialInferredTuple; + const uint32_t field_count = actual_type->data.structure.src_field_count; + + if (wanted_type->id == ZigTypeIdArray && (is_array_init || field_count == 0) && wanted_type->data.array.len == field_count) { return ir_analyze_struct_literal_to_array(ira, source_instr, value, wanted_type); } else if (wanted_type->id == ZigTypeIdStruct && - (init_kind == ContainerInitKindStruct || field_count == 0)) + (!is_array_init || field_count == 0)) { return ir_analyze_struct_literal_to_struct(ira, source_instr, value, wanted_type); - } else if (wanted_type->id == ZigTypeIdUnion && init_kind == ContainerInitKindStruct && field_count == 1) { + } else if (wanted_type->id == ZigTypeIdUnion && !is_array_init && field_count == 1) { return ir_analyze_struct_literal_to_union(ira, source_instr, value, wanted_type); } } @@ -17799,6 +17808,7 @@ static IrInstGen *ir_analyze_instruction_export(IrAnalyze *ira, IrInstSrcExport } } break; case ZigTypeIdInt: + want_var_export = true; break; case ZigTypeIdVoid: case ZigTypeIdBool: @@ -20369,6 +20379,17 @@ static ZigType *adjust_ptr_len(CodeGen *g, ZigType *ptr_type, PtrLen ptr_len) { ptr_type->data.pointer.allow_zero); } +static ZigType *adjust_ptr_allow_zero(CodeGen *g, ZigType *ptr_type, bool allow_zero) { + assert(ptr_type->id == ZigTypeIdPointer); + return get_pointer_to_type_extra(g, + ptr_type->data.pointer.child_type, + ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile, + ptr_type->data.pointer.ptr_len, + ptr_type->data.pointer.explicit_alignment, + ptr_type->data.pointer.bit_offset_in_host, ptr_type->data.pointer.host_int_bytes, + allow_zero); +} + static IrInstGen *ir_analyze_instruction_elem_ptr(IrAnalyze *ira, IrInstSrcElemPtr *elem_ptr_instruction) { Error err; IrInstGen *array_ptr = elem_ptr_instruction->array_ptr->child; @@ -23148,12 +23169,15 @@ static IrInstGen *ir_analyze_instruction_enum_tag_name(IrAnalyze *ira, IrInstSrc if (instr_is_comptime(target)) { if ((err = type_resolve(ira->codegen, target->value->type, ResolveStatusZeroBitsKnown))) return ira->codegen->invalid_inst_gen; - if (target->value->type->data.enumeration.non_exhaustive) { - ir_add_error(ira, &instruction->base.base, - buf_sprintf("TODO @tagName on non-exhaustive enum https://github.com/ziglang/zig/issues/3991")); + TypeEnumField *field = find_enum_field_by_tag(target->value->type, &target->value->data.x_bigint); + if (field == nullptr) { + Buf *int_buf = buf_alloc(); + bigint_append_buf(int_buf, &target->value->data.x_bigint, 10); + + ir_add_error(ira, &target->base, + buf_sprintf("no tag by value %s", buf_ptr(int_buf))); return ira->codegen->invalid_inst_gen; } - TypeEnumField *field = find_enum_field_by_tag(target->value->type, &target->value->data.x_bigint); ZigValue *array_val = create_const_str_lit(ira->codegen, field->name)->data.x_ptr.data.ref.pointee; IrInstGen *result = ir_const(ira, &instruction->base.base, nullptr); init_const_slice(ira->codegen, result->value, array_val, 0, buf_len(field->name), true); @@ -25920,6 +25944,8 @@ static IrInstGen *ir_analyze_instruction_slice(IrAnalyze *ira, IrInstSrcSlice *i ZigType *non_sentinel_slice_ptr_type; ZigType *elem_type; + bool generate_non_null_assert = false; + if (array_type->id == ZigTypeIdArray) { elem_type = array_type->data.array.child_type; bool is_comptime_const = ptr_ptr->value->special == ConstValSpecialStatic && @@ -25947,6 +25973,14 @@ static IrInstGen *ir_analyze_instruction_slice(IrAnalyze *ira, IrInstSrcSlice *i elem_type = array_type->data.pointer.child_type; if (array_type->data.pointer.ptr_len == PtrLenC) { array_type = adjust_ptr_len(ira->codegen, array_type, PtrLenUnknown); + + // C pointers are allowzero by default. + // However, we want to be able to slice them without generating an allowzero slice (see issue #4401). + // To achieve this, we generate a runtime safety check and make the slice type non-allowzero. + if (array_type->data.pointer.allow_zero) { + array_type = adjust_ptr_allow_zero(ira->codegen, array_type, false); + generate_non_null_assert = true; + } } ZigType *maybe_sentineled_slice_ptr_type = array_type; non_sentinel_slice_ptr_type = adjust_ptr_sentinel(ira->codegen, maybe_sentineled_slice_ptr_type, nullptr); @@ -26218,7 +26252,6 @@ static IrInstGen *ir_analyze_instruction_slice(IrAnalyze *ira, IrInstSrcSlice *i IrInstGen *result_loc = ir_resolve_result(ira, &instruction->base.base, instruction->result_loc, return_type, nullptr, true, true); - if (result_loc != nullptr) { if (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable) { return result_loc; @@ -26231,8 +26264,17 @@ static IrInstGen *ir_analyze_instruction_slice(IrAnalyze *ira, IrInstSrcSlice *i return ira->codegen->invalid_inst_gen; } - return ir_build_slice_gen(ira, &instruction->base.base, return_type, - ptr_ptr, casted_start, end, instruction->safety_check_on, result_loc); + if (generate_non_null_assert) { + IrInstGen *ptr_val = ir_get_deref(ira, &instruction->base.base, ptr_ptr, nullptr); + + if (type_is_invalid(ptr_val->value->type)) + return ira->codegen->invalid_inst_gen; + + ir_build_assert_non_null(ira, &instruction->base.base, ptr_val); + } + + return ir_build_slice_gen(ira, &instruction->base.base, return_type, ptr_ptr, + casted_start, end, instruction->safety_check_on, result_loc); } static IrInstGen *ir_analyze_instruction_has_field(IrAnalyze *ira, IrInstSrcHasField *instruction) { @@ -27818,9 +27860,15 @@ static IrInstGen *ir_analyze_int_to_ptr(IrAnalyze *ira, IrInst* source_instr, Ir } IrInstGen *result = ir_const(ira, source_instr, ptr_type); - result->value->data.x_ptr.special = ConstPtrSpecialHardCodedAddr; - result->value->data.x_ptr.mut = ConstPtrMutRuntimeVar; - result->value->data.x_ptr.data.hard_coded_addr.addr = addr; + if (ptr_type->id == ZigTypeIdOptional && addr == 0) { + result->value->data.x_ptr.special = ConstPtrSpecialNull; + result->value->data.x_ptr.mut = ConstPtrMutComptimeConst; + } else { + result->value->data.x_ptr.special = ConstPtrSpecialHardCodedAddr; + result->value->data.x_ptr.mut = ConstPtrMutRuntimeVar; + result->value->data.x_ptr.data.hard_coded_addr.addr = addr; + } + return result; } @@ -27878,15 +27926,15 @@ static IrInstGen *ir_analyze_instruction_ptr_to_int(IrAnalyze *ira, IrInstSrcPtr ZigType *usize = ira->codegen->builtin_types.entry_usize; - // We check size explicitly so we can use get_src_ptr_type here. - if (get_src_ptr_type(target->value->type) == nullptr) { + ZigType *src_ptr_type = get_src_ptr_type(target->value->type); + if (src_ptr_type == nullptr) { ir_add_error(ira, &target->base, buf_sprintf("expected pointer, found '%s'", buf_ptr(&target->value->type->name))); return ira->codegen->invalid_inst_gen; } bool has_bits; - if ((err = type_has_bits2(ira->codegen, target->value->type, &has_bits))) + if ((err = type_has_bits2(ira->codegen, src_ptr_type, &has_bits))) return ira->codegen->invalid_inst_gen; if (!has_bits) { @@ -27899,11 +27947,19 @@ static IrInstGen *ir_analyze_instruction_ptr_to_int(IrAnalyze *ira, IrInstSrcPtr ZigValue *val = ir_resolve_const(ira, target, UndefBad); if (!val) return ira->codegen->invalid_inst_gen; - if (val->type->id == ZigTypeIdPointer && val->data.x_ptr.special == ConstPtrSpecialHardCodedAddr) { + + // Since we've already run this type trough get_codegen_ptr_type it is + // safe to access the x_ptr fields + if (val->data.x_ptr.special == ConstPtrSpecialHardCodedAddr) { IrInstGen *result = ir_const(ira, &instruction->base.base, usize); bigint_init_unsigned(&result->value->data.x_bigint, val->data.x_ptr.data.hard_coded_addr.addr); result->value->type = usize; return result; + } else if (val->data.x_ptr.special == ConstPtrSpecialNull) { + IrInstGen *result = ir_const(ira, &instruction->base.base, usize); + bigint_init_unsigned(&result->value->data.x_bigint, 0); + result->value->type = usize; + return result; } } diff --git a/src/link.cpp b/src/link.cpp index ded2ac2cce..e6d186cef9 100644 --- a/src/link.cpp +++ b/src/link.cpp @@ -1751,9 +1751,9 @@ static void construct_linker_job_elf(LinkJob *lj) { } if (g->have_dynamic_link && (is_dyn_lib || g->out_type == OutTypeExe)) { - assert(g->dynamic_linker_path != nullptr); + assert(g->zig_target->dynamic_linker != nullptr); lj->args.append("-dynamic-linker"); - lj->args.append(buf_ptr(g->dynamic_linker_path)); + lj->args.append(g->zig_target->dynamic_linker); } } @@ -2376,99 +2376,6 @@ static void construct_linker_job_coff(LinkJob *lj) { } } - -// Parse (([0-9]+)(.([0-9]+)(.([0-9]+)?))?)? and return the -// grouped values as integers. Numbers which are not provided are set to 0. -// return true if the entire string was parsed (9.2), or all groups were -// parsed (10.3.5extrastuff). -static bool darwin_get_release_version(const char *str, int *major, int *minor, int *micro, bool *had_extra) { - *had_extra = false; - - *major = 0; - *minor = 0; - *micro = 0; - - if (*str == '\0') - return false; - - char *end; - *major = (int)strtol(str, &end, 10); - if (*str != '\0' && *end == '\0') - return true; - if (*end != '.') - return false; - - str = end + 1; - *minor = (int)strtol(str, &end, 10); - if (*str != '\0' && *end == '\0') - return true; - if (*end != '.') - return false; - - str = end + 1; - *micro = (int)strtol(str, &end, 10); - if (*str != '\0' && *end == '\0') - return true; - if (str == end) - return false; - *had_extra = true; - return true; -} - -enum DarwinPlatformKind { - MacOS, - IPhoneOS, - IPhoneOSSimulator, -}; - -struct DarwinPlatform { - DarwinPlatformKind kind; - int major; - int minor; - int micro; -}; - -static void get_darwin_platform(LinkJob *lj, DarwinPlatform *platform) { - CodeGen *g = lj->codegen; - - if (g->mmacosx_version_min) { - platform->kind = MacOS; - } else if (g->mios_version_min) { - platform->kind = IPhoneOS; - } else if (g->zig_target->os == OsMacOSX) { - platform->kind = MacOS; - g->mmacosx_version_min = buf_create_from_str("10.14"); - } else { - zig_panic("unable to infer -mmacosx-version-min or -mios-version-min"); - } - - bool had_extra; - if (platform->kind == MacOS) { - if (!darwin_get_release_version(buf_ptr(g->mmacosx_version_min), - &platform->major, &platform->minor, &platform->micro, &had_extra) || - had_extra || platform->major != 10 || platform->minor >= 100 || platform->micro >= 100) - { - zig_panic("invalid -mmacosx-version-min"); - } - } else if (platform->kind == IPhoneOS) { - if (!darwin_get_release_version(buf_ptr(g->mios_version_min), - &platform->major, &platform->minor, &platform->micro, &had_extra) || - had_extra || platform->major >= 10 || platform->minor >= 100 || platform->micro >= 100) - { - zig_panic("invalid -mios-version-min"); - } - } else { - zig_unreachable(); - } - - if (platform->kind == IPhoneOS && - (g->zig_target->arch == ZigLLVM_x86 || - g->zig_target->arch == ZigLLVM_x86_64)) - { - platform->kind = IPhoneOSSimulator; - } -} - static void construct_linker_job_macho(LinkJob *lj) { CodeGen *g = lj->codegen; @@ -2512,25 +2419,25 @@ static void construct_linker_job_macho(LinkJob *lj) { lj->args.append("-arch"); lj->args.append(get_darwin_arch_string(g->zig_target)); - DarwinPlatform platform; - get_darwin_platform(lj, &platform); - switch (platform.kind) { - case MacOS: + if (g->zig_target->glibc_or_darwin_version != nullptr) { + if (g->zig_target->os == OsMacOSX) { lj->args.append("-macosx_version_min"); - break; - case IPhoneOS: - lj->args.append("-iphoneos_version_min"); - break; - case IPhoneOSSimulator: - lj->args.append("-ios_simulator_version_min"); - break; + } else if (g->zig_target->os == OsIOS) { + if (g->zig_target->arch == ZigLLVM_x86 || g->zig_target->arch == ZigLLVM_x86_64) { + lj->args.append("-ios_simulator_version_min"); + } else { + lj->args.append("-iphoneos_version_min"); + } + } + Buf *version_string = buf_sprintf("%d.%d.%d", + g->zig_target->glibc_or_darwin_version->major, + g->zig_target->glibc_or_darwin_version->minor, + g->zig_target->glibc_or_darwin_version->patch); + lj->args.append(buf_ptr(version_string)); + + lj->args.append("-sdk_version"); + lj->args.append(buf_ptr(version_string)); } - Buf *version_string = buf_sprintf("%d.%d.%d", platform.major, platform.minor, platform.micro); - lj->args.append(buf_ptr(version_string)); - - lj->args.append("-sdk_version"); - lj->args.append(buf_ptr(version_string)); - if (g->out_type == OutTypeExe) { lj->args.append("-pie"); diff --git a/src/main.cpp b/src/main.cpp index 299b9cf9fe..469f03a911 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -89,8 +89,7 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) { " --single-threaded source may assume it is only used single-threaded\n" " -dynamic create a shared library (.so; .dll; .dylib)\n" " --strip exclude debug symbols\n" - " -target [name] -- see the targets command\n" - " -target-glibc [version] target a specific glibc version (default: 2.17)\n" + " -target [name] -- see the targets command\n" " --verbose-tokenize enable compiler debug output for tokenization\n" " --verbose-ast enable compiler debug output for AST parsing\n" " --verbose-link enable compiler debug output for linking\n" @@ -128,8 +127,6 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) { " --subsystem [subsystem] (windows) /SUBSYSTEM: to the linker\n" " -F[dir] (darwin) add search path for frameworks\n" " -framework [name] (darwin) link against framework\n" - " -mios-version-min [ver] (darwin) set iOS deployment target\n" - " -mmacosx-version-min [ver] (darwin) set Mac OS X deployment target\n" " --ver-major [ver] dynamic library semver major version\n" " --ver-minor [ver] dynamic library semver minor version\n" " --ver-patch [ver] dynamic library semver patch version\n" @@ -405,7 +402,7 @@ static int main0(int argc, char **argv) { bool link_eh_frame_hdr = false; ErrColor color = ErrColorAuto; CacheOpt enable_cache = CacheOptAuto; - Buf *dynamic_linker = nullptr; + const char *dynamic_linker = nullptr; const char *libc_txt = nullptr; ZigList clang_argv = {0}; ZigList lib_dirs = {0}; @@ -416,11 +413,8 @@ static int main0(int argc, char **argv) { bool have_libc = false; const char *target_string = nullptr; bool rdynamic = false; - const char *mmacosx_version_min = nullptr; - const char *mios_version_min = nullptr; const char *linker_script = nullptr; Buf *version_script = nullptr; - const char *target_glibc = nullptr; ZigList rpath_list = {0}; bool each_lib_rpath = false; ZigList objects = {0}; @@ -503,7 +497,10 @@ static int main0(int argc, char **argv) { os_path_join(get_zig_special_dir(zig_lib_dir), buf_create_from_str("build_runner.zig"), build_runner_path); ZigTarget target; - get_native_target(&target); + if ((err = target_parse_triple(&target, "native", nullptr, nullptr))) { + fprintf(stderr, "Unable to get native target: %s\n", err_str(err)); + return EXIT_FAILURE; + } Buf *build_file_buf = buf_create_from_str((build_file != nullptr) ? build_file : "build.zig"); Buf build_file_abs = os_path_resolve(&build_file_buf, 1); @@ -770,7 +767,7 @@ static int main0(int argc, char **argv) { } else if (strcmp(arg, "--name") == 0) { out_name = argv[i]; } else if (strcmp(arg, "--dynamic-linker") == 0) { - dynamic_linker = buf_create_from_str(argv[i]); + dynamic_linker = argv[i]; } else if (strcmp(arg, "--libc") == 0) { libc_txt = argv[i]; } else if (strcmp(arg, "-D") == 0) { @@ -844,18 +841,12 @@ static int main0(int argc, char **argv) { cache_dir = argv[i]; } else if (strcmp(arg, "-target") == 0) { target_string = argv[i]; - } else if (strcmp(arg, "-mmacosx-version-min") == 0) { - mmacosx_version_min = argv[i]; - } else if (strcmp(arg, "-mios-version-min") == 0) { - mios_version_min = argv[i]; } else if (strcmp(arg, "-framework") == 0) { frameworks.append(argv[i]); } else if (strcmp(arg, "--linker-script") == 0) { linker_script = argv[i]; } else if (strcmp(arg, "--version-script") == 0) { version_script = buf_create_from_str(argv[i]); - } else if (strcmp(arg, "-target-glibc") == 0) { - target_glibc = argv[i]; } else if (strcmp(arg, "-rpath") == 0) { rpath_list.append(argv[i]); } else if (strcmp(arg, "--test-filter") == 0) { @@ -978,34 +969,11 @@ static int main0(int argc, char **argv) { init_all_targets(); ZigTarget target; - if ((err = target_parse_triple(&target, target_string, mcpu))) { + if ((err = target_parse_triple(&target, target_string, mcpu, dynamic_linker))) { fprintf(stderr, "invalid target: %s\n" "See `%s targets` to display valid targets.\n", err_str(err), arg0); return print_error_usage(arg0); } - if (target_is_glibc(&target)) { - target.glibc_version = heap::c_allocator.create(); - - if (target_glibc != nullptr) { - if ((err = target_parse_glibc_version(target.glibc_version, target_glibc))) { - fprintf(stderr, "invalid glibc version '%s': %s\n", target_glibc, err_str(err)); - return print_error_usage(arg0); - } - } else { - target_init_default_glibc_version(&target); -#if defined(ZIG_OS_LINUX) - if (target.is_native) { - // TODO self-host glibc version detection, and then this logic can go away - if ((err = glibc_detect_native_version(target.glibc_version))) { - // Fall back to the default version. - } - } -#endif - } - } else if (target_glibc != nullptr) { - fprintf(stderr, "'%s' is not a glibc-compatible target", target_string); - return print_error_usage(arg0); - } Buf zig_triple_buf = BUF_INIT; target_triple_zig(&zig_triple_buf, &target); @@ -1226,7 +1194,6 @@ static int main0(int argc, char **argv) { codegen_set_strip(g, strip); g->is_dynamic = is_dynamic; - g->dynamic_linker_path = dynamic_linker; g->verbose_tokenize = verbose_tokenize; g->verbose_ast = verbose_ast; g->verbose_link = verbose_link; @@ -1265,18 +1232,6 @@ static int main0(int argc, char **argv) { } codegen_set_rdynamic(g, rdynamic); - if (mmacosx_version_min && mios_version_min) { - fprintf(stderr, "-mmacosx-version-min and -mios-version-min options not allowed together\n"); - return main_exit(root_progress_node, EXIT_FAILURE); - } - - if (mmacosx_version_min) { - codegen_set_mmacosx_version_min(g, buf_create_from_str(mmacosx_version_min)); - } - - if (mios_version_min) { - codegen_set_mios_version_min(g, buf_create_from_str(mios_version_min)); - } if (test_filter) { codegen_set_test_filter(g, buf_create_from_str(test_filter)); @@ -1365,7 +1320,10 @@ static int main0(int argc, char **argv) { return main_exit(root_progress_node, EXIT_SUCCESS); } else if (cmd == CmdTest) { ZigTarget native; - get_native_target(&native); + if ((err = target_parse_triple(&native, "native", nullptr, nullptr))) { + fprintf(stderr, "Unable to get native target: %s\n", err_str(err)); + return EXIT_FAILURE; + } g->enable_cache = get_cache_opt(enable_cache, output_dir == nullptr); codegen_build_and_link(g); diff --git a/src/os.cpp b/src/os.cpp index 8e0bcc433b..f65a578e17 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -1073,8 +1073,8 @@ static Error set_file_times(OsFile file, OsTimeStamp ts) { return ErrorNone; #else struct timespec times[2] = { - { ts.sec, ts.nsec }, - { ts.sec, ts.nsec }, + { (time_t)ts.sec, (time_t)ts.nsec }, + { (time_t)ts.sec, (time_t)ts.nsec }, }; if (futimens(file, times) == -1) { switch (errno) { diff --git a/src/stage2.cpp b/src/stage2.cpp index 736f11622e..1f6cb2d6aa 100644 --- a/src/stage2.cpp +++ b/src/stage2.cpp @@ -91,7 +91,109 @@ void stage2_progress_complete_one(Stage2ProgressNode *node) {} void stage2_progress_disable_tty(Stage2Progress *progress) {} void stage2_progress_update_node(Stage2ProgressNode *node, size_t completed_count, size_t estimated_total_items){} -Error stage2_target_parse(struct ZigTarget *target, const char *zig_triple, const char *mcpu) { +static Os get_zig_os_type(ZigLLVM_OSType os_type) { + switch (os_type) { + case ZigLLVM_UnknownOS: + return OsFreestanding; + case ZigLLVM_Ananas: + return OsAnanas; + case ZigLLVM_CloudABI: + return OsCloudABI; + case ZigLLVM_DragonFly: + return OsDragonFly; + case ZigLLVM_FreeBSD: + return OsFreeBSD; + case ZigLLVM_Fuchsia: + return OsFuchsia; + case ZigLLVM_IOS: + return OsIOS; + case ZigLLVM_KFreeBSD: + return OsKFreeBSD; + case ZigLLVM_Linux: + return OsLinux; + case ZigLLVM_Lv2: + return OsLv2; + case ZigLLVM_Darwin: + case ZigLLVM_MacOSX: + return OsMacOSX; + case ZigLLVM_NetBSD: + return OsNetBSD; + case ZigLLVM_OpenBSD: + return OsOpenBSD; + case ZigLLVM_Solaris: + return OsSolaris; + case ZigLLVM_Win32: + return OsWindows; + case ZigLLVM_Haiku: + return OsHaiku; + case ZigLLVM_Minix: + return OsMinix; + case ZigLLVM_RTEMS: + return OsRTEMS; + case ZigLLVM_NaCl: + return OsNaCl; + case ZigLLVM_CNK: + return OsCNK; + case ZigLLVM_AIX: + return OsAIX; + case ZigLLVM_CUDA: + return OsCUDA; + case ZigLLVM_NVCL: + return OsNVCL; + case ZigLLVM_AMDHSA: + return OsAMDHSA; + case ZigLLVM_PS4: + return OsPS4; + case ZigLLVM_ELFIAMCU: + return OsELFIAMCU; + case ZigLLVM_TvOS: + return OsTvOS; + case ZigLLVM_WatchOS: + return OsWatchOS; + case ZigLLVM_Mesa3D: + return OsMesa3D; + case ZigLLVM_Contiki: + return OsContiki; + case ZigLLVM_AMDPAL: + return OsAMDPAL; + case ZigLLVM_HermitCore: + return OsHermitCore; + case ZigLLVM_Hurd: + return OsHurd; + case ZigLLVM_WASI: + return OsWASI; + case ZigLLVM_Emscripten: + return OsEmscripten; + } + zig_unreachable(); +} + +static void get_native_target(ZigTarget *target) { + // first zero initialize + *target = {}; + + ZigLLVM_OSType os_type; + ZigLLVM_ObjectFormatType oformat; // ignored; based on arch/os + ZigLLVMGetNativeTarget( + &target->arch, + &target->vendor, + &os_type, + &target->abi, + &oformat); + target->os = get_zig_os_type(os_type); + target->is_native = true; + if (target->abi == ZigLLVM_UnknownEnvironment) { + target->abi = target_default_abi(target->arch, target->os); + } + if (target_is_glibc(target)) { + target->glibc_or_darwin_version = heap::c_allocator.create(); + target_init_default_glibc_version(target); + } +} + +Error stage2_target_parse(struct ZigTarget *target, const char *zig_triple, const char *mcpu, + const char *dynamic_linker) +{ Error err; if (zig_triple == nullptr) { @@ -100,13 +202,11 @@ Error stage2_target_parse(struct ZigTarget *target, const char *zig_triple, cons if (mcpu == nullptr) { target->llvm_cpu_name = ZigLLVMGetHostCPUName(); target->llvm_cpu_features = ZigLLVMGetNativeFeatures(); - target->builtin_str = "Target.Cpu.baseline(arch);\n"; target->cache_hash = "native\n\n"; } else if (strcmp(mcpu, "baseline") == 0) { target->is_native = false; target->llvm_cpu_name = ""; target->llvm_cpu_features = ""; - target->builtin_str = "Target.Cpu.baseline(arch);\n"; target->cache_hash = "baseline\n\n"; } else { const char *msg = "stage0 can't handle CPU/features in the target"; @@ -148,10 +248,12 @@ Error stage2_target_parse(struct ZigTarget *target, const char *zig_triple, cons const char *msg = "stage0 can't handle CPU/features in the target"; stage2_panic(msg, strlen(msg)); } - target->builtin_str = "Target.Cpu.baseline(arch);\n"; target->cache_hash = "\n\n"; } + if (dynamic_linker != nullptr) { + target->dynamic_linker = dynamic_linker; + } return ErrorNone; } @@ -186,11 +288,6 @@ enum Error stage2_libc_find_native(struct Stage2LibCInstallation *libc) { stage2_panic(msg, strlen(msg)); } -enum Error stage2_detect_dynamic_linker(const struct ZigTarget *target, char **out_ptr, size_t *out_len) { - const char *msg = "stage0 called stage2_detect_dynamic_linker"; - stage2_panic(msg, strlen(msg)); -} - enum Error stage2_detect_native_paths(struct Stage2NativePaths *native_paths) { native_paths->include_dirs_ptr = nullptr; native_paths->include_dirs_len = 0; diff --git a/src/stage2.h b/src/stage2.h index e6db241e88..b73269f4e8 100644 --- a/src/stage2.h +++ b/src/stage2.h @@ -103,6 +103,8 @@ enum Error { ErrorWindowsSdkNotFound, ErrorUnknownDynamicLinkerPath, ErrorTargetHasNoDynamicLinker, + ErrorInvalidAbiVersion, + ErrorInvalidOperatingSystemVersion, }; // ABI warning @@ -268,14 +270,12 @@ enum Os { }; // ABI warning -struct ZigGLibCVersion { - uint32_t major; // always 2 +struct Stage2SemVer { + uint32_t major; uint32_t minor; uint32_t patch; }; -struct Stage2TargetData; - // ABI warning struct ZigTarget { enum ZigLLVM_ArchType arch; @@ -286,20 +286,20 @@ struct ZigTarget { bool is_native; - struct ZigGLibCVersion *glibc_version; // null means default + // null means default. this is double-purposed to be darwin min version + struct Stage2SemVer *glibc_or_darwin_version; const char *llvm_cpu_name; const char *llvm_cpu_features; - const char *builtin_str; + const char *cpu_builtin_str; const char *cache_hash; + const char *os_builtin_str; + const char *dynamic_linker; }; // ABI warning -ZIG_EXTERN_C enum Error stage2_detect_dynamic_linker(const struct ZigTarget *target, - char **out_ptr, size_t *out_len); - -// ABI warning -ZIG_EXTERN_C enum Error stage2_target_parse(struct ZigTarget *target, const char *zig_triple, const char *mcpu); +ZIG_EXTERN_C enum Error stage2_target_parse(struct ZigTarget *target, const char *zig_triple, const char *mcpu, + const char *dynamic_linker); // ABI warning diff --git a/src/target.cpp b/src/target.cpp index 161b8330d6..db1e4be809 100644 --- a/src/target.cpp +++ b/src/target.cpp @@ -286,83 +286,6 @@ ZigLLVM_OSType get_llvm_os_type(Os os_type) { zig_unreachable(); } -static Os get_zig_os_type(ZigLLVM_OSType os_type) { - switch (os_type) { - case ZigLLVM_UnknownOS: - return OsFreestanding; - case ZigLLVM_Ananas: - return OsAnanas; - case ZigLLVM_CloudABI: - return OsCloudABI; - case ZigLLVM_DragonFly: - return OsDragonFly; - case ZigLLVM_FreeBSD: - return OsFreeBSD; - case ZigLLVM_Fuchsia: - return OsFuchsia; - case ZigLLVM_IOS: - return OsIOS; - case ZigLLVM_KFreeBSD: - return OsKFreeBSD; - case ZigLLVM_Linux: - return OsLinux; - case ZigLLVM_Lv2: - return OsLv2; - case ZigLLVM_Darwin: - case ZigLLVM_MacOSX: - return OsMacOSX; - case ZigLLVM_NetBSD: - return OsNetBSD; - case ZigLLVM_OpenBSD: - return OsOpenBSD; - case ZigLLVM_Solaris: - return OsSolaris; - case ZigLLVM_Win32: - return OsWindows; - case ZigLLVM_Haiku: - return OsHaiku; - case ZigLLVM_Minix: - return OsMinix; - case ZigLLVM_RTEMS: - return OsRTEMS; - case ZigLLVM_NaCl: - return OsNaCl; - case ZigLLVM_CNK: - return OsCNK; - case ZigLLVM_AIX: - return OsAIX; - case ZigLLVM_CUDA: - return OsCUDA; - case ZigLLVM_NVCL: - return OsNVCL; - case ZigLLVM_AMDHSA: - return OsAMDHSA; - case ZigLLVM_PS4: - return OsPS4; - case ZigLLVM_ELFIAMCU: - return OsELFIAMCU; - case ZigLLVM_TvOS: - return OsTvOS; - case ZigLLVM_WatchOS: - return OsWatchOS; - case ZigLLVM_Mesa3D: - return OsMesa3D; - case ZigLLVM_Contiki: - return OsContiki; - case ZigLLVM_AMDPAL: - return OsAMDPAL; - case ZigLLVM_HermitCore: - return OsHermitCore; - case ZigLLVM_Hurd: - return OsHurd; - case ZigLLVM_WASI: - return OsWASI; - case ZigLLVM_Emscripten: - return OsEmscripten; - } - zig_unreachable(); -} - const char *target_os_name(Os os_type) { switch (os_type) { case OsFreestanding: @@ -423,7 +346,7 @@ const char *target_abi_name(ZigLLVM_EnvironmentType abi) { return ZigLLVMGetEnvironmentTypeName(abi); } -Error target_parse_glibc_version(ZigGLibCVersion *glibc_ver, const char *text) { +Error target_parse_glibc_version(Stage2SemVer *glibc_ver, const char *text) { glibc_ver->major = 2; glibc_ver->minor = 0; glibc_ver->patch = 0; @@ -446,31 +369,8 @@ Error target_parse_glibc_version(ZigGLibCVersion *glibc_ver, const char *text) { return ErrorNone; } -void get_native_target(ZigTarget *target) { - // first zero initialize - *target = {}; - - ZigLLVM_OSType os_type; - ZigLLVM_ObjectFormatType oformat; // ignored; based on arch/os - ZigLLVMGetNativeTarget( - &target->arch, - &target->vendor, - &os_type, - &target->abi, - &oformat); - target->os = get_zig_os_type(os_type); - target->is_native = true; - if (target->abi == ZigLLVM_UnknownEnvironment) { - target->abi = target_default_abi(target->arch, target->os); - } - if (target_is_glibc(target)) { - target->glibc_version = heap::c_allocator.create(); - target_init_default_glibc_version(target); - } -} - void target_init_default_glibc_version(ZigTarget *target) { - *target->glibc_version = {2, 17, 0}; + *target->glibc_or_darwin_version = {2, 17, 0}; } Error target_parse_arch(ZigLLVM_ArchType *out_arch, const char *arch_ptr, size_t arch_len) { @@ -509,8 +409,8 @@ Error target_parse_abi(ZigLLVM_EnvironmentType *out_abi, const char *abi_ptr, si return ErrorUnknownABI; } -Error target_parse_triple(ZigTarget *target, const char *triple, const char *mcpu) { - return stage2_target_parse(target, triple, mcpu); +Error target_parse_triple(ZigTarget *target, const char *triple, const char *mcpu, const char *dynamic_linker) { + return stage2_target_parse(target, triple, mcpu, dynamic_linker); } const char *target_arch_name(ZigLLVM_ArchType arch) { diff --git a/src/target.hpp b/src/target.hpp index 9396eb2623..01f2c6b168 100644 --- a/src/target.hpp +++ b/src/target.hpp @@ -41,12 +41,12 @@ enum CIntType { CIntTypeCount, }; -Error target_parse_triple(ZigTarget *target, const char *triple, const char *mcpu); +Error target_parse_triple(ZigTarget *target, const char *triple, const char *mcpu, const char *dynamic_linker); Error target_parse_arch(ZigLLVM_ArchType *arch, const char *arch_ptr, size_t arch_len); Error target_parse_os(Os *os, const char *os_ptr, size_t os_len); Error target_parse_abi(ZigLLVM_EnvironmentType *abi, const char *abi_ptr, size_t abi_len); -Error target_parse_glibc_version(ZigGLibCVersion *out, const char *text); +Error target_parse_glibc_version(Stage2SemVer *out, const char *text); void target_init_default_glibc_version(ZigTarget *target); size_t target_arch_count(void); @@ -73,7 +73,6 @@ ZigLLVM_ObjectFormatType target_oformat_enum(size_t index); const char *target_oformat_name(ZigLLVM_ObjectFormatType oformat); ZigLLVM_ObjectFormatType target_object_format(const ZigTarget *target); -void get_native_target(ZigTarget *target); void target_triple_llvm(Buf *triple, const ZigTarget *target); void target_triple_zig(Buf *triple, const ZigTarget *target); diff --git a/test/assemble_and_link.zig b/test/assemble_and_link.zig index 8c727e87b5..86209bd034 100644 --- a/test/assemble_and_link.zig +++ b/test/assemble_and_link.zig @@ -1,8 +1,8 @@ -const builtin = @import("builtin"); +const std = @import("std"); const tests = @import("tests.zig"); pub fn addCases(cases: *tests.CompareOutputContext) void { - if (builtin.os == builtin.Os.linux and builtin.arch == builtin.Arch.x86_64) { + if (std.Target.current.os.tag == .linux and std.Target.current.cpu.arch == .x86_64) { cases.addAsm("hello world linux x86_64", \\.text \\.globl _start diff --git a/test/cli.zig b/test/cli.zig index b7d03d9e21..117c714a29 100644 --- a/test/cli.zig +++ b/test/cli.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const builtin = @import("builtin"); const testing = std.testing; const process = std.process; const fs = std.fs; @@ -93,11 +92,11 @@ fn testZigInitLib(zig_exe: []const u8, dir_path: []const u8) !void { fn testZigInitExe(zig_exe: []const u8, dir_path: []const u8) !void { _ = try exec(dir_path, &[_][]const u8{ zig_exe, "init-exe" }); const run_result = try exec(dir_path, &[_][]const u8{ zig_exe, "build", "run" }); - testing.expect(std.mem.eql(u8, run_result.stderr, "All your base are belong to us.\n")); + testing.expect(std.mem.eql(u8, run_result.stderr, "All your codebase are belong to us.\n")); } fn testGodboltApi(zig_exe: []const u8, dir_path: []const u8) anyerror!void { - if (builtin.os != .linux or builtin.arch != .x86_64) return; + if (std.Target.current.os.tag != .linux or std.Target.current.cpu.arch != .x86_64) return; const example_zig_path = try fs.path.join(a, &[_][]const u8{ dir_path, "example.zig" }); const example_s_path = try fs.path.join(a, &[_][]const u8{ dir_path, "example.s" }); diff --git a/test/compare_output.zig b/test/compare_output.zig index 7a41d46f54..ec89af35f8 100644 --- a/test/compare_output.zig +++ b/test/compare_output.zig @@ -1,4 +1,3 @@ -const builtin = @import("builtin"); const std = @import("std"); const os = std.os; const tests = @import("tests.zig"); @@ -131,8 +130,8 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { , "Hello, world!\n 12 12 a\n"); cases.addC("number literals", - \\const builtin = @import("builtin"); - \\const is_windows = builtin.os == builtin.Os.windows; + \\const std = @import("std"); + \\const is_windows = std.Target.current.os.tag == .windows; \\const c = @cImport({ \\ if (is_windows) { \\ // See https://github.com/ziglang/zig/issues/515 @@ -306,8 +305,8 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { , ""); cases.addC("casting between float and integer types", - \\const builtin = @import("builtin"); - \\const is_windows = builtin.os == builtin.Os.windows; + \\const std = @import("std"); + \\const is_windows = std.Target.current.os.tag == .windows; \\const c = @cImport({ \\ if (is_windows) { \\ // See https://github.com/ziglang/zig/issues/515 diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 56df006e82..979bf45bbe 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1,8 +1,34 @@ const tests = @import("tests.zig"); -const builtin = @import("builtin"); -const Target = @import("std").Target; +const std = @import("std"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.addTest("type mismatch with tuple concatenation", + \\export fn entry() void { + \\ var x = .{}; + \\ x = x ++ .{ 1, 2, 3 }; + \\} + , &[_][]const u8{ + "tmp.zig:3:11: error: expected type 'struct:2:14', found 'struct:3:11'", + }); + + cases.addTest("@tagName on invalid value of non-exhaustive enum", + \\test "enum" { + \\ const E = enum(u8) {A, B, _}; + \\ _ = @tagName(@intToEnum(E, 5)); + \\} + , &[_][]const u8{ + "tmp.zig:3:18: error: no tag by value 5", + }); + + cases.addTest("@ptrToInt with pointer to zero-sized type", + \\export fn entry() void { + \\ var pointer: ?*u0 = null; + \\ var x = @ptrToInt(pointer); + \\} + , &[_][]const u8{ + "tmp.zig:3:23: error: pointer to size 0 type has no address", + }); + cases.addTest("slice to pointer conversion mismatch", \\pub fn bytesAsSlice(bytes: var) [*]align(1) const u16 { \\ return @ptrCast([*]align(1) const u16, bytes.ptr)[0..1]; @@ -360,12 +386,10 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { , &[_][]const u8{ "tmp.zig:3:5: error: target arch 'wasm32' does not support calling with a new stack", }); - tc.target = Target{ - .Cross = .{ - .cpu = Target.Cpu.baseline(.wasm32), - .os = .wasi, - .abi = .none, - }, + tc.target = std.zig.CrossTarget{ + .cpu_arch = .wasm32, + .os_tag = .wasi, + .abi = .none, }; break :x tc; }); @@ -761,12 +785,10 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { , &[_][]const u8{ "tmp.zig:2:14: error: could not find 'foo' in the inputs or outputs", }); - tc.target = Target{ - .Cross = .{ - .cpu = Target.Cpu.baseline(.x86_64), - .os = .linux, - .abi = .gnu, - }, + tc.target = std.zig.CrossTarget{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .gnu, }; break :x tc; }); @@ -1426,7 +1448,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { "tmp.zig:2:18: error: invalid operands to binary expression: 'error{A}' and 'error{B}'", }); - if (builtin.os == builtin.Os.linux) { + if (std.Target.current.os.tag == .linux) { cases.addTest("implicit dependency on libc", \\extern "c" fn exit(u8) void; \\export fn entry() void { @@ -2716,16 +2738,6 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { "tmp.zig:5:5: error: else prong required when switching on type 'anyerror'", }); - cases.add("inferred error set with no returned error", - \\export fn entry() void { - \\ foo() catch unreachable; - \\} - \\fn foo() !void { - \\} - , &[_][]const u8{ - "tmp.zig:4:11: error: function with inferred error set must return at least one possible error", - }); - cases.add("error not handled in switch", \\export fn entry() void { \\ foo(452) catch |err| switch (err) { diff --git a/test/runtime_safety.zig b/test/runtime_safety.zig index e4ea7f52db..e183c6979f 100644 --- a/test/runtime_safety.zig +++ b/test/runtime_safety.zig @@ -745,4 +745,17 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ (await p) catch unreachable; \\} ); + + // Slicing a C pointer returns a non-allowzero slice, thus we need to emit + // a safety check to ensure the pointer is not null. + cases.addRuntimeSafety("slicing null C pointer", + \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { + \\ @import("std").os.exit(126); + \\} + \\ + \\pub fn main() void { + \\ var ptr: [*c]const u32 = null; + \\ var slice = ptr[0..3]; + \\} + ); } diff --git a/test/src/translate_c.zig b/test/src/translate_c.zig index 21aa92537c..9a6bd0d323 100644 --- a/test/src/translate_c.zig +++ b/test/src/translate_c.zig @@ -7,6 +7,7 @@ const fmt = std.fmt; const mem = std.mem; const fs = std.fs; const warn = std.debug.warn; +const CrossTarget = std.zig.CrossTarget; pub const TranslateCContext = struct { b: *build.Builder, @@ -19,7 +20,7 @@ pub const TranslateCContext = struct { sources: ArrayList(SourceFile), expected_lines: ArrayList([]const u8), allow_warnings: bool, - target: std.Target = .Native, + target: CrossTarget = CrossTarget{}, const SourceFile = struct { filename: []const u8, @@ -75,7 +76,7 @@ pub const TranslateCContext = struct { pub fn addWithTarget( self: *TranslateCContext, name: []const u8, - target: std.Target, + target: CrossTarget, source: []const u8, expected_lines: []const []const u8, ) void { diff --git a/test/stack_traces.zig b/test/stack_traces.zig index 80a5f0a602..d00f18763b 100644 --- a/test/stack_traces.zig +++ b/test/stack_traces.zig @@ -1,8 +1,8 @@ -const builtin = @import("builtin"); const std = @import("std"); const os = std.os; const tests = @import("tests.zig"); +// zig fmt: off pub fn addCases(cases: *tests.StackTracesContext) void { const source_return = \\const std = @import("std"); @@ -41,6 +41,7 @@ pub fn addCases(cases: *tests.StackTracesContext) void { \\ try foo(); \\} ; + const source_dumpCurrentStackTrace = \\const std = @import("std"); \\ @@ -56,8 +57,7 @@ pub fn addCases(cases: *tests.StackTracesContext) void { \\} ; - // zig fmt: off - switch (builtin.os) { + switch (std.Target.current.os.tag) { .freebsd => { cases.addCase( "return", @@ -310,15 +310,15 @@ pub fn addCases(cases: *tests.StackTracesContext) void { source_return, [_][]const u8{ // debug - \\error: TheSkyIsFalling - \\source.zig:4:5: [address] in _main.0 (test.o) + \\error: TheSkyIsFalling + \\source.zig:4:5: [address] in main (test) \\ return error.TheSkyIsFalling; \\ ^ \\ , // release-safe - \\error: TheSkyIsFalling - \\source.zig:4:5: [address] in _main (test.o) + \\error: TheSkyIsFalling + \\source.zig:4:5: [address] in std.start.main (test) \\ return error.TheSkyIsFalling; \\ ^ \\ @@ -337,21 +337,21 @@ pub fn addCases(cases: *tests.StackTracesContext) void { source_try_return, [_][]const u8{ // debug - \\error: TheSkyIsFalling - \\source.zig:4:5: [address] in _foo (test.o) + \\error: TheSkyIsFalling + \\source.zig:4:5: [address] in foo (test) \\ return error.TheSkyIsFalling; \\ ^ - \\source.zig:8:5: [address] in _main.0 (test.o) + \\source.zig:8:5: [address] in main (test) \\ try foo(); \\ ^ \\ , // release-safe - \\error: TheSkyIsFalling - \\source.zig:4:5: [address] in _main (test.o) + \\error: TheSkyIsFalling + \\source.zig:4:5: [address] in std.start.main (test) \\ return error.TheSkyIsFalling; \\ ^ - \\source.zig:8:5: [address] in _main (test.o) + \\source.zig:8:5: [address] in std.start.main (test) \\ try foo(); \\ ^ \\ @@ -370,33 +370,33 @@ pub fn addCases(cases: *tests.StackTracesContext) void { source_try_try_return_return, [_][]const u8{ // debug - \\error: TheSkyIsFalling - \\source.zig:12:5: [address] in _make_error (test.o) + \\error: TheSkyIsFalling + \\source.zig:12:5: [address] in make_error (test) \\ return error.TheSkyIsFalling; \\ ^ - \\source.zig:8:5: [address] in _bar (test.o) + \\source.zig:8:5: [address] in bar (test) \\ return make_error(); \\ ^ - \\source.zig:4:5: [address] in _foo (test.o) + \\source.zig:4:5: [address] in foo (test) \\ try bar(); \\ ^ - \\source.zig:16:5: [address] in _main.0 (test.o) + \\source.zig:16:5: [address] in main (test) \\ try foo(); \\ ^ \\ , // release-safe - \\error: TheSkyIsFalling - \\source.zig:12:5: [address] in _main (test.o) + \\error: TheSkyIsFalling + \\source.zig:12:5: [address] in std.start.main (test) \\ return error.TheSkyIsFalling; \\ ^ - \\source.zig:8:5: [address] in _main (test.o) + \\source.zig:8:5: [address] in std.start.main (test) \\ return make_error(); \\ ^ - \\source.zig:4:5: [address] in _main (test.o) + \\source.zig:4:5: [address] in std.start.main (test) \\ try bar(); \\ ^ - \\source.zig:16:5: [address] in _main (test.o) + \\source.zig:16:5: [address] in std.start.main (test) \\ try foo(); \\ ^ \\ @@ -440,7 +440,7 @@ pub fn addCases(cases: *tests.StackTracesContext) void { source_try_return, [_][]const u8{ // debug - \\error: TheSkyIsFalling + \\error: TheSkyIsFalling \\source.zig:4:5: [address] in foo (test.obj) \\ return error.TheSkyIsFalling; \\ ^ @@ -466,7 +466,7 @@ pub fn addCases(cases: *tests.StackTracesContext) void { source_try_try_return_return, [_][]const u8{ // debug - \\error: TheSkyIsFalling + \\error: TheSkyIsFalling \\source.zig:12:5: [address] in make_error (test.obj) \\ return error.TheSkyIsFalling; \\ ^ @@ -496,5 +496,5 @@ pub fn addCases(cases: *tests.StackTracesContext) void { }, else => {}, } - // zig fmt: off } +// zig fmt: off diff --git a/test/stage1/behavior.zig b/test/stage1/behavior.zig index a3341c44a9..dfc4321500 100644 --- a/test/stage1/behavior.zig +++ b/test/stage1/behavior.zig @@ -40,6 +40,7 @@ comptime { _ = @import("behavior/bugs/3384.zig"); _ = @import("behavior/bugs/3586.zig"); _ = @import("behavior/bugs/3742.zig"); + _ = @import("behavior/bugs/4560.zig"); _ = @import("behavior/bugs/394.zig"); _ = @import("behavior/bugs/421.zig"); _ = @import("behavior/bugs/529.zig"); diff --git a/test/stage1/behavior/asm.zig b/test/stage1/behavior/asm.zig index d1404c6f45..170ad3325d 100644 --- a/test/stage1/behavior/asm.zig +++ b/test/stage1/behavior/asm.zig @@ -1,9 +1,10 @@ const std = @import("std"); -const config = @import("builtin"); const expect = std.testing.expect; +const is_x86_64_linux = std.Target.current.cpu.arch == .x86_64 and std.Target.current.os.tag == .linux; + comptime { - if (config.arch == config.Arch.x86_64 and config.os == config.Os.linux) { + if (is_x86_64_linux) { asm ( \\.globl this_is_my_alias; \\.type this_is_my_alias, @function; @@ -13,7 +14,7 @@ comptime { } test "module level assembly" { - if (config.arch == config.Arch.x86_64 and config.os == config.Os.linux) { + if (is_x86_64_linux) { expect(this_is_my_alias() == 1234); } } diff --git a/test/stage1/behavior/bugs/4560.zig b/test/stage1/behavior/bugs/4560.zig new file mode 100644 index 0000000000..6821527894 --- /dev/null +++ b/test/stage1/behavior/bugs/4560.zig @@ -0,0 +1,32 @@ +const std = @import("std"); + +test "fixed" { + var s: S = .{ + .a = 1, + .b = .{ + .size = 123, + .max_distance_from_start_index = 456, + }, + }; + std.testing.expect(s.a == 1); + std.testing.expect(s.b.size == 123); + std.testing.expect(s.b.max_distance_from_start_index == 456); +} + +const S = struct { + a: u32, + b: Map, + + const Map = StringHashMap(*S); +}; + +pub fn StringHashMap(comptime V: type) type { + return HashMap([]const u8, V); +} + +pub fn HashMap(comptime K: type, comptime V: type) type { + return struct { + size: usize, + max_distance_from_start_index: usize, + }; +} diff --git a/test/stage1/behavior/byteswap.zig b/test/stage1/behavior/byteswap.zig index c799ba4849..d6e07e7a56 100644 --- a/test/stage1/behavior/byteswap.zig +++ b/test/stage1/behavior/byteswap.zig @@ -1,6 +1,5 @@ const std = @import("std"); const expect = std.testing.expect; -const builtin = @import("builtin"); test "@byteSwap integers" { const ByteSwapIntTest = struct { @@ -41,10 +40,10 @@ test "@byteSwap integers" { test "@byteSwap vectors" { // https://github.com/ziglang/zig/issues/3563 - if (builtin.os == .dragonfly) return error.SkipZigTest; + if (std.Target.current.os.tag == .dragonfly) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/3317 - if (builtin.arch == .mipsel) return error.SkipZigTest; + if (std.Target.current.cpu.arch == .mipsel) return error.SkipZigTest; const ByteSwapVectorTest = struct { fn run() void { diff --git a/test/stage1/behavior/cast.zig b/test/stage1/behavior/cast.zig index 1a74eb341a..080f50cc03 100644 --- a/test/stage1/behavior/cast.zig +++ b/test/stage1/behavior/cast.zig @@ -487,6 +487,17 @@ test "@intToEnum passed a comptime_int to an enum with one item" { expect(x == E.A); } +test "@intToEnum runtime to an extern enum with duplicate values" { + const E = extern enum(u8) { + A = 1, + B = 1, + }; + var a: u8 = 1; + var x = @intToEnum(E, a); + expect(x == E.A); + expect(x == E.B); +} + test "@intCast to u0 and use the result" { const S = struct { fn doTheTest(zero: u1, one: u1, bigzero: i32) void { diff --git a/test/stage1/behavior/enum.zig b/test/stage1/behavior/enum.zig index 18489ba24b..b6cb86a363 100644 --- a/test/stage1/behavior/enum.zig +++ b/test/stage1/behavior/enum.zig @@ -198,7 +198,17 @@ test "@tagName" { comptime expect(mem.eql(u8, testEnumTagNameBare(BareNumber.Three), "Three")); } -fn testEnumTagNameBare(n: BareNumber) []const u8 { +test "@tagName extern enum with duplicates" { + expect(mem.eql(u8, testEnumTagNameBare(ExternDuplicates.B), "A")); + comptime expect(mem.eql(u8, testEnumTagNameBare(ExternDuplicates.B), "A")); +} + +test "@tagName non-exhaustive enum" { + expect(mem.eql(u8, testEnumTagNameBare(NonExhaustive.B), "B")); + comptime expect(mem.eql(u8, testEnumTagNameBare(NonExhaustive.B), "B")); +} + +fn testEnumTagNameBare(n: var) []const u8 { return @tagName(n); } @@ -208,6 +218,17 @@ const BareNumber = enum { Three, }; +const ExternDuplicates = extern enum(u8) { + A = 1, + B = 1, +}; + +const NonExhaustive = enum(u8) { + A, + B, + _, +}; + test "enum alignment" { comptime { expect(@alignOf(AlignTestEnum) >= @alignOf([9]u8)); diff --git a/test/stage1/behavior/eval.zig b/test/stage1/behavior/eval.zig index 8e70e97aaa..55ace6198c 100644 --- a/test/stage1/behavior/eval.zig +++ b/test/stage1/behavior/eval.zig @@ -807,3 +807,28 @@ test "return 0 from function that has u0 return type" { } } } + +test "two comptime calls with array default initialized to undefined" { + const S = struct { + const CrossTarget = struct { + dynamic_linker: DynamicLinker = DynamicLinker{}, + + pub fn parse() void { + var result: CrossTarget = .{ }; + result.getCpuArch(); + } + + pub fn getCpuArch(self: CrossTarget) void { } + }; + + const DynamicLinker = struct { + buffer: [255]u8 = undefined, + }; + + }; + + comptime { + S.CrossTarget.parse(); + S.CrossTarget.parse(); + } +} diff --git a/test/stage1/behavior/fn.zig b/test/stage1/behavior/fn.zig index 13859b92ca..c1e5459378 100644 --- a/test/stage1/behavior/fn.zig +++ b/test/stage1/behavior/fn.zig @@ -1,4 +1,7 @@ -const expect = @import("std").testing.expect; +const std = @import("std"); +const testing = std.testing; +const expect = testing.expect; +const expectEqual = testing.expectEqual; test "params" { expect(testParamsAdd(22, 11) == 33); @@ -272,3 +275,12 @@ test "ability to give comptime types and non comptime types to same parameter" { S.doTheTest(); comptime S.doTheTest(); } + +test "function with inferred error set but returning no error" { + const S = struct { + fn foo() !void {} + }; + + const return_ty = @typeInfo(@TypeOf(S.foo)).Fn.return_type.?; + expectEqual(0, @typeInfo(@typeInfo(return_ty).ErrorUnion.error_set).ErrorSet.?.len); +} diff --git a/test/stage1/behavior/math.zig b/test/stage1/behavior/math.zig index ea92449915..a9c93850b6 100644 --- a/test/stage1/behavior/math.zig +++ b/test/stage1/behavior/math.zig @@ -525,7 +525,7 @@ test "comptime_int xor" { } test "f128" { - if (std.Target.current.isWindows()) { + if (std.Target.current.os.tag == .windows) { // TODO https://github.com/ziglang/zig/issues/508 return error.SkipZigTest; } @@ -619,10 +619,6 @@ test "vector integer addition" { } test "NaN comparison" { - if (std.Target.current.isWindows()) { - // TODO https://github.com/ziglang/zig/issues/508 - return error.SkipZigTest; - } testNanEqNan(f16); testNanEqNan(f32); testNanEqNan(f64); diff --git a/test/stage1/behavior/misc.zig b/test/stage1/behavior/misc.zig index 681f5be500..44a8334b5d 100644 --- a/test/stage1/behavior/misc.zig +++ b/test/stage1/behavior/misc.zig @@ -335,7 +335,7 @@ test "string concatenation" { comptime expect(@TypeOf(a) == *const [12:0]u8); comptime expect(@TypeOf(b) == *const [12:0]u8); - const len = mem.len(u8, b); + const len = mem.len(b); const len_with_null = len + 1; { var i: u32 = 0; diff --git a/test/stage1/behavior/namespace_depends_on_compile_var.zig b/test/stage1/behavior/namespace_depends_on_compile_var.zig index 4c4fc4eefe..8c5c19d733 100644 --- a/test/stage1/behavior/namespace_depends_on_compile_var.zig +++ b/test/stage1/behavior/namespace_depends_on_compile_var.zig @@ -1,5 +1,5 @@ -const builtin = @import("builtin"); -const expect = @import("std").testing.expect; +const std = @import("std"); +const expect = std.testing.expect; test "namespace depends on compile var" { if (some_namespace.a_bool) { @@ -8,7 +8,7 @@ test "namespace depends on compile var" { expect(!some_namespace.a_bool); } } -const some_namespace = switch (builtin.os) { - builtin.Os.linux => @import("namespace_depends_on_compile_var/a.zig"), +const some_namespace = switch (std.builtin.os.tag) { + .linux => @import("namespace_depends_on_compile_var/a.zig"), else => @import("namespace_depends_on_compile_var/b.zig"), }; diff --git a/test/stage1/behavior/pointers.zig b/test/stage1/behavior/pointers.zig index fdaa5867d7..bcc1d62df3 100644 --- a/test/stage1/behavior/pointers.zig +++ b/test/stage1/behavior/pointers.zig @@ -318,3 +318,15 @@ test "pointer arithmetic affects the alignment" { expect(@typeInfo(@TypeOf(ptr4)).Pointer.alignment == 4); } } + +test "@ptrToInt on null optional at comptime" { + { + const pointer = @intToPtr(?*u8, 0x000); + const x = @ptrToInt(pointer); + comptime expect(0 == @ptrToInt(pointer)); + } + { + const pointer = @intToPtr(?*u8, 0xf00); + comptime expect(0xf00 == @ptrToInt(pointer)); + } +} diff --git a/test/stage1/behavior/sizeof_and_typeof.zig b/test/stage1/behavior/sizeof_and_typeof.zig index d1e1fed40f..a729bf731d 100644 --- a/test/stage1/behavior/sizeof_and_typeof.zig +++ b/test/stage1/behavior/sizeof_and_typeof.zig @@ -1,5 +1,7 @@ -const builtin = @import("builtin"); -const expect = @import("std").testing.expect; +const std = @import("std"); +const builtin = std.builtin; +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; test "@sizeOf and @TypeOf" { const y: @TypeOf(x) = 120; @@ -135,3 +137,53 @@ test "@bitSizeOf" { a: u2, }) == 2); } + +test "@sizeOf comparison against zero" { + const S0 = struct { + f: *@This(), + }; + const U0 = union { + f: *@This(), + }; + const S1 = struct { + fn H(comptime T: type) type { + return struct { + x: T, + }; + } + f0: H(*@This()), + f1: H(**@This()), + f2: H(***@This()), + }; + const U1 = union { + fn H(comptime T: type) type { + return struct { + x: T, + }; + } + f0: H(*@This()), + f1: H(**@This()), + f2: H(***@This()), + }; + const S = struct { + fn doTheTest(comptime T: type, comptime result: bool) void { + expectEqual(result, @sizeOf(T) > 0); + } + }; + // Zero-sized type + S.doTheTest(u0, false); + S.doTheTest(*u0, false); + // Non byte-sized type + S.doTheTest(u1, true); + S.doTheTest(*u1, true); + // Regular type + S.doTheTest(u8, true); + S.doTheTest(*u8, true); + S.doTheTest(f32, true); + S.doTheTest(*f32, true); + // Container with ptr pointing to themselves + S.doTheTest(S0, true); + S.doTheTest(U0, true); + S.doTheTest(S1, true); + S.doTheTest(U1, true); +} diff --git a/test/stage1/behavior/slice.zig b/test/stage1/behavior/slice.zig index b985ec1f8e..9dd57f474e 100644 --- a/test/stage1/behavior/slice.zig +++ b/test/stage1/behavior/slice.zig @@ -1,6 +1,7 @@ const std = @import("std"); const expect = std.testing.expect; const expectEqualSlices = std.testing.expectEqualSlices; +const expectEqual = std.testing.expectEqual; const mem = std.mem; const x = @intToPtr([*]i32, 0x1000)[0..0x500]; @@ -42,6 +43,17 @@ test "C pointer" { expectEqualSlices(u8, "kjdhfkjdhf", slice); } +test "C pointer slice access" { + var buf: [10]u32 = [1]u32{42} ** 10; + const c_ptr = @ptrCast([*c]const u32, &buf); + + comptime expectEqual([]const u32, @TypeOf(c_ptr[0..1])); + + for (c_ptr[0..5]) |*cl| { + expectEqual(@as(u32, 42), cl.*); + } +} + fn sliceSum(comptime q: []const u8) i32 { comptime var result = 0; inline for (q) |item| { @@ -97,3 +109,20 @@ test "obtaining a null terminated slice" { comptime expect(@TypeOf(ptr2) == [:0]u8); comptime expect(@TypeOf(ptr2[0..2]) == []u8); } + +test "empty array to slice" { + const S = struct { + fn doTheTest() void { + const empty: []align(16) u8 = &[_]u8{}; + const align_1: []align(1) u8 = empty; + const align_4: []align(4) u8 = empty; + const align_16: []align(16) u8 = empty; + expectEqual(1, @typeInfo(@TypeOf(align_1)).Pointer.alignment); + expectEqual(4, @typeInfo(@TypeOf(align_4)).Pointer.alignment); + expectEqual(16, @typeInfo(@TypeOf(align_16)).Pointer.alignment); + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} diff --git a/test/stage1/behavior/vector.zig b/test/stage1/behavior/vector.zig index e89399c5e2..01e5ac1fb8 100644 --- a/test/stage1/behavior/vector.zig +++ b/test/stage1/behavior/vector.zig @@ -2,7 +2,6 @@ const std = @import("std"); const mem = std.mem; const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; -const builtin = @import("builtin"); test "implicit cast vector to array - bool" { const S = struct { @@ -114,7 +113,7 @@ test "array to vector" { test "vector casts of sizes not divisable by 8" { // https://github.com/ziglang/zig/issues/3563 - if (builtin.os == .dragonfly) return error.SkipZigTest; + if (std.Target.current.os.tag == .dragonfly) return error.SkipZigTest; const S = struct { fn doTheTest() void { diff --git a/test/standalone.zig b/test/standalone.zig index 2c5b9c790e..aec9c82726 100644 --- a/test/standalone.zig +++ b/test/standalone.zig @@ -18,10 +18,10 @@ pub fn addCases(cases: *tests.StandaloneContext) void { cases.addBuildFile("test/standalone/use_alias/build.zig"); cases.addBuildFile("test/standalone/brace_expansion/build.zig"); cases.addBuildFile("test/standalone/empty_env/build.zig"); - if (std.Target.current.getOs() != .wasi) { + if (std.Target.current.os.tag != .wasi) { cases.addBuildFile("test/standalone/load_dynamic_library/build.zig"); } - if (std.Target.current.getArch() == .x86_64) { // TODO add C ABI support for other architectures + if (std.Target.current.cpu.arch == .x86_64) { // TODO add C ABI support for other architectures cases.addBuildFile("test/stage1/c_abi/build.zig"); } } diff --git a/test/standalone/global_linkage/build.zig b/test/standalone/global_linkage/build.zig new file mode 100644 index 0000000000..c6c45c008a --- /dev/null +++ b/test/standalone/global_linkage/build.zig @@ -0,0 +1,23 @@ +const Builder = @import("std").build.Builder; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + const target = b.standardTargetOptions(null); + + const obj1 = b.addStaticLibrary("obj1", "obj1.zig"); + obj1.setBuildMode(mode); + obj1.setTheTarget(target); + + const obj2 = b.addStaticLibrary("obj2", "obj2.zig"); + obj2.setBuildMode(mode); + obj2.setTheTarget(target); + + const main = b.addTest("main.zig"); + main.setBuildMode(mode); + main.setTheTarget(target); + main.linkLibrary(obj1); + main.linkLibrary(obj2); + + const test_step = b.step("test", "Test it"); + test_step.dependOn(&main.step); +} diff --git a/test/standalone/global_linkage/main.zig b/test/standalone/global_linkage/main.zig new file mode 100644 index 0000000000..53d953765b --- /dev/null +++ b/test/standalone/global_linkage/main.zig @@ -0,0 +1,9 @@ +const std = @import("std"); + +extern var obj1_integer: usize; +extern var obj2_integer: usize; + +test "access the external integers" { + std.testing.expect(obj1_integer == 421); + std.testing.expect(obj2_integer == 422); +} diff --git a/test/standalone/global_linkage/obj1.zig b/test/standalone/global_linkage/obj1.zig new file mode 100644 index 0000000000..95814cda3d --- /dev/null +++ b/test/standalone/global_linkage/obj1.zig @@ -0,0 +1,7 @@ +extern var internal_integer: usize = 1; +extern var obj1_integer: usize = 421; + +comptime { + @export(internal_integer, .{ .name = "internal_integer", .linkage = .Internal }); + @export(obj1_integer, .{ .name = "obj1_integer", .linkage = .Strong }); +} diff --git a/test/standalone/global_linkage/obj2.zig b/test/standalone/global_linkage/obj2.zig new file mode 100644 index 0000000000..f2d44b2dc0 --- /dev/null +++ b/test/standalone/global_linkage/obj2.zig @@ -0,0 +1,7 @@ +extern var internal_integer: usize = 2; +extern var obj2_integer: usize = 422; + +comptime { + @export(internal_integer, .{ .name = "internal_integer", .linkage = .Internal }); + @export(obj2_integer, .{ .name = "obj2_integer", .linkage = .Strong }); +} diff --git a/test/tests.zig b/test/tests.zig index 5982c69d6d..0a7f6942ba 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1,16 +1,15 @@ const std = @import("std"); +const builtin = std.builtin; const debug = std.debug; const warn = debug.warn; const build = std.build; -pub const Target = build.Target; -pub const CrossTarget = build.CrossTarget; +const CrossTarget = std.zig.CrossTarget; const Buffer = std.Buffer; const io = std.io; const fs = std.fs; const mem = std.mem; const fmt = std.fmt; const ArrayList = std.ArrayList; -const builtin = @import("builtin"); const Mode = builtin.Mode; const LibExeObjStep = build.LibExeObjStep; @@ -31,7 +30,7 @@ pub const RunTranslatedCContext = @import("src/run_translated_c.zig").RunTransla pub const CompareOutputContext = @import("src/compare_output.zig").CompareOutputContext; const TestTarget = struct { - target: Target = .Native, + target: CrossTarget = @as(CrossTarget, .{}), mode: builtin.Mode = .Debug, link_libc: bool = false, single_threaded: bool = false, @@ -53,93 +52,77 @@ const test_targets = blk: { }, TestTarget{ - .target = Target{ - .Cross = CrossTarget{ - .cpu = Target.Cpu.baseline(.x86_64), - .os = .linux, - .abi = .none, - }, + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .none, }, }, TestTarget{ - .target = Target{ - .Cross = CrossTarget{ - .cpu = Target.Cpu.baseline(.x86_64), - .os = .linux, - .abi = .gnu, - }, + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .gnu, }, .link_libc = true, }, TestTarget{ - .target = Target{ - .Cross = CrossTarget{ - .cpu = Target.Cpu.baseline(.x86_64), - .os = .linux, - .abi = .musl, - }, + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .musl, }, .link_libc = true, }, TestTarget{ - .target = Target{ - .Cross = CrossTarget{ - .cpu = Target.Cpu.baseline(.i386), - .os = .linux, - .abi = .none, - }, + .target = .{ + .cpu_arch = .i386, + .os_tag = .linux, + .abi = .none, }, }, TestTarget{ - .target = Target{ - .Cross = CrossTarget{ - .cpu = Target.Cpu.baseline(.i386), - .os = .linux, - .abi = .musl, - }, + .target = .{ + .cpu_arch = .i386, + .os_tag = .linux, + .abi = .musl, }, .link_libc = true, }, TestTarget{ - .target = Target{ - .Cross = CrossTarget{ - .cpu = Target.Cpu.baseline(.aarch64), - .os = .linux, - .abi = .none, - }, + .target = .{ + .cpu_arch = .aarch64, + .os_tag = .linux, + .abi = .none, }, }, TestTarget{ - .target = Target{ - .Cross = CrossTarget{ - .cpu = Target.Cpu.baseline(.aarch64), - .os = .linux, - .abi = .musl, - }, + .target = .{ + .cpu_arch = .aarch64, + .os_tag = .linux, + .abi = .musl, }, .link_libc = true, }, TestTarget{ - .target = Target{ - .Cross = CrossTarget{ - .cpu = Target.Cpu.baseline(.aarch64), - .os = .linux, - .abi = .gnu, - }, + .target = .{ + .cpu_arch = .aarch64, + .os_tag = .linux, + .abi = .gnu, }, .link_libc = true, }, TestTarget{ - .target = Target.parse(.{ + .target = CrossTarget.parse(.{ .arch_os_abi = "arm-linux-none", .cpu_features = "generic+v8a", }) catch unreachable, }, TestTarget{ - .target = Target.parse(.{ + .target = CrossTarget.parse(.{ .arch_os_abi = "arm-linux-musleabihf", .cpu_features = "generic+v8a", }) catch unreachable, @@ -147,7 +130,7 @@ const test_targets = blk: { }, // TODO https://github.com/ziglang/zig/issues/3287 //TestTarget{ - // .target = Target.parse(.{ + // .target = CrossTarget.parse(.{ // .arch_os_abi = "arm-linux-gnueabihf", // .cpu_features = "generic+v8a", // }) catch unreachable, @@ -155,109 +138,89 @@ const test_targets = blk: { //}, TestTarget{ - .target = Target{ - .Cross = CrossTarget{ - .cpu = Target.Cpu.baseline(.mipsel), - .os = .linux, - .abi = .none, - }, + .target = .{ + .cpu_arch = .mipsel, + .os_tag = .linux, + .abi = .none, }, }, TestTarget{ - .target = Target{ - .Cross = CrossTarget{ - .cpu = Target.Cpu.baseline(.mipsel), - .os = .linux, - .abi = .musl, - }, + .target = .{ + .cpu_arch = .mipsel, + .os_tag = .linux, + .abi = .musl, }, .link_libc = true, }, TestTarget{ - .target = Target{ - .Cross = CrossTarget{ - .cpu = Target.Cpu.baseline(.riscv64), - .os = .linux, - .abi = .none, - }, + .target = .{ + .cpu_arch = .riscv64, + .os_tag = .linux, + .abi = .none, }, }, // https://github.com/ziglang/zig/issues/4485 //TestTarget{ - // .target = Target{ - // .Cross = CrossTarget{ - // .cpu = Target.Cpu.baseline(.riscv64), - // .os = .linux, - // .abi = .musl, - // }, + // .target = .{ + // .cpu_arch = .riscv64, + // .os_tag = .linux, + // .abi = .musl, // }, // .link_libc = true, //}, // https://github.com/ziglang/zig/issues/3340 //TestTarget{ - // .target = Target{ - // .Cross = CrossTarget{ - // .cpu = Target.Cpu.baseline(.riscv64), - // .os = .linux, - // .abi = .gnu, - // }, + // .target = .{ + // .cpu_arch = .riscv64, + // .os = .linux, + // .abi = .gnu, // }, // .link_libc = true, //}, TestTarget{ - .target = Target{ - .Cross = CrossTarget{ - .cpu = Target.Cpu.baseline(.x86_64), - .os = .macosx, - .abi = .gnu, - }, + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .macosx, + .abi = .gnu, }, // TODO https://github.com/ziglang/zig/issues/3295 .disable_native = true, }, TestTarget{ - .target = Target{ - .Cross = CrossTarget{ - .cpu = Target.Cpu.baseline(.i386), - .os = .windows, - .abi = .msvc, - }, + .target = .{ + .cpu_arch = .i386, + .os_tag = .windows, + .abi = .msvc, }, }, TestTarget{ - .target = Target{ - .Cross = CrossTarget{ - .cpu = Target.Cpu.baseline(.x86_64), - .os = .windows, - .abi = .msvc, - }, + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .windows, + .abi = .msvc, }, }, TestTarget{ - .target = Target{ - .Cross = CrossTarget{ - .cpu = Target.Cpu.baseline(.i386), - .os = .windows, - .abi = .gnu, - }, + .target = .{ + .cpu_arch = .i386, + .os_tag = .windows, + .abi = .gnu, }, .link_libc = true, }, TestTarget{ - .target = Target{ - .Cross = CrossTarget{ - .cpu = Target.Cpu.baseline(.x86_64), - .os = .windows, - .abi = .gnu, - }, + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .windows, + .abi = .gnu, }, .link_libc = true, }, @@ -466,13 +429,13 @@ pub fn addPkgTests( const step = b.step(b.fmt("test-{}", .{name}), desc); for (test_targets) |test_target| { - if (skip_non_native and test_target.target != .Native) + if (skip_non_native and !test_target.target.isNative()) continue; if (skip_libc and test_target.link_libc) continue; - if (test_target.link_libc and test_target.target.osRequiresLibC()) { + if (test_target.link_libc and test_target.target.getOs().requiresLibC()) { // This would be a redundant test. continue; } @@ -482,8 +445,8 @@ pub fn addPkgTests( const ArchTag = @TagType(builtin.Arch); if (test_target.disable_native and - test_target.target.getOs() == builtin.os and - test_target.target.getArch() == builtin.arch) + test_target.target.getOsTag() == std.Target.current.os.tag and + test_target.target.getCpuArch() == std.Target.current.cpu.arch) { continue; } @@ -493,17 +456,14 @@ pub fn addPkgTests( } else false; if (!want_this_mode) continue; - const libc_prefix = if (test_target.target.osRequiresLibC()) + const libc_prefix = if (test_target.target.getOs().requiresLibC()) "" else if (test_target.link_libc) "c" else "bare"; - const triple_prefix = if (test_target.target == .Native) - @as([]const u8, "native") - else - test_target.target.zigTripleNoSubArch(b.allocator) catch unreachable; + const triple_prefix = test_target.target.zigTriple(b.allocator) catch unreachable; const these_tests = b.addTest(root_src); const single_threaded_txt = if (test_target.single_threaded) "single" else "multi"; @@ -517,7 +477,7 @@ pub fn addPkgTests( these_tests.single_threaded = test_target.single_threaded; these_tests.setFilter(test_filter); these_tests.setBuildMode(test_target.mode); - these_tests.setTheTarget(test_target.target); + these_tests.setTarget(test_target.target); if (test_target.link_libc) { these_tests.linkSystemLibrary("c"); } @@ -694,7 +654,7 @@ pub const StackTracesContext = struct { const delims = [_][]const u8{ ":", ":", ":", " in " }; var marks = [_]usize{0} ** 4; // offset search past `[drive]:` on windows - var pos: usize = if (builtin.os == .windows) 2 else 0; + var pos: usize = if (std.Target.current.os.tag == .windows) 2 else 0; for (delims) |delim, i| { marks[i] = mem.indexOfPos(u8, line, pos, delim) orelse { try buf.append(line); @@ -747,7 +707,7 @@ pub const CompileErrorContext = struct { link_libc: bool, is_exe: bool, is_test: bool, - target: Target = .Native, + target: CrossTarget = CrossTarget{}, const SourceFile = struct { filename: []const u8, @@ -839,12 +799,9 @@ pub const CompileErrorContext = struct { zig_args.append("--output-dir") catch unreachable; zig_args.append(b.pathFromRoot(b.cache_root)) catch unreachable; - switch (self.case.target) { - .Native => {}, - .Cross => { - try zig_args.append("-target"); - try zig_args.append(try self.case.target.zigTriple(b.allocator)); - }, + if (!self.case.target.isNative()) { + try zig_args.append("-target"); + try zig_args.append(try self.case.target.zigTriple(b.allocator)); } switch (self.build_mode) { diff --git a/test/translate_c.zig b/test/translate_c.zig index 701513153c..1d6c2a60ae 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -1,6 +1,6 @@ const tests = @import("tests.zig"); -const builtin = @import("builtin"); -const Target = @import("std").Target; +const std = @import("std"); +const CrossTarget = std.zig.CrossTarget; pub fn addCases(cases: *tests.TranslateCContext) void { cases.add("macro line continuation", @@ -665,7 +665,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\} }); - if (builtin.os != builtin.Os.windows) { + if (std.Target.current.os.tag != .windows) { // Windows treats this as an enum with type c_int cases.add("big negative enum init values when C ABI supports long long enums", \\enum EnumWithInits { @@ -1064,7 +1064,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\} }); - if (builtin.os != builtin.Os.windows) { + if (std.Target.current.os.tag != .windows) { // sysv_abi not currently supported on windows cases.add("Macro qualified functions", \\void __attribute__((sysv_abi)) foo(void); @@ -1093,12 +1093,10 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub const fn1 = ?fn (u8) callconv(.C) void; }); - cases.addWithTarget("Calling convention", tests.Target{ - .Cross = .{ - .cpu = Target.Cpu.baseline(.i386), - .os = .linux, - .abi = .none, - }, + cases.addWithTarget("Calling convention", .{ + .cpu_arch = .i386, + .os_tag = .linux, + .abi = .none, }, \\void __attribute__((fastcall)) foo1(float *a); \\void __attribute__((stdcall)) foo2(float *a); @@ -1113,7 +1111,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub fn foo5(a: [*c]f32) callconv(.Thiscall) void; }); - cases.addWithTarget("Calling convention", Target.parse(.{ + cases.addWithTarget("Calling convention", CrossTarget.parse(.{ .arch_os_abi = "arm-linux-none", .cpu_features = "generic+v8_5a", }) catch unreachable, @@ -1124,7 +1122,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub fn foo2(a: [*c]f32) callconv(.AAPCSVFP) void; }); - cases.addWithTarget("Calling convention", Target.parse(.{ + cases.addWithTarget("Calling convention", CrossTarget.parse(.{ .arch_os_abi = "aarch64-linux-none", .cpu_features = "generic+v8_5a", }) catch unreachable, @@ -1596,7 +1594,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\} }); - if (builtin.os != .windows) { + if (std.Target.current.os.tag != .windows) { // When clang uses the -windows-none triple it behaves as MSVC and // interprets the inner `struct Bar` as an anonymous structure cases.add("type referenced struct",