diff --git a/build.zig b/build.zig index e1ae5cf3cd..32694afb0a 100644 --- a/build.zig +++ b/build.zig @@ -298,7 +298,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..9b8aca18d0 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -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; diff --git a/lib/std/build.zig b/lib/std/build.zig index 25f7d536b1..92b06a0261 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,77 @@ 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 = .{}, + }; + + /// 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 Arch, OS, and ABI to build for.", + ) orelse return args.default_target; + + // TODO add cpu and features as part of the target triple + + var diags: std.Target.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); + }, + else => |e| return e, + }; + + 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 +850,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)) { @@ -978,111 +1032,11 @@ test "builder.findProgram compiles" { _ = 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`. -pub const CrossTarget = std.Target; - -/// Wraps `std.Target` so that it can be annotated as "the native target" or an explicitly specified target. -pub const Target = union(enum) { - Native, - Cross: std.Target, - - pub fn getTarget(self: Target) std.Target { - return switch (self) { - .Native => std.Target.current, - .Cross => |t| t, - }; - } - - pub fn getOs(self: Target) std.Target.Os.Tag { - return self.getTarget().os.tag; - } - - pub fn getCpu(self: Target) std.Target.Cpu { - return self.getTarget().cpu; - } - - pub fn getAbi(self: Target) std.Target.Abi { - return self.getTarget().abi; - } - - pub fn getArch(self: Target) std.Target.Cpu.Arch { - return self.getCpu().arch; - } - - pub fn isFreeBSD(self: Target) bool { - return self.getTarget().os.tag == .freebsd; - } - - pub fn isDarwin(self: Target) bool { - return self.getTarget().os.tag.isDarwin(); - } - - pub fn isNetBSD(self: Target) bool { - return self.getTarget().os.tag == .netbsd; - } - - pub fn isUefi(self: Target) bool { - return self.getTarget().os.tag == .uefi; - } - - pub fn isDragonFlyBSD(self: Target) bool { - return self.getTarget().os.tag == .dragonfly; - } - - pub fn isLinux(self: Target) bool { - return self.getTarget().os.tag == .linux; - } - - pub fn isWindows(self: Target) bool { - return self.getTarget().os.tag == .windows; - } - - pub fn oFileExt(self: Target) []const u8 { - return self.getTarget().oFileExt(); - } - - pub fn exeFileExt(self: Target) []const u8 { - return self.getTarget().exeFileExt(); - } - - pub fn staticLibSuffix(self: Target) []const u8 { - return self.getTarget().staticLibSuffix(); - } - - pub fn libPrefix(self: Target) []const u8 { - return self.getTarget().libPrefix(); - } - - pub fn zigTriple(self: Target, allocator: *mem.Allocator) ![]u8 { - return self.getTarget().zigTriple(allocator); - } - - pub fn linuxTriple(self: Target, allocator: *mem.Allocator) ![]u8 { - return self.getTarget().linuxTriple(allocator); - } - - pub fn wantSharedLibSymLinks(self: Target) bool { - return self.getTarget().wantSharedLibSymLinks(); - } - - pub fn vcpkgTriplet(self: Target, allocator: *mem.Allocator, linkage: std.build.VcpkgLinkage) ![]const u8 { - return self.getTarget().vcpkgTriplet(allocator, linkage); - } - - pub fn getExternalExecutor(self: Target) std.Target.Executor { - switch (self) { - .Native => return .native, - .Cross => |t| return t.getExternalExecutor(), - } - } - - pub fn isGnuLibC(self: Target) bool { - return self.getTarget().isGnuLibC(); - } -}; +/// Deprecated. Use `std.zig.CrossTarget`. +pub const Target = std.zig.CrossTarget; pub const Pkg = struct { name: []const u8, @@ -1135,7 +1089,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, @@ -1188,7 +1142,6 @@ pub const LibExeObjStep = struct { install_step: ?*InstallArtifactStep, libc_file: ?[]const u8 = null, - target_glibc: ?Version = null, valgrind_support: ?bool = null, @@ -1288,7 +1241,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, @@ -1379,36 +1331,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); } @@ -2002,47 +1929,41 @@ 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 != std.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(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 (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.linker_script) |linker_script| { @@ -2517,10 +2438,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/translate_c.zig b/lib/std/build/translate_c.zig index 90e95be5e1..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: build.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: build.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 3204ce905e..de37fda903 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -429,6 +429,29 @@ pub const Version = struct { .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 diff --git a/lib/std/target.zig b/lib/std/target.zig index 9007771c1a..440f50b811 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -60,6 +60,16 @@ pub const Target = struct { else => false, }; } + + pub fn dynamicLibSuffix(tag: Tag) [:0]const u8 { + if (tag.isDarwin()) { + return ".dylib"; + } + switch (tag) { + .windows => return ".dll", + else => return ".so", + } + } }; /// Based on NTDDI version constants from @@ -210,64 +220,31 @@ pub const Target = struct { } }; - pub fn parse(text: []const u8) !Os { - var it = mem.separate(text, "."); - const os_name = it.next().?; - const tag = std.meta.stringToEnum(Tag, os_name) orelse return error.UnknownOperatingSystem; - const version_text = it.rest(); - const S = struct { - fn parseNone(s: []const u8) !void { - if (s.len != 0) return error.InvalidOperatingSystemVersion; - } - fn parseSemVer(s: []const u8, d_range: Version.Range) !Version.Range { - if (s.len == 0) return d_range; - var range_it = mem.separate(s, "..."); - - const min_text = range_it.next().?; - const min_ver = Version.parse(min_text) catch |err| switch (err) { - error.Overflow => return error.InvalidOperatingSystemVersion, - error.InvalidCharacter => return error.InvalidOperatingSystemVersion, - error.InvalidVersion => return error.InvalidOperatingSystemVersion, - }; - - const max_text = range_it.next() orelse return Version.Range{ - .min = min_ver, - .max = d_range.max, - }; - const max_ver = Version.parse(max_text) catch |err| switch (err) { - error.Overflow => return error.InvalidOperatingSystemVersion, - error.InvalidCharacter => return error.InvalidOperatingSystemVersion, - error.InvalidVersion => return error.InvalidOperatingSystemVersion, - }; - - return Version.Range{ .min = min_ver, .max = max_ver }; - } - fn parseWindows(s: []const u8, d_range: WindowsVersion.Range) !WindowsVersion.Range { - if (s.len == 0) return d_range; - var range_it = mem.separate(s, "..."); - - const min_text = range_it.next().?; - const min_ver = std.meta.stringToEnum(WindowsVersion, min_text) orelse - return error.InvalidOperatingSystemVersion; - - const max_text = range_it.next() orelse return WindowsVersion.Range{ - .min = min_ver, - .max = d_range.max, - }; - const max_ver = std.meta.stringToEnum(WindowsVersion, max_text) orelse - return error.InvalidOperatingSystemVersion; - - return WindowsVersion.Range{ .min = min_ver, .max = max_ver }; - } + pub fn defaultVersionRange(tag: Tag) Os { + return .{ + .tag = tag, + .version_range = VersionRange.default(tag), }; - const d_range = VersionRange.default(tag); - switch (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, - .dragonfly, .fuchsia, - .ios, .kfreebsd, .lv2, .solaris, @@ -282,8 +259,6 @@ pub const Target = struct { .amdhsa, .ps4, .elfiamcu, - .tvos, - .watchos, .mesa3d, .contiki, .amdpal, @@ -293,41 +268,7 @@ pub const Target = struct { .emscripten, .uefi, .other, - => return Os{ - .tag = tag, - .version_range = .{ .none = try S.parseNone(version_text) }, - }, - - .freebsd, - .macosx, - .netbsd, - .openbsd, - => return Os{ - .tag = tag, - .version_range = .{ .semver = try S.parseSemVer(version_text, d_range.semver) }, - }, - - .linux => return Os{ - .tag = tag, - .version_range = .{ - .linux = .{ - .range = try S.parseSemVer(version_text, d_range.linux.range), - .glibc = d_range.linux.glibc, - }, - }, - }, - - .windows => return Os{ - .tag = tag, - .version_range = .{ .windows = try S.parseWindows(version_text, d_range.windows) }, - }, - } - } - - pub fn defaultVersionRange(tag: Tag) Os { - return .{ - .tag = tag, - .version_range = VersionRange.default(tag), + => false, }; } }; @@ -434,6 +375,13 @@ pub const Target = struct { else => false, }; } + + pub fn oFileExt(abi: Abi) [:0]const u8 { + return switch (abi) { + .msvc => ".obj", + else => ".o", + }; + } }; pub const ObjectFormat = enum { @@ -500,6 +448,12 @@ pub const Target = struct { 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)); @@ -526,6 +480,15 @@ pub const Target = struct { 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); @@ -663,7 +626,7 @@ pub const Target = struct { return cpu; } } - return error.UnknownCpu; + return error.UnknownCpuModel; } pub fn toElfMachine(arch: Arch) std.elf.EM { @@ -779,6 +742,66 @@ pub const Target = struct { }; } + 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, + => return 64, + } + } + /// Returns a name that matches the lib/std/target/* directory name. pub fn genericName(arch: Arch) []const u8 { return switch (arch) { @@ -846,16 +869,6 @@ pub const Target = struct { 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 { @@ -872,41 +885,44 @@ pub const Target = struct { .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); } }; @@ -918,239 +934,70 @@ pub const Target = struct { pub const stack_align = 16; - /// TODO add OS version ranges and glibc version - pub fn zigTriple(self: Target, allocator: *mem.Allocator) ![]u8 { - return std.fmt.allocPrint(allocator, "{}-{}-{}", .{ - @tagName(self.cpu.arch), - @tagName(self.os.tag), - @tagName(self.abi), - }); + 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(target: Target, allocator: *mem.Allocator, linkage: std.build.VcpkgLinkage) ![]const u8 { - const arch = switch (target.cpu.arch) { - .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.os) { - .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 linuxTriple(self: Target, allocator: *mem.Allocator) ![]u8 { - return std.fmt.allocPrint(allocator, "{}-{}-{}", .{ - @tagName(self.cpu.arch), - @tagName(self.os.tag), - @tagName(self.abi), - }); - } - - 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; - var os = try Os.parse(os_name); - diags.os = os; - - const opt_abi_text = it.next(); - const abi = if (opt_abi_text) |abi_text| blk: { - var abi_it = mem.separate(abi_text, "."); - const abi = std.meta.stringToEnum(Abi, abi_it.next().?) orelse - return error.UnknownApplicationBinaryInterface; - const abi_ver_text = abi_it.rest(); - if (abi_ver_text.len != 0) { - if (os.tag == .linux and abi.isGnu()) { - os.version_range.linux.glibc = Version.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; - } - } - break :blk abi; - } 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, - }; - }; - return Target{ - .cpu = cpu, - .os = os, - .abi = abi, - }; - } - - pub fn oFileExt(self: Target) []const u8 { - return switch (self.abi) { - .msvc => ".obj", - else => ".o", - }; - } - - pub fn exeFileExt(self: Target) []const u8 { - if (self.os.tag == .windows) { - return ".exe"; - } else if (self.os.tag == .uefi) { - return ".efi"; - } else if (self.cpu.arch.isWasm()) { - return ".wasm"; - } else { - return ""; - } - } - - pub fn staticLibSuffix(self: Target) []const u8 { - if (self.cpu.arch.isWasm()) { + pub fn staticLibSuffix_cpu_arch_abi(cpu_arch: Cpu.Arch, abi: Abi) [:0]const u8 { + if (cpu_arch.isWasm()) { return ".wasm"; } - switch (self.abi) { + switch (abi) { .msvc => return ".lib", else => return ".a", } } - pub fn dynamicLibSuffix(self: Target) []const u8 { - if (self.isDarwin()) { - return ".dylib"; - } - switch (self.os) { - .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.cpu.arch.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.abi) { + switch (abi) { .msvc => return "", else => return "lib", } } + pub fn libPrefix(self: Target) [:0]const u8 { + return libPrefix_cpu_arch_abi(self.cpu.arch, self.abi); + } + pub fn getObjectFormat(self: Target) ObjectFormat { if (self.os.tag == .windows or self.os.tag == .uefi) { return .coff; @@ -1190,129 +1037,18 @@ pub const Target = struct { return self.os.tag.isDarwin(); } + pub fn isGnuLibC_os_tag_abi(os_tag: Os.Tag, abi: Abi) bool { + return os_tag == .linux and abi.isGnu(); + } + pub fn isGnuLibC(self: Target) bool { - return self.os.tag == .linux and self.abi.isGnu(); - } - - pub fn wantSharedLibSymLinks(self: Target) bool { - return self.os.tag != .windows; - } - - pub fn osRequiresLibC(self: Target) bool { - return self.isDarwin() or self.os.tag == .freebsd or self.os.tag == .netbsd; - } - - pub fn getArchPtrBitWidth(self: Target) u32 { - switch (self.cpu.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, - => return 64, - } + return isGnuLibC_os_tag_abi(self.os.tag, self.abi); } pub fn supportsNewStackCall(self: Target) bool { return !self.cpu.arch.isWasm(); } - pub const Executor = union(enum) { - native, - qemu: []const u8, - wine: []const u8, - wasmtime: []const u8, - unavailable, - }; - - pub fn getExternalExecutor(self: Target) Executor { - // If the target OS matches the host OS, we can use QEMU to emulate a foreign architecture. - if (self.os.tag == builtin.os.tag) { - return switch (self.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 (self.os.tag) { - .windows => switch (self.getArchPtrBitWidth()) { - 32 => return Executor{ .wine = "wine" }, - 64 => return Executor{ .wine = "wine64" }, - else => return .unavailable, - }, - .wasi => switch (self.getArchPtrBitWidth()) { - 32 => return Executor{ .wasmtime = "wasmtime" }, - else => return .unavailable, - }, - else => return .unavailable, - } - } - pub const FloatAbi = enum { hard, soft, @@ -1359,7 +1095,7 @@ pub const Target = struct { }![:0]u8 { const a = allocator; if (self.isAndroid()) { - return mem.dupeZ(a, u8, if (self.getArchPtrBitWidth() == 64) + return mem.dupeZ(a, u8, if (self.cpu.arch.ptrBitWidth() == 64) "/system/bin/linker64" else "/system/bin/linker"); @@ -1477,52 +1213,3 @@ pub const Target = struct { } } }; - -test "Target.parse" { - { - const target = try Target.parse(.{ - .arch_os_abi = "x86_64-linux-gnu", - .cpu_features = "x86_64-sse-sse2-avx-cx8", - }); - - 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 target = try Target.parse(.{ - .arch_os_abi = "arm-linux-musleabihf", - .cpu_features = "generic+v8a", - }); - - 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 target = try Target.parse(.{ - .arch_os_abi = "aarch64-linux.3.10...4.4.1-gnu.2.27", - .cpu_features = "generic+v8a", - }); - - 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); - } -} diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 348f651a88..398a71ff37 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,16 @@ 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/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..e785c40073 --- /dev/null +++ b/lib/std/zig/cross_target.zig @@ -0,0 +1,766 @@ +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. +pub const CrossTarget = struct { + /// `null` means native. + cpu_arch: ?Target.Cpu.Arch = null, + + /// If `cpu_arch` is native, `null` means native. Otherwise it means baseline. + /// If this is non-null, `cpu_arch` must be specified. + cpu_model: ?*const Target.Cpu.Model = null, + + /// Sparse set of CPU features to add to the set from `cpu_model`. + /// If this is non-empty, `cpu_arch` must be specified. + 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`. + /// If this is non-empty, `cpu_arch` must be specified. + 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 the native C ABI, if `os_tag` is native, otherwise it means the default C ABI. + abi: ?Target.Abi = 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, + + pub const OsVersion = union(enum) { + none: void, + semver: SemVer, + windows: Target.Os.WindowsVersion, + }; + + pub const SemVer = std.builtin.Version; + + pub fn fromTarget(target: Target) CrossTarget { + var result: CrossTarget = .{ + .cpu_arch = target.cpu.arch, + .cpu_model = 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, + .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, + => { + self.os_version_min = .{ .none = {} }; + self.os_version_max = .{ .none = {} }; + }, + + .freebsd, + .macosx, + .netbsd, + .openbsd, + => { + 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 }; + }, + } + } + + 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, + + /// 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 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; + + // Start with everything initialized to default values. + var result: CrossTarget = .{}; + + 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; + 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 = null; + } else if (mem.eql(u8, cpu_name, "baseline")) { + result.cpu_model = Target.Cpu.Model.baseline(arch); + } else { + result.cpu_model = 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; + } + + pub fn getCpu(self: CrossTarget) Target.Cpu { + if (self.cpu_arch) |arch| { + if (self.cpu_model) |model| { + var adjusted_model = model.toCpu(arch); + self.updateCpuFeatures(&adjusted_model.features); + return adjusted_model; + } else { + var adjusted_baseline = Target.Cpu.baseline(arch); + self.updateCpuFeatures(&adjusted_baseline.features); + return adjusted_baseline; + } + } else { + assert(self.cpu_model == null); + assert(self.cpu_features_sub.isEmpty()); + assert(self.cpu_features_add.isEmpty()); + // 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; + } + } + + 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; + } + + 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; + } + + 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.?; + } + + 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.?; + } + + pub fn getAbi(self: CrossTarget) Target.Abi { + if (self.abi) |abi| return abi; + + if (self.isNativeOs()) { + // 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 isNativeCpu(self: CrossTarget) bool { + return self.cpu_arch == null and self.cpu_model == null and + self.cpu_features_sub.isEmpty() and self.cpu_features_add.isEmpty(); + } + + pub fn isNativeOs(self: CrossTarget) bool { + return self.os_tag == null and self.os_version_min == null and self.os_version_max == null; + } + + pub fn isNativeAbi(self: CrossTarget) bool { + return self.abi == null and self.glibc_version == null; + } + + pub fn isNative(self: CrossTarget) bool { + return self.isNativeCpu() and self.isNativeOs() and self.isNativeAbi(); + } + + 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.isNativeCpu()) "native" else @tagName(self.getCpuArch()); + 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.abi) |abi| { + try result.print("-{}", .{@tagName(abi)}); + if (self.glibc_version) |v| { + try result.print(".{}", .{v}); + } + } else { + assert(self.glibc_version == null); + } + + 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, + }; + + pub fn getExternalExecutor(self: CrossTarget) Executor { + const os_tag = self.getOsTag(); + const cpu_arch = self.getCpuArch(); + + // If the target OS matches the host OS, we can use QEMU to emulate a foreign architecture. + if (os_tag == Target.current.os.tag) { + 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 }; + } + + 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().?; + 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" { + { + 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/src-self-hosted/stage2.zig b/src-self-hosted/stage2.zig index 7f0f35abe0..ecaad0daf2 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, @@ -634,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.cpu = blk: { - const llvm = @import("llvm.zig"); - const llvm_cpu_name = llvm.GetHostCPUName(); - const llvm_cpu_features = llvm.GetNativeFeatures(); - break :blk try detectNativeCpuWithLLVM(target.cpu.arch, 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{}, @@ -661,7 +658,6 @@ export fn stage2_target_parse( 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, @@ -681,44 +677,42 @@ fn stage2TargetParse( zig_triple_oz: ?[*:0]const u8, mcpu_oz: ?[*:0]const u8, ) !void { - const target: std.build.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 std.build.Target{ - .Cross = Target.parse(.{ - .arch_os_abi = zig_triple, - .cpu_features = mcpu, - .diagnostics = &diags, - }) catch |err| switch (err) { - error.UnknownCpu => { - 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); - }, - else => |e| return e, + var diags: CrossTarget.ParseOptions.Diagnostics = .{}; + break :blk CrossTarget.parse(.{ + .arch_os_abi = zig_triple, + .cpu_features = mcpu, + .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); + }, + else => |e| return e, }; - } else std.build.Target.Native; + } else .{}; try stage1_target.fromTarget(target); } @@ -908,8 +902,8 @@ const Stage2Target = extern struct { dynamic_linker: ?[*:0]const u8, - fn toTarget(in_target: Stage2Target) std.build.Target { - if (in_target.is_native) return .Native; + 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; @@ -924,28 +918,11 @@ const Stage2Target = extern struct { }; } - fn fromTarget(self: *Stage2Target, build_target: std.build.Target) !void { + fn fromTarget(self: *Stage2Target, cross_target: CrossTarget) !void { const allocator = std.heap.c_allocator; - var dynamic_linker: ?[*:0]u8 = null; - const target = switch (build_target) { - .Native => blk: { - const info = try std.zig.system.NativeTargetInfo.detect(std.heap.c_allocator); - if (info.dynamic_linker) |dl| { - dynamic_linker = dl.ptr; - } - // TODO we want to just use 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; - var t = info.target; - t.cpu = try detectNativeCpuWithLLVM(arch, llvm_cpu_name, llvm_cpu_features); - break :blk t; - }, - .Cross => |t| t, - }; + 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, @@ -1145,7 +1122,7 @@ const Stage2Target = extern struct { .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 = build_target == .Native, + .is_native = cross_target.isNative(), .glibc_version = glibc_version, .dynamic_linker = dynamic_linker, }; @@ -1156,6 +1133,40 @@ fn enumInt(comptime Enum: type, int: c_int) Enum { return @intToEnum(Enum, @intCast(@TagType(Enum), int)); } +/// TODO move dynamic linker to be part of the target +/// TODO self-host this function +fn crossTargetToTarget(cross_target: CrossTarget, dynamic_linker_ptr: *?[*:0]u8) !Target { + var adjusted_target = cross_target.toTarget(); + if (cross_target.isNativeCpu() or cross_target.isNativeOs()) { + const detected_info = try std.zig.system.NativeTargetInfo.detect(std.heap.c_allocator); + if (cross_target.isNativeCpu()) { + adjusted_target.cpu = detected_info.target.cpu; + + // TODO We want to just use detected_info.target but implementing + // CPU model & feature detection is todo so here we rely on LLVM. + // There is another occurrence of this; search for detectNativeCpuWithLLVM. + const llvm = @import("llvm.zig"); + const llvm_cpu_name = llvm.GetHostCPUName(); + const llvm_cpu_features = llvm.GetNativeFeatures(); + const arch = std.Target.current.cpu.arch; + adjusted_target.cpu = try detectNativeCpuWithLLVM(arch, llvm_cpu_name, llvm_cpu_features); + } + if (cross_target.isNativeOs()) { + adjusted_target.os = detected_info.target.os; + + if (detected_info.dynamic_linker) |dl| { + dynamic_linker_ptr.* = dl.ptr; + } + if (cross_target.abi == null) { + adjusted_target.abi = detected_info.target.abi; + } + } else if (cross_target.abi == null) { + adjusted_target.abi = Target.Abi.default(adjusted_target.cpu.arch, adjusted_target.os); + } + } + return adjusted_target; +} + // ABI warning const Stage2GLibCVersion = extern struct { major: u32, diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 7c1eb6d409..979bf45bbe 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1,5 +1,5 @@ const tests = @import("tests.zig"); -const Target = @import("std").Target; +const std = @import("std"); pub fn addCases(cases: *tests.CompileErrorContext) void { cases.addTest("type mismatch with tuple concatenation", @@ -386,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 = tests.Target{ - .Cross = .{ - .cpu = Target.Cpu.baseline(.wasm32), - .os = Target.Os.defaultVersionRange(.wasi), - .abi = .none, - }, + tc.target = std.zig.CrossTarget{ + .cpu_arch = .wasm32, + .os_tag = .wasi, + .abi = .none, }; break :x tc; }); @@ -787,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 = tests.Target{ - .Cross = .{ - .cpu = Target.Cpu.baseline(.x86_64), - .os = Target.Os.defaultVersionRange(.linux), - .abi = .gnu, - }, + tc.target = std.zig.CrossTarget{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .gnu, }; break :x tc; }); @@ -1452,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 (Target.current.os.tag == .linux) { + if (std.Target.current.os.tag == .linux) { cases.addTest("implicit dependency on libc", \\extern "c" fn exit(u8) void; \\export fn entry() void { diff --git a/test/src/translate_c.zig b/test/src/translate_c.zig index 968f09eeb8..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: build.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: build.Target, + target: CrossTarget, source: []const u8, expected_lines: []const []const u8, ) void { diff --git a/test/tests.zig b/test/tests.zig index 78eaf56273..9cf4e7bd98 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -3,7 +3,7 @@ const builtin = std.builtin; const debug = std.debug; const warn = debug.warn; const build = std.build; -pub const Target = build.Target; +const CrossTarget = std.zig.CrossTarget; const Buffer = std.Buffer; const io = std.io; const fs = std.fs; @@ -30,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: build.Target = .Native, + target: CrossTarget = @as(CrossTarget, .{}), mode: builtin.Mode = .Debug, link_libc: bool = false, single_threaded: bool = false, @@ -52,105 +52,85 @@ const test_targets = blk: { }, TestTarget{ - .target = Target{ - .Cross = .{ - .cpu = std.Target.Cpu.baseline(.x86_64), - .os = std.Target.Os.defaultVersionRange(.linux), - .abi = .none, - }, + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .none, }, }, TestTarget{ - .target = Target{ - .Cross = .{ - .cpu = std.Target.Cpu.baseline(.x86_64), - .os = std.Target.Os.defaultVersionRange(.linux), - .abi = .gnu, - }, + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .gnu, }, .link_libc = true, }, TestTarget{ - .target = Target{ - .Cross = .{ - .cpu = std.Target.Cpu.baseline(.x86_64), - .os = std.Target.Os.defaultVersionRange(.linux), - .abi = .musl, - }, - }, - .link_libc = true, - }, - - TestTarget{ - .target = Target{ - .Cross = .{ - .cpu = std.Target.Cpu.baseline(.i386), - .os = std.Target.Os.defaultVersionRange(.linux), - .abi = .none, - }, - }, - }, - TestTarget{ - .target = Target{ - .Cross = .{ - .cpu = std.Target.Cpu.baseline(.i386), - .os = std.Target.Os.defaultVersionRange(.linux), - .abi = .musl, - }, - }, - .link_libc = true, - }, - - TestTarget{ - .target = Target{ - .Cross = .{ - .cpu = std.Target.Cpu.baseline(.aarch64), - .os = std.Target.Os.defaultVersionRange(.linux), - .abi = .none, - }, - }, - }, - TestTarget{ - .target = Target{ - .Cross = .{ - .cpu = std.Target.Cpu.baseline(.aarch64), - .os = std.Target.Os.defaultVersionRange(.linux), - .abi = .musl, - }, - }, - .link_libc = true, - }, - TestTarget{ - .target = Target{ - .Cross = .{ - .cpu = std.Target.Cpu.baseline(.aarch64), - .os = std.Target.Os.defaultVersionRange(.linux), - .abi = .gnu, - }, + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .musl, }, .link_libc = true, }, TestTarget{ .target = .{ - .Cross = std.Target.parse(.{ - .arch_os_abi = "arm-linux-none", - .cpu_features = "generic+v8a", - }) catch unreachable, + .cpu_arch = .i386, + .os_tag = .linux, + .abi = .none, }, }, TestTarget{ .target = .{ - .Cross = std.Target.parse(.{ - .arch_os_abi = "arm-linux-musleabihf", - .cpu_features = "generic+v8a", - }) catch unreachable, + .cpu_arch = .i386, + .os_tag = .linux, + .abi = .musl, }, .link_libc = true, }, + + TestTarget{ + .target = .{ + .cpu_arch = .aarch64, + .os_tag = .linux, + .abi = .none, + }, + }, + TestTarget{ + .target = .{ + .cpu_arch = .aarch64, + .os_tag = .linux, + .abi = .musl, + }, + .link_libc = true, + }, + TestTarget{ + .target = .{ + .cpu_arch = .aarch64, + .os_tag = .linux, + .abi = .gnu, + }, + .link_libc = true, + }, + + TestTarget{ + .target = CrossTarget.parse(.{ + .arch_os_abi = "arm-linux-none", + .cpu_features = "generic+v8a", + }) catch unreachable, + }, + TestTarget{ + .target = CrossTarget.parse(.{ + .arch_os_abi = "arm-linux-musleabihf", + .cpu_features = "generic+v8a", + }) catch unreachable, + .link_libc = true, + }, // TODO https://github.com/ziglang/zig/issues/3287 //TestTarget{ - // .target = std.Target.parse(.{ + // .target = CrossTarget.parse(.{ // .arch_os_abi = "arm-linux-gnueabihf", // .cpu_features = "generic+v8a", // }) catch unreachable, @@ -158,75 +138,61 @@ const test_targets = blk: { //}, TestTarget{ - .target = Target{ - .Cross = .{ - .cpu = std.Target.Cpu.baseline(.mipsel), - .os = std.Target.Os.defaultVersionRange(.linux), - .abi = .none, - }, + .target = .{ + .cpu_arch = .mipsel, + .os_tag = .linux, + .abi = .none, }, }, TestTarget{ - .target = Target{ - .Cross = .{ - .cpu = std.Target.Cpu.baseline(.mipsel), - .os = std.Target.Os.defaultVersionRange(.linux), - .abi = .musl, - }, + .target = .{ + .cpu_arch = .mipsel, + .os_tag = .linux, + .abi = .musl, }, .link_libc = true, }, TestTarget{ - .target = Target{ - .Cross = .{ - .cpu = std.Target.Cpu.baseline(.x86_64), - .os = std.Target.Os.defaultVersionRange(.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 = .{ - .cpu = std.Target.Cpu.baseline(.i386), - .os = std.Target.Os.defaultVersionRange(.windows), - .abi = .msvc, - }, + .target = .{ + .cpu_arch = .i386, + .os_tag = .windows, + .abi = .msvc, }, }, TestTarget{ - .target = Target{ - .Cross = .{ - .cpu = std.Target.Cpu.baseline(.x86_64), - .os = std.Target.Os.defaultVersionRange(.windows), - .abi = .msvc, - }, + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .windows, + .abi = .msvc, }, }, TestTarget{ - .target = Target{ - .Cross = .{ - .cpu = std.Target.Cpu.baseline(.i386), - .os = std.Target.Os.defaultVersionRange(.windows), - .abi = .gnu, - }, + .target = .{ + .cpu_arch = .i386, + .os_tag = .windows, + .abi = .gnu, }, .link_libc = true, }, TestTarget{ - .target = Target{ - .Cross = .{ - .cpu = std.Target.Cpu.baseline(.x86_64), - .os = std.Target.Os.defaultVersionRange(.windows), - .abi = .gnu, - }, + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .windows, + .abi = .gnu, }, .link_libc = true, }, @@ -435,13 +401,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.getTarget().osRequiresLibC()) { + if (test_target.link_libc and test_target.target.getOs().requiresLibC()) { // This would be a redundant test. continue; } @@ -451,8 +417,8 @@ pub fn addPkgTests( const ArchTag = @TagType(builtin.Arch); if (test_target.disable_native and - test_target.target.getOs() == std.Target.current.os.tag and - test_target.target.getArch() == std.Target.current.cpu.arch) + test_target.target.getOsTag() == std.Target.current.os.tag and + test_target.target.getCpuArch() == std.Target.current.cpu.arch) { continue; } @@ -462,17 +428,14 @@ pub fn addPkgTests( } else false; if (!want_this_mode) continue; - const libc_prefix = if (test_target.target.getTarget().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.zigTriple(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"; @@ -486,7 +449,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"); } @@ -716,7 +679,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, @@ -808,12 +771,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 07364fb032..1d6c2a60ae 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -1,6 +1,6 @@ const tests = @import("tests.zig"); const std = @import("std"); -const Target = std.Target; +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 (Target.current.os.tag != .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 (Target.current.os.tag != .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); @@ -1094,11 +1094,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void { }); cases.addWithTarget("Calling convention", .{ - .Cross = .{ - .cpu = Target.Cpu.baseline(.i386), - .os = Target.Os.defaultVersionRange(.linux), - .abi = .none, - }, + .cpu_arch = .i386, + .os_tag = .linux, + .abi = .none, }, \\void __attribute__((fastcall)) foo1(float *a); \\void __attribute__((stdcall)) foo2(float *a); @@ -1113,12 +1111,10 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub fn foo5(a: [*c]f32) callconv(.Thiscall) void; }); - cases.addWithTarget("Calling convention", .{ - .Cross = Target.parse(.{ - .arch_os_abi = "arm-linux-none", - .cpu_features = "generic+v8_5a", - }) catch unreachable, - }, + cases.addWithTarget("Calling convention", CrossTarget.parse(.{ + .arch_os_abi = "arm-linux-none", + .cpu_features = "generic+v8_5a", + }) catch unreachable, \\void __attribute__((pcs("aapcs"))) foo1(float *a); \\void __attribute__((pcs("aapcs-vfp"))) foo2(float *a); , &[_][]const u8{ @@ -1126,12 +1122,10 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub fn foo2(a: [*c]f32) callconv(.AAPCSVFP) void; }); - cases.addWithTarget("Calling convention", .{ - .Cross = Target.parse(.{ - .arch_os_abi = "aarch64-linux-none", - .cpu_features = "generic+v8_5a", - }) catch unreachable, - }, + cases.addWithTarget("Calling convention", CrossTarget.parse(.{ + .arch_os_abi = "aarch64-linux-none", + .cpu_features = "generic+v8_5a", + }) catch unreachable, \\void __attribute__((aarch64_vector_pcs)) foo1(float *a); , &[_][]const u8{ \\pub fn foo1(a: [*c]f32) callconv(.Vectorcall) void; @@ -1600,7 +1594,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\} }); - if (Target.current.os.tag != .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",