Commit 48c7e6c48b

Andrew Kelley <andrew@ziglang.org>
2020-01-22 23:13:31
std.Target.CpuFeatures is now a struct with both CPU and feature set
Previously it was a tagged union which was one of: * baseline * a specific CPU * a set of features Now, it's possible to have a CPU but also modify the CPU's feature set on top of that. This is closer to what LLVM does. This is more correct because Zig's notion of CPUs (and LLVM's) is not exact CPU models. For example "skylake" is not one very specific model; there are several different pieces of hardware that match "skylake" that have different feature sets enabled.
1 parent c6bfece
lib/std/target/avr.zig
@@ -2378,7 +2378,3 @@ pub const all_cpus = &[_]*const Cpu{
     &cpu.avrxmega7,
     &cpu.m3000,
 };
-
-pub const baseline_features = featureSet(&[_]Feature{
-    .avr0,
-});
lib/std/target/riscv.zig
@@ -69,11 +69,39 @@ pub const all_features = blk: {
 };
 
 pub const cpu = struct {
+    pub const baseline_rv32 = Cpu{
+        .name = "baseline_rv32",
+        .llvm_name = "generic-rv32",
+        .features = featureSet(&[_]Feature{
+            .a,
+            .c,
+            .d,
+            .f,
+            .m,
+            .relax,
+        }),
+    };
+
+    pub const baseline_rv64 = Cpu{
+        .name = "baseline_rv64",
+        .llvm_name = "generic-rv64",
+        .features = featureSet(&[_]Feature{
+            .@"64bit",
+            .a,
+            .c,
+            .d,
+            .f,
+            .m,
+            .relax,
+        }),
+    };
+
     pub const generic_rv32 = Cpu{
         .name = "generic_rv32",
         .llvm_name = "generic-rv32",
         .features = featureSet(&[_]Feature{}),
     };
+
     pub const generic_rv64 = Cpu{
         .name = "generic_rv64",
         .llvm_name = "generic-rv64",
@@ -87,25 +115,8 @@ pub const cpu = struct {
 /// TODO: Replace this with usage of `std.meta.declList`. It does work, but stage1
 /// compiler has inefficient memory and CPU usage, affecting build times.
 pub const all_cpus = &[_]*const Cpu{
+    &cpu.baseline_rv32,
+    &cpu.baseline_rv64,
     &cpu.generic_rv32,
     &cpu.generic_rv64,
 };
-
-pub const baseline_32_features = featureSet(&[_]Feature{
-    .a,
-    .c,
-    .d,
-    .f,
-    .m,
-    .relax,
-});
-
-pub const baseline_64_features = featureSet(&[_]Feature{
-    .@"64bit",
-    .a,
-    .c,
-    .d,
-    .f,
-    .m,
-    .relax,
-});
lib/std/build.zig
@@ -484,6 +484,7 @@ pub const Builder = struct {
                         .arch = builtin.arch,
                         .os = builtin.os,
                         .abi = builtin.abi,
+                        .cpu_features = builtin.cpu_features,
                     },
                 }).linuxTriple(self.allocator);
 
@@ -1375,6 +1376,7 @@ pub const LibExeObjStep = struct {
                 .arch = target_arch,
                 .os = target_os,
                 .abi = target_abi,
+                .cpu_features = target_arch.getBaselineCpuFeatures(),
             },
         });
     }
@@ -1972,25 +1974,41 @@ pub const LibExeObjStep = struct {
                 try zig_args.append("-target");
                 try zig_args.append(self.target.zigTriple(builder.allocator) catch unreachable);
 
-                switch (cross.cpu_features) {
-                    .baseline => {},
-                    .cpu => |cpu| {
+                const all_features = self.target.getArch().allFeaturesList();
+                var populated_cpu_features = cross.cpu_features.cpu.features;
+                populated_cpu_features.populateDependencies(all_features);
+
+                if (populated_cpu_features.eql(cross.cpu_features.features)) {
+                    // The CPU name alone is sufficient.
+                    // If it is the baseline CPU, no command line args are required.
+                    if (cross.cpu_features.cpu != self.target.getArch().getBaselineCpuFeatures().cpu) {
                         try zig_args.append("-target-cpu");
-                        try zig_args.append(cpu.name);
-                    },
-                    .features => |features| {
-                        try zig_args.append("-target-cpu-features");
-
-                        var feature_str_buffer = try std.Buffer.initSize(builder.allocator, 0);
-                        for (self.target.getArch().allFeaturesList()) |feature, i| {
-                            if (features.isEnabled(@intCast(Target.Cpu.Feature.Set.Index, i))) {
-                                try feature_str_buffer.append(feature.name);
-                                try feature_str_buffer.append(",");
-                            }
+                        try zig_args.append(cross.cpu_features.cpu.name);
+                    }
+                } else {
+                    try zig_args.append("-target-cpu");
+                    try zig_args.append(cross.cpu_features.cpu.name);
+
+                    try zig_args.append("-target-feature");
+                    var feature_str_buffer = try std.Buffer.initSize(builder.allocator, 0);
+                    for (all_features) |feature, i_usize| {
+                        const i = @intCast(Target.Cpu.Feature.Set.Index, i_usize);
+                        const in_cpu_set = populated_cpu_features.isEnabled(i);
+                        const in_actual_set = cross.cpu_features.features.isEnabled(i);
+                        if (in_cpu_set and !in_actual_set) {
+                            try feature_str_buffer.appendByte('-');
+                            try feature_str_buffer.append(feature.name);
+                            try feature_str_buffer.appendByte(',');
+                        } else if (!in_cpu_set and in_actual_set) {
+                            try feature_str_buffer.appendByte('+');
+                            try feature_str_buffer.append(feature.name);
+                            try feature_str_buffer.appendByte(',');
                         }
-
-                        try zig_args.append(feature_str_buffer.toSlice());
-                    },
+                    }
+                    if (mem.endsWith(u8, feature_str_buffer.toSliceConst(), ",")) {
+                        feature_str_buffer.shrink(feature_str_buffer.len() - 1);
+                    }
+                    try zig_args.append(feature_str_buffer.toSliceConst());
                 }
             },
         }
lib/std/target.zig
@@ -172,6 +172,15 @@ pub const Target = union(enum) {
             r6,
         };
 
+        pub fn subArchName(arch: Arch) ?[]const u8 {
+            return switch (arch) {
+                .arm, .armeb, .thumb, .thumbeb => |arm32| @tagName(arm32),
+                .aarch64, .aarch64_be, .aarch64_32 => |arm64| @tagName(arm64),
+                .kalimba => |kalimba| @tagName(kalimba),
+                else => return null,
+            };
+        }
+
         pub fn subArchFeature(arch: Arch) ?u8 {
             return switch (arch) {
                 .arm, .armeb, .thumb, .thumbeb => |arm32| switch (arm32) {
@@ -251,24 +260,12 @@ pub const Target = union(enum) {
             return error.UnknownCpu;
         }
 
-        /// This parsing function supports 2 syntaxes.
-        /// * Comma-separated list of features, with + or - in front of each feature. This
-        ///   form represents a deviation from baseline.
-        /// * Comma-separated list of features, with no + or - in front of each feature. This
-        ///   form represents an exclusive list of enabled features; no other features besides
-        ///   the ones listed, and their dependencies, will be enabled.
+        /// Comma-separated list of features, with + or - in front of each feature. This
+        /// form represents a deviation from baseline CPU, which is provided as a parameter.
         /// Extra commas are ignored.
-        pub fn parseCpuFeatureSet(arch: Arch, features_text: []const u8) !Cpu.Feature.Set {
-            // Here we compute both and choose the correct result at the end, based
-            // on whether or not we saw + and - signs.
-            var whitelist_set = Cpu.Feature.Set.empty;
-            var baseline_set = arch.baselineFeatures();
-            var mode: enum {
-                unknown,
-                baseline,
-                whitelist,
-            } = .unknown;
-
+        pub fn parseCpuFeatureSet(arch: Arch, cpu: *const Cpu, features_text: []const u8) !Cpu.Feature.Set {
+            const all_features = arch.allFeaturesList();
+            var set = cpu.features;
             var it = mem.tokenize(features_text, ",");
             while (it.next()) |item_text| {
                 var feature_name: []const u8 = undefined;
@@ -277,40 +274,20 @@ pub const Target = union(enum) {
                     sub,
                 } = undefined;
                 if (mem.startsWith(u8, item_text, "+")) {
-                    switch (mode) {
-                        .unknown, .baseline => mode = .baseline,
-                        .whitelist => return error.InvalidCpuFeatures,
-                    }
                     op = .add;
                     feature_name = item_text[1..];
                 } else if (mem.startsWith(u8, item_text, "-")) {
-                    switch (mode) {
-                        .unknown, .baseline => mode = .baseline,
-                        .whitelist => return error.InvalidCpuFeatures,
-                    }
                     op = .sub;
                     feature_name = item_text[1..];
                 } else {
-                    switch (mode) {
-                        .unknown, .whitelist => mode = .whitelist,
-                        .baseline => return error.InvalidCpuFeatures,
-                    }
-                    op = .add;
-                    feature_name = item_text;
+                    return error.InvalidCpuFeatures;
                 }
-                const all_features = arch.allFeaturesList();
                 for (all_features) |feature, index_usize| {
                     const index = @intCast(Cpu.Feature.Set.Index, index_usize);
                     if (mem.eql(u8, feature_name, feature.name)) {
                         switch (op) {
-                            .add => {
-                                baseline_set.addFeature(index);
-                                whitelist_set.addFeature(index);
-                            },
-                            .sub => {
-                                baseline_set.removeFeature(index);
-                                whitelist_set.removeFeature(index);
-                            },
+                            .add => set.addFeature(index),
+                            .sub => set.removeFeature(index),
                         }
                         break;
                     }
@@ -319,10 +296,8 @@ pub const Target = union(enum) {
                 }
             }
 
-            return switch (mode) {
-                .unknown, .whitelist => whitelist_set,
-                .baseline => baseline_set,
-            };
+            set.populateDependencies(all_features);
+            return set;
         }
 
         pub fn toElfMachine(arch: Arch) std.elf.EM {
@@ -485,29 +460,37 @@ pub const Target = union(enum) {
 
         /// 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 baselineFeatures(arch: Arch) Cpu.Feature.Set {
-            return switch (arch) {
-                .arm, .armeb, .thumb, .thumbeb => arm.cpu.generic.features,
-                .aarch64, .aarch64_be, .aarch64_32 => aarch64.cpu.generic.features,
-                .avr => avr.baseline_features,
-                .bpfel, .bpfeb => bpf.cpu.generic.features,
-                .hexagon => hexagon.cpu.generic.features,
-                .mips, .mipsel => mips.cpu.mips32.features,
-                .mips64, .mips64el => mips.cpu.mips64.features,
-                .msp430 => msp430.cpu.generic.features,
-                .powerpc, .powerpc64, .powerpc64le => powerpc.cpu.generic.features,
-                .amdgcn => amdgpu.cpu.generic.features,
-                .riscv32 => riscv.baseline_32_features,
-                .riscv64 => riscv.baseline_64_features,
-                .sparc, .sparcv9, .sparcel => sparc.cpu.generic.features,
-                .s390x => systemz.cpu.generic.features,
-                .i386 => x86.cpu.pentium4.features,
-                .x86_64 => x86.cpu.x86_64.features,
-                .nvptx, .nvptx64 => nvptx.cpu.sm_20.features,
-                .wasm32, .wasm64 => wasm.cpu.generic.features,
-
-                else => Cpu.Feature.Set.empty,
+        pub fn getBaselineCpuFeatures(arch: Arch) CpuFeatures {
+            const S = struct {
+                const generic_cpu = Cpu{
+                    .name = "generic",
+                    .llvm_name = null,
+                    .features = Cpu.Feature.Set.empty,
+                };
+            };
+            const cpu = switch (arch) {
+                .arm, .armeb, .thumb, .thumbeb => &arm.cpu.generic,
+                .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_cpu,
             };
+            return CpuFeatures.initFromCpu(arch, cpu);
         }
 
         /// All CPUs Zig is aware of, sorted lexicographically by name.
@@ -685,19 +668,28 @@ pub const Target = union(enum) {
         arch: Arch,
         os: Os,
         abi: Abi,
-        cpu_features: CpuFeatures = .baseline,
+        cpu_features: CpuFeatures,
     };
 
-    pub const CpuFeatures = union(enum) {
-        /// The "default" set of CPU features for cross-compiling. A conservative set
-        /// of features that is expected to be supported on most available hardware.
-        baseline,
-
-        /// Target one specific CPU.
+    pub const CpuFeatures = struct {
+        /// The CPU to target. It has a set of features
+        /// which are overridden with the `features` field.
         cpu: *const Cpu,
 
         /// Explicitly provide the entire CPU feature set.
         features: Cpu.Feature.Set,
+
+        pub fn initFromCpu(arch: Arch, cpu: *const Cpu) CpuFeatures {
+            var features = cpu.features;
+            if (arch.subArchFeature()) |sub_arch_index| {
+                features.addFeature(sub_arch_index);
+            }
+            features.populateDependencies(arch.allFeaturesList());
+            return CpuFeatures{
+                .cpu = cpu,
+                .features = features,
+            };
+        }
     };
 
     pub const current = Target{
@@ -718,14 +710,6 @@ pub const Target = union(enum) {
         };
     }
 
-    pub fn cpuFeatureSet(self: Target) Cpu.Feature.Set {
-        return switch (self.getCpuFeatures()) {
-            .baseline => self.getArch().baselineFeatures(),
-            .cpu => |cpu| cpu.features,
-            .features => |features| features,
-        };
-    }
-
     pub fn zigTriple(self: Target, allocator: *mem.Allocator) ![]u8 {
         return std.fmt.allocPrint(allocator, "{}{}-{}-{}", .{
             @tagName(self.getArch()),
@@ -791,14 +775,18 @@ pub const Target = union(enum) {
         });
     }
 
+    /// TODO: Support CPU features here?
+    /// https://github.com/ziglang/zig/issues/4261
     pub fn parse(text: []const u8) !Target {
         var it = mem.separate(text, "-");
         const arch_name = it.next() orelse return error.MissingArchitecture;
         const os_name = it.next() orelse return error.MissingOperatingSystem;
         const abi_name = it.next();
+        const arch = try parseArchSub(arch_name);
 
         var cross = Cross{
-            .arch = try parseArchSub(arch_name),
+            .arch = arch,
+            .cpu_features = arch.getBaselineCpuFeatures(),
             .os = try parseOs(os_name),
             .abi = undefined,
         };
src/codegen.cpp
@@ -8581,7 +8581,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
             stage2_cpu_features_get_builtin_str(g->zig_target->cpu_features, &ptr, &len);
             buf_append_mem(contents, ptr, len);
         } else {
-            buf_append_str(contents, ".baseline;\n");
+            buf_append_str(contents, "arch.getBaselineCpuFeatures();\n");
         }
     }
     if (g->libc_link_lib != nullptr && g->zig_target->glibc_version != nullptr) {
src/main.cpp
@@ -866,7 +866,7 @@ int main(int argc, char **argv) {
                     cpu = argv[i];
                 } else if (strcmp(arg, "-target-feature") == 0) {
                     features = argv[i];
-                }else {
+                } else {
                     fprintf(stderr, "Invalid argument: %s\n", arg);
                     return print_error_usage(arg0);
                 }
@@ -984,35 +984,10 @@ int main(int argc, char **argv) {
     Buf zig_triple_buf = BUF_INIT;
     target_triple_zig(&zig_triple_buf, &target);
 
-    if (cpu && features) {
-        fprintf(stderr, "-target-cpu and -target-feature options not allowed together\n");
+    const char *stage2_triple_arg = target.is_native ? nullptr : buf_ptr(&zig_triple_buf);
+    if ((err = stage2_cpu_features_parse(&target.cpu_features, stage2_triple_arg, cpu, features))) {
+        fprintf(stderr, "unable to initialize CPU features: %s\n", err_str(err));
         return main_exit(root_progress_node, EXIT_FAILURE);
-    } else if (cpu) {
-        if ((err = stage2_cpu_features_parse_cpu(&target.cpu_features, buf_ptr(&zig_triple_buf), cpu))) {
-            fprintf(stderr, "-target-cpu error: %s\n", err_str(err));
-            return main_exit(root_progress_node, EXIT_FAILURE);
-        }
-    } else if (features) {
-        if ((err = stage2_cpu_features_parse_features(&target.cpu_features, buf_ptr(&zig_triple_buf),
-                        features)))
-        {
-            fprintf(stderr, "-target-feature error: %s\n", err_str(err));
-            return main_exit(root_progress_node, EXIT_FAILURE);
-        }
-    } else if (target.is_native) {
-        const char *cpu_name = ZigLLVMGetHostCPUName();
-        const char *cpu_features = ZigLLVMGetNativeFeatures();
-        if ((err = stage2_cpu_features_llvm(&target.cpu_features, buf_ptr(&zig_triple_buf),
-                        cpu_name, cpu_features)))
-        {
-            fprintf(stderr, "unable to determine native CPU features: %s\n", err_str(err));
-            return main_exit(root_progress_node, EXIT_FAILURE);
-        }
-    } else {
-        if ((err = stage2_cpu_features_baseline(&target.cpu_features, buf_ptr(&zig_triple_buf)))) {
-            fprintf(stderr, "unable to determine baseline CPU features: %s\n", err_str(err));
-            return main_exit(root_progress_node, EXIT_FAILURE);
-        }
     }
 
     if (output_dir != nullptr && enable_cache == CacheOptOn) {
src/userland.cpp
@@ -2,7 +2,8 @@
 // src-self-hosted/stage1.zig
 
 #include "userland.h"
-#include "ast_render.hpp"
+#include "util.hpp"
+#include "zig_llvm.h"
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -96,32 +97,30 @@ struct Stage2CpuFeatures {
     const char *cache_hash;
 };
 
-Error stage2_cpu_features_parse_cpu(Stage2CpuFeatures **out, const char *zig_triple, const char *str) {
-    const char *msg = "stage0 called stage2_cpu_features_parse_cpu";
-    stage2_panic(msg, strlen(msg));
-}
-Error stage2_cpu_features_parse_features(Stage2CpuFeatures **out, const char *zig_triple, const char *str) {
-    const char *msg = "stage0 called stage2_cpu_features_parse_features";
-    stage2_panic(msg, strlen(msg));
-}
-Error stage2_cpu_features_baseline(Stage2CpuFeatures **out, const char *zig_triple) {
-    Stage2CpuFeatures *result = allocate<Stage2CpuFeatures>(1, "Stage2CpuFeatures");
-    result->builtin_str = ".baseline;\n";
-    result->cache_hash = "\n\n";
-    *out = result;
-    return ErrorNone;
-}
-Error stage2_cpu_features_llvm(Stage2CpuFeatures **out, const char *zig_triple,
-        const char *llvm_cpu_name, const char *llvm_features)
+Error stage2_cpu_features_parse(struct Stage2CpuFeatures **out, const char *zig_triple,
+        const char *cpu_name, const char *cpu_features)
 {
-    Stage2CpuFeatures *result = allocate<Stage2CpuFeatures>(1, "Stage2CpuFeatures");
-    result->llvm_cpu_name = llvm_cpu_name;
-    result->llvm_cpu_features = llvm_features;
-    result->builtin_str = ".baseline;\n";
-    result->cache_hash = "native\n\n";
-    *out = result;
-    return ErrorNone;
+    if (zig_triple == nullptr) {
+        Stage2CpuFeatures *result = allocate<Stage2CpuFeatures>(1, "Stage2CpuFeatures");
+        result->llvm_cpu_name = ZigLLVMGetHostCPUName();
+        result->llvm_cpu_features = ZigLLVMGetNativeFeatures();
+        result->builtin_str = "arch.getBaselineCpuFeatures();\n";
+        result->cache_hash = "native\n\n";
+        *out = result;
+        return ErrorNone;
+    }
+    if (cpu_name == nullptr && cpu_features == nullptr) {
+        Stage2CpuFeatures *result = allocate<Stage2CpuFeatures>(1, "Stage2CpuFeatures");
+        result->builtin_str = "arch.getBaselineCpuFeatures();\n";
+        result->cache_hash = "\n\n";
+        *out = result;
+        return ErrorNone;
+    }
+
+    const char *msg = "stage0 called stage2_cpu_features_parse with non-null cpu name or features";
+    stage2_panic(msg, strlen(msg));
 }
+
 void stage2_cpu_features_get_cache_hash(const Stage2CpuFeatures *cpu_features,
         const char **ptr, size_t *len)
 {
src/userland.h
@@ -184,20 +184,8 @@ ZIG_EXTERN_C void stage2_progress_update_node(Stage2ProgressNode *node,
 struct Stage2CpuFeatures;
 
 // ABI warning
-ZIG_EXTERN_C Error stage2_cpu_features_parse_cpu(struct Stage2CpuFeatures **result,
-        const char *zig_triple, const char *cpu_name);
-
-// ABI warning
-ZIG_EXTERN_C Error stage2_cpu_features_parse_features(struct Stage2CpuFeatures **result,
-        const char *zig_triple, const char *features);
-
-// ABI warning
-ZIG_EXTERN_C Error stage2_cpu_features_baseline(struct Stage2CpuFeatures **result,
-        const char *zig_triple);
-
-// ABI warning
-ZIG_EXTERN_C Error stage2_cpu_features_llvm(struct Stage2CpuFeatures **result,
-        const char *zig_triple, const char *llvm_cpu_name, const char *llvm_features);
+ZIG_EXTERN_C Error stage2_cpu_features_parse(struct Stage2CpuFeatures **result,
+        const char *zig_triple, const char *cpu_name, const char *cpu_features);
 
 // ABI warning
 ZIG_EXTERN_C const char *stage2_cpu_features_get_llvm_cpu(const struct Stage2CpuFeatures *cpu_features);
src-self-hosted/print_targets.zig
@@ -227,16 +227,14 @@ pub fn cmdTargets(
     try jws.objectField("abi");
     try jws.emitString(@tagName(native_target.getAbi()));
     try jws.objectField("cpuName");
-    switch (native_target.getCpuFeatures()) {
-        .baseline, .features => try jws.emitNull(),
-        .cpu => |cpu| try jws.emitString(cpu.name),
-    }
+    const cpu_features = native_target.getCpuFeatures();
+    try jws.emitString(cpu_features.cpu.name);
     {
         try jws.objectField("cpuFeatures");
         try jws.beginArray();
-        const feature_set = native_target.cpuFeatureSet();
-        for (native_target.getArch().allFeaturesList()) |feature, i| {
-            if (feature_set.isEnabled(@intCast(u8, i))) {
+        for (native_target.getArch().allFeaturesList()) |feature, i_usize| {
+            const index = @intCast(Target.Cpu.Feature.Set.Index, i_usize);
+            if (cpu_features.features.isEnabled(index)) {
                 try jws.arrayElem();
                 try jws.emitString(feature.name);
             }
src-self-hosted/stage1.zig
@@ -540,74 +540,66 @@ export fn stage2_progress_update_node(node: *std.Progress.Node, done_count: usiz
     node.context.maybeRefresh();
 }
 
-/// I have observed the CPU name reported by LLVM being incorrect. On
-/// the SourceHut build services, LLVM 9.0 reports the CPU as "athlon-xp",
-/// which is a 32-bit CPU, even though the system is 64-bit and the reported
-/// CPU features include, among other things, +64bit.
-/// So the strategy taken here is that we observe both reported CPU, and the
-/// reported CPU features. The features are trusted more; but if the features
-/// match exactly the features of the reported CPU, then we trust the reported CPU.
 fn cpuFeaturesFromLLVM(
     arch: Target.Arch,
     llvm_cpu_name_z: ?[*:0]const u8,
     llvm_cpu_features_opt: ?[*:0]const u8,
 ) !Target.CpuFeatures {
-    var set = arch.baselineFeatures();
-    const llvm_cpu_features = llvm_cpu_features_opt orelse return Target.CpuFeatures{
-        .features = set,
-    };
+    var result = arch.getBaselineCpuFeatures();
 
-    const all_features = arch.allFeaturesList();
+    if (llvm_cpu_name_z) |cpu_name_z| {
+        const llvm_cpu_name = mem.toSliceConst(u8, cpu_name_z);
 
-    var it = mem.tokenize(mem.toSliceConst(u8, llvm_cpu_features), ",");
-    while (it.next()) |decorated_llvm_feat| {
-        var op: enum {
-            add,
-            sub,
-        } = undefined;
-        var llvm_feat: []const u8 = undefined;
-        if (mem.startsWith(u8, decorated_llvm_feat, "+")) {
-            op = .add;
-            llvm_feat = decorated_llvm_feat[1..];
-        } else if (mem.startsWith(u8, decorated_llvm_feat, "-")) {
-            op = .sub;
-            llvm_feat = decorated_llvm_feat[1..];
-        } else {
-            return error.InvalidLlvmCpuFeaturesFormat;
-        }
-        for (all_features) |feature, index| {
-            const this_llvm_name = feature.llvm_name orelse continue;
-            if (mem.eql(u8, llvm_feat, this_llvm_name)) {
-                switch (op) {
-                    .add => set.addFeature(@intCast(u8, index)),
-                    .sub => set.removeFeature(@intCast(u8, index)),
-                }
+        for (arch.allCpus()) |cpu| {
+            const this_llvm_name = cpu.llvm_name orelse continue;
+            if (mem.eql(u8, this_llvm_name, llvm_cpu_name)) {
+                // Here we use the non-dependencies-populated set,
+                // so that subtracting features later in this function
+                // affect the prepopulated set.
+                result = Target.CpuFeatures{
+                    .cpu = cpu,
+                    .features = cpu.features,
+                };
                 break;
             }
         }
     }
 
-    if (llvm_cpu_name_z) |cpu_name_z| {
-        const llvm_cpu_name = mem.toSliceConst(u8, cpu_name_z);
+    const all_features = arch.allFeaturesList();
 
-        for (arch.allCpus()) |cpu| {
-            const this_llvm_name = cpu.llvm_name orelse continue;
-            if (mem.eql(u8, this_llvm_name, llvm_cpu_name)) {
-                // Only trust the CPU if the reported features exactly match.
-                var populated_reported_features = set;
-                populated_reported_features.populateDependencies(all_features);
-                var populated_cpu_features = cpu.features;
-                populated_cpu_features.populateDependencies(all_features);
-                if (populated_reported_features.eql(populated_cpu_features)) {
-                    return Target.CpuFeatures{ .cpu = cpu };
-                } else {
-                    return Target.CpuFeatures{ .features = set };
+    if (llvm_cpu_features_opt) |llvm_cpu_features| {
+        var it = mem.tokenize(mem.toSliceConst(u8, llvm_cpu_features), ",");
+        while (it.next()) |decorated_llvm_feat| {
+            var op: enum {
+                add,
+                sub,
+            } = undefined;
+            var llvm_feat: []const u8 = undefined;
+            if (mem.startsWith(u8, decorated_llvm_feat, "+")) {
+                op = .add;
+                llvm_feat = decorated_llvm_feat[1..];
+            } else if (mem.startsWith(u8, decorated_llvm_feat, "-")) {
+                op = .sub;
+                llvm_feat = decorated_llvm_feat[1..];
+            } else {
+                return error.InvalidLlvmCpuFeaturesFormat;
+            }
+            for (all_features) |feature, index_usize| {
+                const this_llvm_name = feature.llvm_name orelse continue;
+                if (mem.eql(u8, llvm_feat, this_llvm_name)) {
+                    const index = @intCast(Target.Cpu.Feature.Set.Index, index_usize);
+                    switch (op) {
+                        .add => result.features.addFeature(index),
+                        .sub => result.features.removeFeature(index),
+                    }
+                    break;
                 }
             }
         }
     }
 
-    return Target.CpuFeatures{ .features = set };
+    result.features.populateDependencies(all_features);
+    return result;
 }
 
 // ABI warning
@@ -639,7 +631,6 @@ const Stage2CpuFeatures = struct {
     allocator: *mem.Allocator,
     cpu_features: Target.CpuFeatures,
 
-    llvm_cpu_name: ?[*:0]const u8,
     llvm_features_str: ?[*:0]const u8,
 
     builtin_str: [:0]const u8,
@@ -647,125 +638,64 @@ const Stage2CpuFeatures = struct {
 
     const Self = @This();
 
-    fn createBaseline(allocator: *mem.Allocator, arch: Target.Arch) !*Self {
-        const self = try allocator.create(Self);
-        errdefer allocator.destroy(self);
-
-        const builtin_str = try std.fmt.allocPrint0(allocator, ".baseline;\n", .{});
-        errdefer allocator.free(builtin_str);
-
-        const cache_hash = try std.fmt.allocPrint0(allocator, "\n\n", .{});
-        errdefer allocator.free(cache_hash);
-
-        self.* = Self{
-            .allocator = allocator,
-            .cpu_features = .baseline,
-            .llvm_cpu_name = null,
-            .llvm_features_str = try initLLVMFeatures(allocator, arch, arch.baselineFeatures()),
-            .builtin_str = builtin_str,
-            .cache_hash = cache_hash,
-        };
-
-        return self;
-    }
-
-    fn createFromLLVM(
-        allocator: *mem.Allocator,
-        zig_triple: [*:0]const u8,
-        llvm_cpu_name_z: ?[*:0]const u8,
-        llvm_cpu_features: ?[*:0]const u8,
-    ) !*Self {
-        const target = try Target.parse(mem.toSliceConst(u8, zig_triple));
-        const arch = target.Cross.arch;
-        const cpu_features = try cpuFeaturesFromLLVM(arch, llvm_cpu_name_z, llvm_cpu_features);
-        switch (cpu_features) {
-            .baseline => return createBaseline(allocator, arch),
-            .cpu => |cpu| return createFromCpu(allocator, arch, cpu),
-            .features => |features| return createFromCpuFeatures(allocator, arch, features),
-        }
-    }
-
-    fn createFromCpu(allocator: *mem.Allocator, arch: Target.Arch, cpu: *const Target.Cpu) !*Self {
-        const self = try allocator.create(Self);
-        errdefer allocator.destroy(self);
-
-        const builtin_str = try std.fmt.allocPrint0(allocator, "CpuFeatures{{ .cpu = &Target.{}.cpu.{} }};\n", .{
-            arch.genericName(),
-            cpu.name,
-        });
-        errdefer allocator.free(builtin_str);
-
-        const cache_hash = try std.fmt.allocPrint0(allocator, "{}\n{}", .{ cpu.name, cpu.features.asBytes() });
-        errdefer allocator.free(cache_hash);
-
-        self.* = Self{
-            .allocator = allocator,
-            .cpu_features = .{ .cpu = cpu },
-            .llvm_cpu_name = if (cpu.llvm_name) |n| n.ptr else null,
-            .llvm_features_str = null,
-            .builtin_str = builtin_str,
-            .cache_hash = cache_hash,
-        };
-        return self;
-    }
-
-    fn initLLVMFeatures(
-        allocator: *mem.Allocator,
-        arch: Target.Arch,
-        feature_set: Target.Cpu.Feature.Set,
-    ) ![*:0]const u8 {
-        var llvm_features_buffer = try std.Buffer.initSize(allocator, 0);
-        defer llvm_features_buffer.deinit();
-
-        const all_features = arch.allFeaturesList();
-        var populated_feature_set = feature_set;
-        if (arch.subArchFeature()) |sub_arch_index| {
-            populated_feature_set.addFeature(sub_arch_index);
-        }
-        populated_feature_set.populateDependencies(all_features);
-        for (all_features) |feature, index| {
-            const llvm_name = feature.llvm_name orelse continue;
-            const plus_or_minus = "-+"[@boolToInt(populated_feature_set.isEnabled(@intCast(u8, index)))];
-            try llvm_features_buffer.appendByte(plus_or_minus);
-            try llvm_features_buffer.append(llvm_name);
-            try llvm_features_buffer.append(",");
-        }
-        assert(mem.endsWith(u8, llvm_features_buffer.toSliceConst(), ","));
-        llvm_features_buffer.shrink(llvm_features_buffer.len() - 1);
-
-        return llvm_features_buffer.toOwnedSlice().ptr;
+    fn createFromNative(allocator: *mem.Allocator) !*Self {
+        const arch = Target.current.getArch();
+        const llvm = @import("llvm.zig");
+        const llvm_cpu_name = llvm.GetHostCPUName();
+        const llvm_cpu_features = llvm.GetNativeFeatures();
+        const cpu_features = try cpuFeaturesFromLLVM(arch, llvm_cpu_name, llvm_cpu_features);
+        return createFromCpuFeatures(allocator, arch, cpu_features);
     }
 
     fn createFromCpuFeatures(
         allocator: *mem.Allocator,
         arch: Target.Arch,
-        feature_set: Target.Cpu.Feature.Set,
+        cpu_features: Target.CpuFeatures,
     ) !*Self {
         const self = try allocator.create(Self);
         errdefer allocator.destroy(self);
 
-        const cache_hash = try std.fmt.allocPrint0(allocator, "\n{}", .{feature_set.asBytes()});
+        const cache_hash = try std.fmt.allocPrint0(allocator, "{}\n{}", .{
+            cpu_features.cpu.name,
+            cpu_features.features.asBytes(),
+        });
         errdefer allocator.free(cache_hash);
 
         const generic_arch_name = arch.genericName();
-        var builtin_str_buffer = try std.Buffer.allocPrint(
-            allocator,
+        var builtin_str_buffer = try std.Buffer.allocPrint(allocator,
             \\CpuFeatures{{
+            \\    .cpu = &Target.{}.cpu.{},
             \\    .features = Target.{}.featureSet(&[_]Target.{}.Feature{{
             \\
-        ,
-            .{ generic_arch_name, generic_arch_name },
-        );
+        , .{
+            generic_arch_name,
+            cpu_features.cpu.name,
+            generic_arch_name,
+            generic_arch_name,
+        });
         defer builtin_str_buffer.deinit();
 
-        for (arch.allFeaturesList()) |feature, index| {
-            if (!feature_set.isEnabled(@intCast(u8, index))) continue;
+        var llvm_features_buffer = try std.Buffer.initSize(allocator, 0);
+        defer llvm_features_buffer.deinit();
+
+        for (arch.allFeaturesList()) |feature, index_usize| {
+            const index = @intCast(Target.Cpu.Feature.Set.Index, index_usize);
+            const is_enabled = cpu_features.features.isEnabled(index);
+
+            if (feature.llvm_name) |llvm_name| {
+                const plus_or_minus = "-+"[@boolToInt(is_enabled)];
+                try llvm_features_buffer.appendByte(plus_or_minus);
+                try llvm_features_buffer.append(llvm_name);
+                try llvm_features_buffer.append(",");
+            }
 
-            // TODO some kind of "zig identifier escape" function rather than
-            // unconditionally using @"" syntax
-            try builtin_str_buffer.append("        .@\"");
-            try builtin_str_buffer.append(feature.name);
-            try builtin_str_buffer.append("\",\n");
+            if (is_enabled) {
+                // TODO some kind of "zig identifier escape" function rather than
+                // unconditionally using @"" syntax
+                try builtin_str_buffer.append("        .@\"");
+                try builtin_str_buffer.append(feature.name);
+                try builtin_str_buffer.append("\",\n");
+            }
         }
 
         try builtin_str_buffer.append(
@@ -774,11 +704,13 @@ const Stage2CpuFeatures = struct {
             \\
         );
 
+        assert(mem.endsWith(u8, llvm_features_buffer.toSliceConst(), ","));
+        llvm_features_buffer.shrink(llvm_features_buffer.len() - 1);
+
         self.* = Self{
             .allocator = allocator,
-            .cpu_features = .{ .features = feature_set },
-            .llvm_cpu_name = null,
-            .llvm_features_str = try initLLVMFeatures(allocator, arch, feature_set),
+            .cpu_features = cpu_features,
+            .llvm_features_str = llvm_features_buffer.toOwnedSlice().ptr,
             .builtin_str = builtin_str_buffer.toOwnedSlice(),
             .cache_hash = cache_hash,
         };
@@ -794,12 +726,13 @@ const Stage2CpuFeatures = struct {
 };
 
 // ABI warning
-export fn stage2_cpu_features_parse_cpu(
+export fn stage2_cpu_features_parse(
     result: **Stage2CpuFeatures,
-    zig_triple: [*:0]const u8,
-    cpu_name: [*:0]const u8,
+    zig_triple: ?[*:0]const u8,
+    cpu_name: ?[*:0]const u8,
+    cpu_features: ?[*:0]const u8,
 ) Error {
-    result.* = parseCpu(zig_triple, cpu_name) catch |err| switch (err) {
+    result.* = stage2ParseCpuFeatures(zig_triple, cpu_name, cpu_features) catch |err| switch (err) {
         error.OutOfMemory => return .OutOfMemory,
         error.UnknownArchitecture => return .UnknownArchitecture,
         error.UnknownSubArchitecture => return .UnknownSubArchitecture,
@@ -807,110 +740,61 @@ export fn stage2_cpu_features_parse_cpu(
         error.UnknownApplicationBinaryInterface => return .UnknownApplicationBinaryInterface,
         error.MissingOperatingSystem => return .MissingOperatingSystem,
         error.MissingArchitecture => return .MissingArchitecture,
-    };
-    return .None;
-}
-
-fn parseCpu(zig_triple: [*:0]const u8, cpu_name_z: [*:0]const u8) !*Stage2CpuFeatures {
-    const cpu_name = mem.toSliceConst(u8, cpu_name_z);
-    const target = try Target.parse(mem.toSliceConst(u8, zig_triple));
-    const arch = target.Cross.arch;
-    const cpu = arch.parseCpu(cpu_name) catch |err| switch (err) {
-        error.UnknownCpu => {
-            std.debug.warn("Unknown CPU: '{}'\nAvailable CPUs for architecture '{}':\n", .{
-                cpu_name,
-                @tagName(arch),
-            });
-            for (arch.allCpus()) |cpu| {
-                std.debug.warn(" {}\n", .{cpu.name});
-            }
-            process.exit(1);
-        },
-        else => |e| return e,
-    };
-    return Stage2CpuFeatures.createFromCpu(std.heap.c_allocator, arch, cpu);
-}
-
-// ABI warning
-export fn stage2_cpu_features_parse_features(
-    result: **Stage2CpuFeatures,
-    zig_triple: [*:0]const u8,
-    features_text: [*:0]const u8,
-) Error {
-    result.* = parseFeatures(zig_triple, features_text) catch |err| switch (err) {
-        error.OutOfMemory => return .OutOfMemory,
+        error.InvalidLlvmCpuFeaturesFormat => return .InvalidLlvmCpuFeaturesFormat,
         error.InvalidCpuFeatures => return .InvalidCpuFeatures,
-        error.UnknownArchitecture => return .UnknownArchitecture,
-        error.UnknownSubArchitecture => return .UnknownSubArchitecture,
-        error.UnknownOperatingSystem => return .UnknownOperatingSystem,
-        error.UnknownApplicationBinaryInterface => return .UnknownApplicationBinaryInterface,
-        error.MissingOperatingSystem => return .MissingOperatingSystem,
-        error.MissingArchitecture => return .MissingArchitecture,
     };
     return .None;
 }
 
-fn parseFeatures(zig_triple: [*:0]const u8, features_text: [*:0]const u8) !*Stage2CpuFeatures {
-    const target = try Target.parse(mem.toSliceConst(u8, zig_triple));
+fn stage2ParseCpuFeatures(
+    zig_triple_oz: ?[*:0]const u8,
+    cpu_name_oz: ?[*:0]const u8,
+    cpu_features_oz: ?[*:0]const u8,
+) !*Stage2CpuFeatures {
+    const zig_triple_z = zig_triple_oz orelse return Stage2CpuFeatures.createFromNative(std.heap.c_allocator);
+    const target = try Target.parse(mem.toSliceConst(u8, zig_triple_z));
     const arch = target.Cross.arch;
-    const set = arch.parseCpuFeatureSet(mem.toSliceConst(u8, features_text)) catch |err| switch (err) {
-        error.UnknownCpuFeature => {
-            std.debug.warn("Unknown CPU features specified.\nAvailable CPU features for architecture '{}':\n", .{
-                @tagName(arch),
-            });
-            for (arch.allFeaturesList()) |feature| {
-                std.debug.warn(" {}\n", .{feature.name});
-            }
-            process.exit(1);
-        },
-        else => |e| return e,
-    };
-    return Stage2CpuFeatures.createFromCpuFeatures(std.heap.c_allocator, arch, set);
-}
 
-// ABI warning
-export fn stage2_cpu_features_baseline(result: **Stage2CpuFeatures, zig_triple: [*:0]const u8) Error {
-    result.* = cpuFeaturesBaseline(zig_triple) catch |err| switch (err) {
-        error.OutOfMemory => return .OutOfMemory,
-        error.UnknownArchitecture => return .UnknownArchitecture,
-        error.UnknownSubArchitecture => return .UnknownSubArchitecture,
-        error.UnknownOperatingSystem => return .UnknownOperatingSystem,
-        error.UnknownApplicationBinaryInterface => return .UnknownApplicationBinaryInterface,
-        error.MissingOperatingSystem => return .MissingOperatingSystem,
-        error.MissingArchitecture => return .MissingArchitecture,
-    };
-    return .None;
-}
-
-fn cpuFeaturesBaseline(zig_triple: [*:0]const u8) !*Stage2CpuFeatures {
-    const target = try Target.parse(mem.toSliceConst(u8, zig_triple));
-    const arch = target.Cross.arch;
-    return Stage2CpuFeatures.createBaseline(std.heap.c_allocator, arch);
-}
+    const cpu = if (cpu_name_oz) |cpu_name_z| blk: {
+        const cpu_name = mem.toSliceConst(u8, cpu_name_z);
+        break :blk arch.parseCpu(cpu_name) catch |err| switch (err) {
+            error.UnknownCpu => {
+                std.debug.warn("Unknown CPU: '{}'\nAvailable CPUs for architecture '{}':\n", .{
+                    cpu_name,
+                    @tagName(arch),
+                });
+                for (arch.allCpus()) |cpu| {
+                    std.debug.warn(" {}\n", .{cpu.name});
+                }
+                process.exit(1);
+            },
+            else => |e| return e,
+        };
+    } else target.Cross.cpu_features.cpu;
+
+    var set = if (cpu_features_oz) |cpu_features_z| blk: {
+        const cpu_features = mem.toSliceConst(u8, cpu_features_z);
+        break :blk arch.parseCpuFeatureSet(cpu, cpu_features) catch |err| switch (err) {
+            error.UnknownCpuFeature => {
+                std.debug.warn(
+                    \\Unknown CPU features specified.
+                    \\Available CPU features for architecture '{}':
+                    \\
+                , .{@tagName(arch)});
+                for (arch.allFeaturesList()) |feature| {
+                    std.debug.warn(" {}\n", .{feature.name});
+                }
+                process.exit(1);
+            },
+            else => |e| return e,
+        };
+    } else cpu.features;
 
-// ABI warning
-export fn stage2_cpu_features_llvm(
-    result: **Stage2CpuFeatures,
-    zig_triple: [*:0]const u8,
-    llvm_cpu_name: ?[*:0]const u8,
-    llvm_cpu_features: ?[*:0]const u8,
-) Error {
-    result.* = Stage2CpuFeatures.createFromLLVM(
-        std.heap.c_allocator,
-        zig_triple,
-        llvm_cpu_name,
-        llvm_cpu_features,
-    ) catch |err| switch (err) {
-        error.OutOfMemory => return .OutOfMemory,
-        error.UnknownArchitecture => return .UnknownArchitecture,
-        error.UnknownSubArchitecture => return .UnknownSubArchitecture,
-        error.InvalidLlvmCpuFeaturesFormat => return .InvalidLlvmCpuFeaturesFormat,
-        error.UnknownOperatingSystem => return .UnknownOperatingSystem,
-        error.UnknownApplicationBinaryInterface => return .UnknownApplicationBinaryInterface,
-        error.MissingOperatingSystem => return .MissingOperatingSystem,
-        error.MissingArchitecture => return .MissingArchitecture,
-    };
-    return .None;
+    set.populateDependencies(arch.allFeaturesList());
+    return Stage2CpuFeatures.createFromCpuFeatures(std.heap.c_allocator, arch, .{
+        .cpu = cpu,
+        .features = set,
+    });
 }
 
 // ABI warning
@@ -935,7 +819,7 @@ export fn stage2_cpu_features_get_builtin_str(
 
 // ABI warning
 export fn stage2_cpu_features_get_llvm_cpu(cpu_features: *const Stage2CpuFeatures) ?[*:0]const u8 {
-    return cpu_features.llvm_cpu_name;
+    return if (cpu_features.cpu_features.cpu.llvm_name) |s| s.ptr else null;
 }
 
 // ABI warning
test/compile_errors.zig
@@ -1,5 +1,6 @@
 const tests = @import("tests.zig");
 const builtin = @import("builtin");
+const Target = @import("std").Target;
 
 pub fn addCases(cases: *tests.CompileErrorContext) void {
     cases.addTest("non-exhaustive enums",
@@ -272,9 +273,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 = tests.CrossTarget{
+        tc.target = Target{
+            .Cross = .{
                 .arch = .wasm32,
+                .cpu_features = Target.Arch.wasm32.getBaselineCpuFeatures(),
                 .os = .wasi,
                 .abi = .none,
             },
@@ -673,9 +675,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 = tests.CrossTarget{
+        tc.target = Target{
+            .Cross = .{
                 .arch = .x86_64,
+                .cpu_features = Target.Arch.x86_64.getBaselineCpuFeatures(),
                 .os = .linux,
                 .abi = .gnu,
             },
test/tests.zig
@@ -38,236 +38,260 @@ const TestTarget = struct {
     disable_native: bool = false,
 };
 
-const test_targets = [_]TestTarget{
-    TestTarget{},
-    TestTarget{
-        .link_libc = true,
-    },
-    TestTarget{
-        .single_threaded = true,
-    },
-
-    TestTarget{
-        .target = Target{
-            .Cross = CrossTarget{
-                .os = .linux,
-                .arch = .x86_64,
-                .abi = .none,
+const test_targets = blk: {
+    // getBaselineCpuFeatures calls populateDependencies which has a O(N ^ 2) algorithm
+    // (where N is roughly 160, which technically makes it O(1), but it adds up to a
+    // lot of branches)
+    @setEvalBranchQuota(50000);
+    break :blk [_]TestTarget{
+        TestTarget{},
+        TestTarget{
+            .link_libc = true,
+        },
+        TestTarget{
+            .single_threaded = true,
+        },
+
+        TestTarget{
+            .target = Target{
+                .Cross = CrossTarget{
+                    .os = .linux,
+                    .arch = .x86_64,
+                    .abi = .none,
+                    .cpu_features = Target.Arch.x86_64.getBaselineCpuFeatures(),
+                },
             },
         },
-    },
-    TestTarget{
-        .target = Target{
-            .Cross = CrossTarget{
-                .os = .linux,
-                .arch = .x86_64,
-                .abi = .gnu,
+        TestTarget{
+            .target = Target{
+                .Cross = CrossTarget{
+                    .os = .linux,
+                    .arch = .x86_64,
+                    .abi = .gnu,
+                    .cpu_features = Target.Arch.x86_64.getBaselineCpuFeatures(),
+                },
             },
+            .link_libc = true,
         },
-        .link_libc = true,
-    },
-    TestTarget{
-        .target = Target{
-            .Cross = CrossTarget{
-                .os = .linux,
-                .arch = .x86_64,
-                .abi = .musl,
+        TestTarget{
+            .target = Target{
+                .Cross = CrossTarget{
+                    .os = .linux,
+                    .arch = .x86_64,
+                    .cpu_features = Target.Arch.x86_64.getBaselineCpuFeatures(),
+                    .abi = .musl,
+                },
             },
+            .link_libc = true,
         },
-        .link_libc = true,
-    },
-
-    TestTarget{
-        .target = Target{
-            .Cross = CrossTarget{
-                .os = .linux,
-                .arch = .i386,
-                .abi = .none,
+
+        TestTarget{
+            .target = Target{
+                .Cross = CrossTarget{
+                    .os = .linux,
+                    .arch = .i386,
+                    .cpu_features = Target.Arch.i386.getBaselineCpuFeatures(),
+                    .abi = .none,
+                },
             },
         },
-    },
-    TestTarget{
-        .target = Target{
-            .Cross = CrossTarget{
-                .os = .linux,
-                .arch = .i386,
-                .abi = .musl,
+        TestTarget{
+            .target = Target{
+                .Cross = CrossTarget{
+                    .os = .linux,
+                    .arch = .i386,
+                    .cpu_features = Target.Arch.i386.getBaselineCpuFeatures(),
+                    .abi = .musl,
+                },
             },
+            .link_libc = true,
         },
-        .link_libc = true,
-    },
-
-    TestTarget{
-        .target = Target{
-            .Cross = CrossTarget{
-                .os = .linux,
-                .arch = builtin.Arch{ .aarch64 = builtin.Arch.Arm64.v8_5a },
-                .abi = .none,
+
+        TestTarget{
+            .target = Target{
+                .Cross = CrossTarget{
+                    .os = .linux,
+                    .arch = Target.Arch{ .aarch64 = .v8_5a },
+                    .cpu_features = (Target.Arch{ .aarch64 = .v8_5a }).getBaselineCpuFeatures(),
+                    .abi = .none,
+                },
             },
         },
-    },
-    TestTarget{
-        .target = Target{
-            .Cross = CrossTarget{
-                .os = .linux,
-                .arch = builtin.Arch{ .aarch64 = builtin.Arch.Arm64.v8_5a },
-                .abi = .musl,
+        TestTarget{
+            .target = Target{
+                .Cross = CrossTarget{
+                    .os = .linux,
+                    .arch = Target.Arch{ .aarch64 = .v8_5a },
+                    .cpu_features = (Target.Arch{ .aarch64 = .v8_5a }).getBaselineCpuFeatures(),
+                    .abi = .musl,
+                },
             },
+            .link_libc = true,
         },
-        .link_libc = true,
-    },
-    TestTarget{
-        .target = Target{
-            .Cross = CrossTarget{
-                .os = .linux,
-                .arch = builtin.Arch{ .aarch64 = builtin.Arch.Arm64.v8_5a },
-                .abi = .gnu,
+        TestTarget{
+            .target = Target{
+                .Cross = CrossTarget{
+                    .os = .linux,
+                    .arch = Target.Arch{ .aarch64 = .v8_5a },
+                    .cpu_features = (Target.Arch{ .aarch64 = .v8_5a }).getBaselineCpuFeatures(),
+                    .abi = .gnu,
+                },
             },
+            .link_libc = true,
         },
-        .link_libc = true,
-    },
-
-    TestTarget{
-        .target = Target{
-            .Cross = CrossTarget{
-                .os = .linux,
-                .arch = builtin.Arch{ .arm = builtin.Arch.Arm32.v8_5a },
-                .abi = .none,
+
+        TestTarget{
+            .target = Target{
+                .Cross = CrossTarget{
+                    .os = .linux,
+                    .arch = Target.Arch{ .arm = .v8_5a },
+                    .cpu_features = (Target.Arch{ .arm = .v8_5a }).getBaselineCpuFeatures(),
+                    .abi = .none,
+                },
             },
         },
-    },
-    TestTarget{
-        .target = Target{
-            .Cross = CrossTarget{
-                .os = .linux,
-                .arch = builtin.Arch{ .arm = builtin.Arch.Arm32.v8_5a },
-                .abi = .musleabihf,
+        TestTarget{
+            .target = Target{
+                .Cross = CrossTarget{
+                    .os = .linux,
+                    .arch = Target.Arch{ .arm = .v8_5a },
+                    .cpu_features = (Target.Arch{ .arm = .v8_5a }).getBaselineCpuFeatures(),
+                    .abi = .musleabihf,
+                },
             },
+            .link_libc = true,
         },
-        .link_libc = true,
-    },
-    // TODO https://github.com/ziglang/zig/issues/3287
-    //TestTarget{
-    //    .target = Target{
-    //        .Cross = CrossTarget{
-    //            .os = .linux,
-    //            .arch = builtin.Arch{ .arm = builtin.Arch.Arm32.v8_5a },
-    //            .abi = .gnueabihf,
-    //        },
-    //    },
-    //    .link_libc = true,
-    //},
-
-    TestTarget{
-        .target = Target{
-            .Cross = CrossTarget{
-                .os = .linux,
-                .arch = .mipsel,
-                .abi = .none,
+        // TODO https://github.com/ziglang/zig/issues/3287
+        //TestTarget{
+        //    .target = Target{
+        //        .Cross = CrossTarget{
+        //            .os = .linux,
+        //            .arch = Target.Arch{ .arm = .v8_5a },
+        //            .cpu_features = (Target.Arch{ .arm = .v8_5a }).getBaselineCpuFeatures(),
+        //            .abi = .gnueabihf,
+        //        },
+        //    },
+        //    .link_libc = true,
+        //},
+
+        TestTarget{
+            .target = Target{
+                .Cross = CrossTarget{
+                    .os = .linux,
+                    .arch = .mipsel,
+                    .cpu_features = Target.Arch.mipsel.getBaselineCpuFeatures(),
+                    .abi = .none,
+                },
             },
         },
-    },
-    TestTarget{
-        .target = Target{
-            .Cross = CrossTarget{
-                .os = .linux,
-                .arch = .mipsel,
-                .abi = .musl,
+        TestTarget{
+            .target = Target{
+                .Cross = CrossTarget{
+                    .os = .linux,
+                    .arch = .mipsel,
+                    .cpu_features = Target.Arch.mipsel.getBaselineCpuFeatures(),
+                    .abi = .musl,
+                },
             },
+            .link_libc = true,
         },
-        .link_libc = true,
-    },
-
-    TestTarget{
-        .target = Target{
-            .Cross = CrossTarget{
-                .os = .macosx,
-                .arch = .x86_64,
-                .abi = .gnu,
+
+        TestTarget{
+            .target = Target{
+                .Cross = CrossTarget{
+                    .os = .macosx,
+                    .arch = .x86_64,
+                    .cpu_features = Target.Arch.x86_64.getBaselineCpuFeatures(),
+                    .abi = .gnu,
+                },
             },
+            // TODO https://github.com/ziglang/zig/issues/3295
+            .disable_native = true,
         },
-        // TODO https://github.com/ziglang/zig/issues/3295
-        .disable_native = true,
-    },
-
-    TestTarget{
-        .target = Target{
-            .Cross = CrossTarget{
-                .os = .windows,
-                .arch = .i386,
-                .abi = .msvc,
+
+        TestTarget{
+            .target = Target{
+                .Cross = CrossTarget{
+                    .os = .windows,
+                    .arch = .i386,
+                    .cpu_features = Target.Arch.i386.getBaselineCpuFeatures(),
+                    .abi = .msvc,
+                },
             },
         },
-    },
-
-    TestTarget{
-        .target = Target{
-            .Cross = CrossTarget{
-                .os = .windows,
-                .arch = .x86_64,
-                .abi = .msvc,
+
+        TestTarget{
+            .target = Target{
+                .Cross = CrossTarget{
+                    .os = .windows,
+                    .arch = .x86_64,
+                    .cpu_features = Target.Arch.x86_64.getBaselineCpuFeatures(),
+                    .abi = .msvc,
+                },
             },
         },
-    },
-
-    TestTarget{
-        .target = Target{
-            .Cross = CrossTarget{
-                .os = .windows,
-                .arch = .i386,
-                .abi = .gnu,
+
+        TestTarget{
+            .target = Target{
+                .Cross = CrossTarget{
+                    .os = .windows,
+                    .arch = .i386,
+                    .cpu_features = Target.Arch.i386.getBaselineCpuFeatures(),
+                    .abi = .gnu,
+                },
             },
+            .link_libc = true,
         },
-        .link_libc = true,
-    },
-
-    TestTarget{
-        .target = Target{
-            .Cross = CrossTarget{
-                .os = .windows,
-                .arch = .x86_64,
-                .abi = .gnu,
+
+        TestTarget{
+            .target = Target{
+                .Cross = CrossTarget{
+                    .os = .windows,
+                    .arch = .x86_64,
+                    .cpu_features = Target.Arch.x86_64.getBaselineCpuFeatures(),
+                    .abi = .gnu,
+                },
             },
+            .link_libc = true,
+        },
+
+        // Do the release tests last because they take a long time
+        TestTarget{
+            .mode = .ReleaseFast,
+        },
+        TestTarget{
+            .link_libc = true,
+            .mode = .ReleaseFast,
         },
-        .link_libc = true,
-    },
-
-    // Do the release tests last because they take a long time
-    TestTarget{
-        .mode = .ReleaseFast,
-    },
-    TestTarget{
-        .link_libc = true,
-        .mode = .ReleaseFast,
-    },
-    TestTarget{
-        .mode = .ReleaseFast,
-        .single_threaded = true,
-    },
-
-    TestTarget{
-        .mode = .ReleaseSafe,
-    },
-    TestTarget{
-        .link_libc = true,
-        .mode = .ReleaseSafe,
-    },
-    TestTarget{
-        .mode = .ReleaseSafe,
-        .single_threaded = true,
-    },
-
-    TestTarget{
-        .mode = .ReleaseSmall,
-    },
-    TestTarget{
-        .link_libc = true,
-        .mode = .ReleaseSmall,
-    },
-    TestTarget{
-        .mode = .ReleaseSmall,
-        .single_threaded = true,
-    },
+        TestTarget{
+            .mode = .ReleaseFast,
+            .single_threaded = true,
+        },
+
+        TestTarget{
+            .mode = .ReleaseSafe,
+        },
+        TestTarget{
+            .link_libc = true,
+            .mode = .ReleaseSafe,
+        },
+        TestTarget{
+            .mode = .ReleaseSafe,
+            .single_threaded = true,
+        },
+
+        TestTarget{
+            .mode = .ReleaseSmall,
+        },
+        TestTarget{
+            .link_libc = true,
+            .mode = .ReleaseSmall,
+        },
+        TestTarget{
+            .mode = .ReleaseSmall,
+            .single_threaded = true,
+        },
+    };
 };
 
 const max_stdout_size = 1 * 1024 * 1024; // 1 MB
test/translate_c.zig
@@ -1,5 +1,6 @@
 const tests = @import("tests.zig");
 const builtin = @import("builtin");
+const Target = @import("std").Target;
 
 pub fn addCases(cases: *tests.TranslateCContext) void {
     cases.add("empty declaration",
@@ -1005,7 +1006,12 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
     });
 
     cases.addWithTarget("Calling convention", tests.Target{
-        .Cross = .{ .os = .linux, .arch = .i386, .abi = .none },
+        .Cross = .{
+            .os = .linux,
+            .arch = .i386,
+            .abi = .none,
+            .cpu_features = Target.Arch.i386.getBaselineCpuFeatures(),
+        },
     },
         \\void __attribute__((fastcall)) foo1(float *a);
         \\void __attribute__((stdcall)) foo2(float *a);
@@ -1021,7 +1027,12 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
     });
 
     cases.addWithTarget("Calling convention", tests.Target{
-        .Cross = .{ .os = .linux, .arch = .{ .arm = .v8_5a }, .abi = .none },
+        .Cross = .{
+            .os = .linux,
+            .arch = .{ .arm = .v8_5a },
+            .abi = .none,
+            .cpu_features = (Target.Arch{ .arm = .v8_5a }).getBaselineCpuFeatures(),
+        },
     },
         \\void __attribute__((pcs("aapcs"))) foo1(float *a);
         \\void __attribute__((pcs("aapcs-vfp"))) foo2(float *a);
@@ -1031,7 +1042,12 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
     });
 
     cases.addWithTarget("Calling convention", tests.Target{
-        .Cross = .{ .os = .linux, .arch = .{ .aarch64 = .v8_5a }, .abi = .none },
+        .Cross = .{
+            .os = .linux,
+            .arch = .{ .aarch64 = .v8_5a },
+            .abi = .none,
+            .cpu_features = (Target.Arch{ .aarch64 = .v8_5a }).getBaselineCpuFeatures(),
+        },
     },
         \\void __attribute__((aarch64_vector_pcs)) foo1(float *a);
     , &[_][]const u8{