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 = struct { cpu: Cpu, os: Os, abi: Abi, pub const Os = struct { tag: Tag, version_range: VersionRange, 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, }; } }; /// 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, .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 .{ .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 }, }, }, .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 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 }; } }; const d_range = VersionRange.default(tag); 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 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), }; } }; pub const aarch64 = @import("target/aarch64.zig"); pub const amdgpu = @import("target/amdgpu.zig"); pub const arm = @import("target/arm.zig"); pub const avr = @import("target/avr.zig"); pub const bpf = @import("target/bpf.zig"); pub const hexagon = @import("target/hexagon.zig"); pub const mips = @import("target/mips.zig"); pub const msp430 = @import("target/msp430.zig"); pub const nvptx = @import("target/nvptx.zig"); pub const powerpc = @import("target/powerpc.zig"); pub const riscv = @import("target/riscv.zig"); pub const sparc = @import("target/sparc.zig"); pub const systemz = @import("target/systemz.zig"); pub const wasm = @import("target/wasm.zig"); pub const x86 = @import("target/x86.zig"); pub const Abi = enum { none, gnu, gnuabin32, gnuabi64, gnueabi, gnueabihf, gnux32, code16, eabi, eabihf, elfv1, elfv2, android, musl, musleabi, musleabihf, msvc, itanium, cygnus, coreclr, simulator, macabi, pub fn default(arch: Cpu.Arch, target_os: Os) Abi { if (arch.isWasm()) { return .musl; } switch (target_os.tag) { .freestanding, .ananas, .cloudabi, .dragonfly, .lv2, .solaris, .haiku, .minix, .rtems, .nacl, .cnk, .aix, .cuda, .nvcl, .amdhsa, .ps4, .elfiamcu, .mesa3d, .contiki, .amdpal, .hermit, .other, => return .eabi, .openbsd, .macosx, .freebsd, .ios, .tvos, .watchos, .fuchsia, .kfreebsd, .netbsd, .hurd, => return .gnu, .windows, .uefi, => return .msvc, .linux, .wasi, .emscripten, => return .musl, } } 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 const ObjectFormat = enum { unknown, coff, elf, macho, wasm, }; pub const SubSystem = enum { Console, Windows, Posix, Native, EfiApplication, EfiBootServiceDriver, EfiRom, EfiRuntimeDriver, }; pub const Cpu = struct { /// Architecture arch: Arch, /// The CPU model to target. It has a set of features /// which are overridden with the `features` field. model: *const Model, /// An explicit list of the entire CPU feature set. It may differ from the specific CPU model's features. features: Feature.Set, pub const Feature = struct { /// The bit index into `Set`. Has a default value of `undefined` because the canonical /// structures are populated via comptime logic. index: Set.Index = undefined, /// Has a default value of `undefined` because the canonical /// structures are populated via comptime logic. name: []const u8 = undefined, /// If this corresponds to an LLVM-recognized feature, this will be populated; /// otherwise null. llvm_name: ?[:0]const u8, /// Human-friendly UTF-8 text. description: []const u8, /// Sparse `Set` of features this depends on. dependencies: Set, /// A bit set of all the features. pub const Set = struct { ints: [usize_count]usize, pub const needed_bit_count = 154; pub const byte_count = (needed_bit_count + 7) / 8; pub const usize_count = (byte_count + (@sizeOf(usize) - 1)) / @sizeOf(usize); pub const Index = std.math.Log2Int(std.meta.IntType(false, usize_count * @bitSizeOf(usize))); pub const ShiftInt = std.math.Log2Int(usize); pub const empty = Set{ .ints = [1]usize{0} ** usize_count }; pub fn empty_workaround() Set { return Set{ .ints = [1]usize{0} ** usize_count }; } 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)); return (set.ints[usize_index] & (@as(usize, 1) << bit_index)) != 0; } /// Adds the specified feature but not its dependencies. pub fn addFeature(set: *Set, arch_feature_index: Index) void { const usize_index = arch_feature_index / @bitSizeOf(usize); const bit_index = @intCast(ShiftInt, arch_feature_index % @bitSizeOf(usize)); set.ints[usize_index] |= @as(usize, 1) << bit_index; } /// Adds the specified feature set but not its dependencies. pub fn addFeatureSet(set: *Set, other_set: Set) void { set.ints = @as(@Vector(usize_count, usize), set.ints) | @as(@Vector(usize_count, usize), other_set.ints); } /// Removes the specified feature but not its dependents. pub fn removeFeature(set: *Set, arch_feature_index: Index) void { const usize_index = arch_feature_index / @bitSizeOf(usize); const bit_index = @intCast(ShiftInt, arch_feature_index % @bitSizeOf(usize)); set.ints[usize_index] &= ~(@as(usize, 1) << bit_index); } pub fn populateDependencies(set: *Set, all_features_list: []const Cpu.Feature) void { @setEvalBranchQuota(1000000); var old = set.ints; while (true) { for (all_features_list) |feature, index_usize| { const index = @intCast(Index, index_usize); if (set.isEnabled(index)) { set.addFeatureSet(feature.dependencies); } } const nothing_changed = mem.eql(usize, &old, &set.ints); if (nothing_changed) return; old = set.ints; } } pub fn asBytes(set: *const Set) *const [byte_count]u8 { return @ptrCast(*const [byte_count]u8, &set.ints); } pub fn eql(set: Set, other: Set) bool { return mem.eql(usize, &set.ints, &other.ints); } }; pub fn feature_set_fns(comptime F: type) type { return struct { /// Populates only the feature bits specified. pub fn featureSet(features: []const F) Set { var x = Set.empty_workaround(); // TODO remove empty_workaround for (features) |feature| { x.addFeature(@enumToInt(feature)); } return x; } pub fn featureSetHas(set: Set, feature: F) bool { return set.isEnabled(@enumToInt(feature)); } }; } }; pub const Arch = enum { arm, armeb, aarch64, aarch64_be, aarch64_32, arc, avr, bpfel, bpfeb, hexagon, mips, mipsel, mips64, mips64el, msp430, powerpc, powerpc64, powerpc64le, r600, amdgcn, riscv32, riscv64, sparc, sparcv9, sparcel, s390x, tce, tcele, thumb, thumbeb, i386, x86_64, xcore, nvptx, nvptx64, le32, le64, amdil, amdil64, hsail, hsail64, spir, spir64, kalimba, shave, lanai, wasm32, wasm64, renderscript32, renderscript64, pub fn isARM(arch: Arch) bool { return switch (arch) { .arm, .armeb => true, else => false, }; } pub fn isThumb(arch: Arch) bool { return switch (arch) { .thumb, .thumbeb => true, else => false, }; } pub fn isWasm(arch: Arch) bool { return switch (arch) { .wasm32, .wasm64 => true, else => false, }; } pub fn isRISCV(arch: Arch) bool { return switch (arch) { .riscv32, .riscv64 => true, else => false, }; } pub fn isMIPS(arch: Arch) bool { return switch (arch) { .mips, .mipsel, .mips64, .mips64el => true, else => false, }; } pub fn parseCpuModel(arch: Arch, cpu_name: []const u8) !*const Cpu.Model { for (arch.allCpuModels()) |cpu| { if (mem.eql(u8, cpu_name, cpu.name)) { return cpu; } } return error.UnknownCpu; } pub fn toElfMachine(arch: Arch) std.elf.EM { return switch (arch) { .avr => ._AVR, .msp430 => ._MSP430, .arc => ._ARC, .arm => ._ARM, .armeb => ._ARM, .hexagon => ._HEXAGON, .le32 => ._NONE, .mips => ._MIPS, .mipsel => ._MIPS_RS3_LE, .powerpc => ._PPC, .r600 => ._NONE, .riscv32 => ._RISCV, .sparc => ._SPARC, .sparcel => ._SPARC, .tce => ._NONE, .tcele => ._NONE, .thumb => ._ARM, .thumbeb => ._ARM, .i386 => ._386, .xcore => ._XCORE, .nvptx => ._NONE, .amdil => ._NONE, .hsail => ._NONE, .spir => ._NONE, .kalimba => ._CSR_KALIMBA, .shave => ._NONE, .lanai => ._LANAI, .wasm32 => ._NONE, .renderscript32 => ._NONE, .aarch64_32 => ._AARCH64, .aarch64 => ._AARCH64, .aarch64_be => ._AARCH64, .mips64 => ._MIPS, .mips64el => ._MIPS_RS3_LE, .powerpc64 => ._PPC64, .powerpc64le => ._PPC64, .riscv64 => ._RISCV, .x86_64 => ._X86_64, .nvptx64 => ._NONE, .le64 => ._NONE, .amdil64 => ._NONE, .hsail64 => ._NONE, .spir64 => ._NONE, .wasm64 => ._NONE, .renderscript64 => ._NONE, .amdgcn => ._NONE, .bpfel => ._BPF, .bpfeb => ._BPF, .sparcv9 => ._SPARCV9, .s390x => ._S390, }; } pub fn endian(arch: Arch) builtin.Endian { return switch (arch) { .avr, .arm, .aarch64_32, .aarch64, .amdgcn, .amdil, .amdil64, .bpfel, .hexagon, .hsail, .hsail64, .kalimba, .le32, .le64, .mipsel, .mips64el, .msp430, .nvptx, .nvptx64, .sparcel, .tcele, .powerpc64le, .r600, .riscv32, .riscv64, .i386, .x86_64, .wasm32, .wasm64, .xcore, .thumb, .spir, .spir64, .renderscript32, .renderscript64, .shave, => .Little, .arc, .armeb, .aarch64_be, .bpfeb, .mips, .mips64, .powerpc, .powerpc64, .thumbeb, .sparc, .sparcv9, .tce, .lanai, .s390x, => .Big, }; } /// Returns a name that matches the lib/std/target/* directory name. pub fn genericName(arch: Arch) []const u8 { return switch (arch) { .arm, .armeb, .thumb, .thumbeb => "arm", .aarch64, .aarch64_be, .aarch64_32 => "aarch64", .avr => "avr", .bpfel, .bpfeb => "bpf", .hexagon => "hexagon", .mips, .mipsel, .mips64, .mips64el => "mips", .msp430 => "msp430", .powerpc, .powerpc64, .powerpc64le => "powerpc", .amdgcn => "amdgpu", .riscv32, .riscv64 => "riscv", .sparc, .sparcv9, .sparcel => "sparc", .s390x => "systemz", .i386, .x86_64 => "x86", .nvptx, .nvptx64 => "nvptx", .wasm32, .wasm64 => "wasm", else => @tagName(arch), }; } /// All CPU features Zig is aware of, sorted lexicographically by name. pub fn allFeaturesList(arch: Arch) []const Cpu.Feature { return switch (arch) { .arm, .armeb, .thumb, .thumbeb => &arm.all_features, .aarch64, .aarch64_be, .aarch64_32 => &aarch64.all_features, .avr => &avr.all_features, .bpfel, .bpfeb => &bpf.all_features, .hexagon => &hexagon.all_features, .mips, .mipsel, .mips64, .mips64el => &mips.all_features, .msp430 => &msp430.all_features, .powerpc, .powerpc64, .powerpc64le => &powerpc.all_features, .amdgcn => &amdgpu.all_features, .riscv32, .riscv64 => &riscv.all_features, .sparc, .sparcv9, .sparcel => &sparc.all_features, .s390x => &systemz.all_features, .i386, .x86_64 => &x86.all_features, .nvptx, .nvptx64 => &nvptx.all_features, .wasm32, .wasm64 => &wasm.all_features, else => &[0]Cpu.Feature{}, }; } /// All processors Zig is aware of, sorted lexicographically by name. pub fn allCpuModels(arch: Arch) []const *const Cpu.Model { return switch (arch) { .arm, .armeb, .thumb, .thumbeb => arm.all_cpus, .aarch64, .aarch64_be, .aarch64_32 => aarch64.all_cpus, .avr => avr.all_cpus, .bpfel, .bpfeb => bpf.all_cpus, .hexagon => hexagon.all_cpus, .mips, .mipsel, .mips64, .mips64el => mips.all_cpus, .msp430 => msp430.all_cpus, .powerpc, .powerpc64, .powerpc64le => powerpc.all_cpus, .amdgcn => amdgpu.all_cpus, .riscv32, .riscv64 => riscv.all_cpus, .sparc, .sparcv9, .sparcel => sparc.all_cpus, .s390x => systemz.all_cpus, .i386, .x86_64 => x86.all_cpus, .nvptx, .nvptx64 => nvptx.all_cpus, .wasm32, .wasm64 => wasm.all_cpus, 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 { name: []const u8, llvm_name: ?[:0]const u8, features: Feature.Set, pub fn toCpu(model: *const Model, arch: Arch) Cpu { var features = model.features; features.populateDependencies(arch.allFeaturesList()); return .{ .arch = arch, .model = model, .features = features, }; } }; /// 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); } }; pub const current = Target{ .cpu = builtin.cpu, .os = builtin.os, .abi = builtin.abi, }; 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), }); } /// 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", .arm, .armeb, .thumb, .thumbeb, .aarch64_32, => "arm", .aarch64, .aarch64_be, => "arm64", 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 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 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()) { return ".wasm"; } switch (self.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 libPrefix(self: Target) []const u8 { if (self.cpu.arch.isWasm()) { return ""; } switch (self.abi) { .msvc => return "", else => return "lib", } } pub fn getObjectFormat(self: Target) ObjectFormat { if (self.os.tag == .windows or self.os.tag == .uefi) { return .coff; } else if (self.isDarwin()) { return .macho; } if (self.cpu.arch.isWasm()) { return .wasm; } return .elf; } pub fn isMinGW(self: Target) bool { return self.os.tag == .windows and self.isGnu(); } pub fn isGnu(self: Target) bool { return self.abi.isGnu(); } pub fn isMusl(self: Target) bool { return self.abi.isMusl(); } pub fn isAndroid(self: Target) bool { return switch (self.abi) { .android => true, else => false, }; } pub fn isWasm(self: Target) bool { return self.cpu.arch.isWasm(); } pub fn isDarwin(self: Target) bool { return self.os.tag.isDarwin(); } 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, } } 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, soft_fp, }; pub fn getFloatAbi(self: Target) FloatAbi { return switch (self.abi) { .gnueabihf, .eabihf, .musleabihf, => .hard, else => .soft, }; } pub fn hasDynamicLinker(self: Target) bool { if (self.cpu.arch.isWasm()) { return false; } switch (self.os.tag) { .freestanding, .ios, .tvos, .watchos, .macosx, .uefi, .windows, .emscripten, .other, => return false, else => return true, } } /// Caller owns returned memory. pub fn getStandardDynamicLinkerPath( self: Target, allocator: *mem.Allocator, ) error{ OutOfMemory, UnknownDynamicLinkerPath, TargetHasNoDynamicLinker, }![:0]u8 { const a = allocator; if (self.isAndroid()) { return mem.dupeZ(a, u8, if (self.getArchPtrBitWidth() == 64) "/system/bin/linker64" else "/system/bin/linker"); } if (self.isMusl()) { var result = try std.Buffer.init(allocator, "/lib/ld-musl-"); defer result.deinit(); var is_arm = false; switch (self.cpu.arch) { .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(); } switch (self.os.tag) { .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.cpu.arch) { .i386, .sparc, .sparcel, => return mem.dupeZ(a, u8, "/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"), .arm, .armeb, .thumb, .thumbeb, => return mem.dupeZ(a, u8, switch (self.getFloatAbi()) { .hard => "/lib/ld-linux-armhf.so.3", else => "/lib/ld-linux.so.3", }), .mips, .mipsel, .mips64, .mips64el, => return error.UnknownDynamicLinkerPath, .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.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"), .wasm32, .wasm64, => return error.TargetHasNoDynamicLinker, .arc, .avr, .bpfel, .bpfeb, .hexagon, .msp430, .r600, .amdgcn, .tce, .tcele, .xcore, .nvptx, .nvptx64, .le32, .le64, .amdil, .amdil64, .hsail, .hsail64, .spir, .spir64, .kalimba, .shave, .lanai, .renderscript32, .renderscript64, => return error.UnknownDynamicLinkerPath, }, .freestanding, .ios, .tvos, .watchos, .macosx, .uefi, .windows, .emscripten, .other, => return error.TargetHasNoDynamicLinker, else => return error.UnknownDynamicLinkerPath, } } }; 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); } }