const std = @import("std");
const assert = std.debug.assert;

const Type = @import("Type.zig");
const AddressSpace = std.builtin.AddressSpace;
const Alignment = @import("InternPool.zig").Alignment;
const Compilation = @import("Compilation.zig");
const Feature = @import("Zcu.zig").Feature;

pub const default_stack_protector_buffer_size = 4;

pub fn cannotDynamicLink(target: std.Target) bool {
    return switch (target.os.tag) {
        .freestanding => true,
        else => target.cpu.arch.isSpirV(),
    };
}

/// On Darwin, we always link libSystem which contains libc.
/// Similarly on FreeBSD and NetBSD we always link system libc
/// since this is the stable syscall interface.
pub fn osRequiresLibC(target: std.Target) bool {
    return target.os.requiresLibC();
}

pub fn libcNeedsLibUnwind(target: std.Target) bool {
    return switch (target.os.tag) {
        .macos,
        .ios,
        .watchos,
        .tvos,
        .visionos,
        .freestanding,
        .wasi, // Wasm/WASI currently doesn't offer support for libunwind, so don't link it.
        => false,

        .windows => target.abi.isGnu(),
        else => true,
    };
}

pub fn requiresPIE(target: std.Target) bool {
    return target.abi.isAndroid() or target.os.tag.isDarwin() or target.os.tag == .openbsd;
}

/// This function returns whether non-pic code is completely invalid on the given target.
pub fn requiresPIC(target: std.Target, linking_libc: bool) bool {
    return target.abi.isAndroid() or
        target.os.tag == .windows or target.os.tag == .uefi or
        osRequiresLibC(target) or
        (linking_libc and target.isGnuLibC());
}

pub fn picLevel(target: std.Target) u32 {
    // MIPS always uses PIC level 1; other platforms vary in their default PIC levels, but they
    // support both level 1 and 2, in which case we prefer 2.
    return if (target.cpu.arch.isMIPS()) 1 else 2;
}

/// This is not whether the target supports Position Independent Code, but whether the -fPIC
/// C compiler argument is valid to Clang.
pub fn supports_fpic(target: std.Target) bool {
    return target.os.tag != .windows and target.os.tag != .uefi;
}

pub fn alwaysSingleThreaded(target: std.Target) bool {
    _ = target;
    return false;
}

pub fn defaultSingleThreaded(target: std.Target) bool {
    switch (target.cpu.arch) {
        .wasm32, .wasm64 => return true,
        else => {},
    }
    switch (target.os.tag) {
        .haiku => return true,
        else => {},
    }
    return false;
}

pub fn hasValgrindSupport(target: std.Target) bool {
    // We can't currently output the necessary Valgrind client request assembly when using the C
    // backend and compiling with an MSVC-like compiler.
    const ofmt_c_msvc = (target.abi == .msvc or target.abi == .itanium) and target.ofmt == .c;

    return switch (target.cpu.arch) {
        .arm, .armeb, .thumb, .thumbeb => switch (target.os.tag) {
            .linux => true,
            else => false,
        },
        .aarch64, .aarch64_be => switch (target.os.tag) {
            .linux, .freebsd => true,
            else => false,
        },
        .mips, .mipsel, .mips64, .mips64el => switch (target.os.tag) {
            .linux => true,
            else => false,
        },
        .powerpc, .powerpcle, .powerpc64, .powerpc64le => switch (target.os.tag) {
            .linux => true,
            else => false,
        },
        .s390x => switch (target.os.tag) {
            .linux => true,
            else => false,
        },
        .x86 => switch (target.os.tag) {
            .linux, .freebsd, .solaris, .illumos => true,
            .windows => !ofmt_c_msvc,
            else => false,
        },
        .x86_64 => switch (target.os.tag) {
            .linux => target.abi != .gnux32 and target.abi != .muslx32,
            .freebsd, .solaris, .illumos => true,
            .windows => !ofmt_c_msvc,
            else => false,
        },
        else => false,
    };
}

/// The set of targets that LLVM has non-experimental support for.
/// Used to select between LLVM backend and self-hosted backend when compiling in
/// release modes.
pub fn hasLlvmSupport(target: std.Target, ofmt: std.Target.ObjectFormat) bool {
    switch (ofmt) {
        // LLVM does not support these object formats:
        .c,
        .plan9,
        => return false,

        .coff,
        .elf,
        .goff,
        .hex,
        .macho,
        .nvptx,
        .spirv,
        .raw,
        .wasm,
        .xcoff,
        => {},
    }

    return switch (target.cpu.arch) {
        .arm,
        .armeb,
        .aarch64,
        .aarch64_be,
        .arc,
        .avr,
        .bpfel,
        .bpfeb,
        .csky,
        .hexagon,
        .loongarch32,
        .loongarch64,
        .m68k,
        .mips,
        .mipsel,
        .mips64,
        .mips64el,
        .msp430,
        .powerpc,
        .powerpcle,
        .powerpc64,
        .powerpc64le,
        .amdgcn,
        .riscv32,
        .riscv64,
        .sparc,
        .sparc64,
        .s390x,
        .thumb,
        .thumbeb,
        .x86,
        .x86_64,
        .xcore,
        .xtensa,
        .nvptx,
        .nvptx64,
        .lanai,
        .wasm32,
        .wasm64,
        .ve,
        => true,

        // An LLVM backend exists but we don't currently support using it.
        .spirv,
        .spirv32,
        .spirv64,
        => false,

        // No LLVM backend exists.
        .kalimba,
        .propeller,
        => false,
    };
}

/// The set of targets that Zig supports using LLD to link for.
pub fn hasLldSupport(ofmt: std.Target.ObjectFormat) bool {
    return switch (ofmt) {
        .elf, .coff, .wasm => true,
        else => false,
    };
}

/// The set of targets that our own self-hosted backends have robust support for.
/// Used to select between LLVM backend and self-hosted backend when compiling in
/// debug mode. A given target should only return true here if it is passing greater
/// than or equal to the number of behavior tests as the respective LLVM backend.
pub fn selfHostedBackendIsAsRobustAsLlvm(target: std.Target) bool {
    _ = target;
    return false;
}

pub fn supportsStackProbing(target: std.Target) bool {
    return target.os.tag != .windows and target.os.tag != .uefi and
        (target.cpu.arch == .x86 or target.cpu.arch == .x86_64);
}

pub fn supportsStackProtector(target: std.Target, backend: std.builtin.CompilerBackend) bool {
    switch (target.os.tag) {
        .plan9 => return false,
        else => {},
    }
    switch (target.cpu.arch) {
        .spirv, .spirv32, .spirv64 => return false,
        else => {},
    }
    return switch (backend) {
        .stage2_llvm => true,
        else => false,
    };
}

pub fn clangSupportsStackProtector(target: std.Target) bool {
    return switch (target.cpu.arch) {
        .spirv, .spirv32, .spirv64 => return false,
        else => true,
    };
}

pub fn libcProvidesStackProtector(target: std.Target) bool {
    return !target.isMinGW() and target.os.tag != .wasi and !target.cpu.arch.isSpirV();
}

pub fn supportsReturnAddress(target: std.Target) bool {
    return switch (target.cpu.arch) {
        .wasm32, .wasm64 => target.os.tag == .emscripten,
        .bpfel, .bpfeb => false,
        .spirv, .spirv32, .spirv64 => false,
        else => true,
    };
}

pub const CompilerRtClassification = enum { none, only_compiler_rt, only_libunwind, both };

pub fn classifyCompilerRtLibName(name: []const u8) CompilerRtClassification {
    if (std.mem.eql(u8, name, "gcc_s")) {
        // libgcc_s includes exception handling functions, so if linking this library
        // is requested, zig needs to instead link libunwind. Otherwise we end up with
        // the linker unable to find `_Unwind_RaiseException` and other related symbols.
        return .both;
    }
    if (std.mem.eql(u8, name, "compiler_rt") or
        std.mem.eql(u8, name, "gcc") or
        std.mem.eql(u8, name, "atomic") or
        std.mem.eql(u8, name, "ssp"))
    {
        return .only_compiler_rt;
    }
    if (std.mem.eql(u8, name, "unwind") or
        std.mem.eql(u8, name, "gcc_eh"))
    {
        return .only_libunwind;
    }
    return .none;
}

pub fn hasDebugInfo(target: std.Target) bool {
    return switch (target.cpu.arch) {
        .nvptx, .nvptx64 => std.Target.nvptx.featureSetHas(target.cpu.features, .ptx75) or
            std.Target.nvptx.featureSetHas(target.cpu.features, .ptx76) or
            std.Target.nvptx.featureSetHas(target.cpu.features, .ptx77) or
            std.Target.nvptx.featureSetHas(target.cpu.features, .ptx78) or
            std.Target.nvptx.featureSetHas(target.cpu.features, .ptx80) or
            std.Target.nvptx.featureSetHas(target.cpu.features, .ptx81),
        .bpfel, .bpfeb => false,
        else => true,
    };
}

pub fn defaultCompilerRtOptimizeMode(target: std.Target) std.builtin.OptimizeMode {
    if (target.cpu.arch.isWasm() and target.os.tag == .freestanding) {
        return .ReleaseSmall;
    } else {
        return .ReleaseFast;
    }
}

pub fn hasRedZone(target: std.Target) bool {
    return switch (target.cpu.arch) {
        .aarch64,
        .aarch64_be,
        .x86,
        .x86_64,
        => true,

        else => false,
    };
}

pub fn libcFullLinkFlags(target: std.Target) []const []const u8 {
    // The linking order of these is significant and should match the order other
    // c compilers such as gcc or clang use.
    const result: []const []const u8 = switch (target.os.tag) {
        .dragonfly, .freebsd, .netbsd, .openbsd => &.{ "-lm", "-lpthread", "-lc", "-lutil" },
        // Solaris releases after 10 merged the threading libraries into libc.
        .solaris, .illumos => &.{ "-lm", "-lsocket", "-lnsl", "-lc" },
        .haiku => &.{ "-lm", "-lroot", "-lpthread", "-lc", "-lnetwork" },
        .linux => switch (target.abi) {
            .android, .androideabi, .ohos, .ohoseabi => &.{ "-lm", "-lc", "-ldl" },
            else => &.{ "-lm", "-lpthread", "-lc", "-ldl", "-lrt", "-lutil" },
        },
        else => &.{},
    };
    return result;
}

pub fn clangMightShellOutForAssembly(target: std.Target) bool {
    // Clang defaults to using the system assembler in some cases.
    return target.cpu.arch.isNvptx() or target.cpu.arch == .xcore;
}

/// Each backend architecture in Clang has a different codepath which may or may not
/// support an -mcpu flag.
pub fn clangAssemblerSupportsMcpuArg(target: std.Target) bool {
    return switch (target.cpu.arch) {
        .arm, .armeb, .thumb, .thumbeb => true,
        else => false,
    };
}

/// Some experimental or poorly-maintained LLVM targets do not properly process CPU models in their
/// Clang driver code. For these, we should omit the `-Xclang -target-cpu -Xclang <model>` flags.
pub fn clangSupportsTargetCpuArg(target: std.Target) bool {
    return switch (target.cpu.arch) {
        .arc,
        .msp430,
        .ve,
        .xcore,
        .xtensa,
        => false,
        else => true,
    };
}

pub fn clangSupportsFloatAbiArg(target: std.Target) bool {
    return switch (target.cpu.arch) {
        .arm,
        .armeb,
        .thumb,
        .thumbeb,
        .csky,
        .mips,
        .mipsel,
        .mips64,
        .mips64el,
        .powerpc,
        .powerpcle,
        .powerpc64,
        .powerpc64le,
        .s390x,
        .sparc,
        .sparc64,
        => true,
        // We use the target triple for LoongArch.
        .loongarch32, .loongarch64 => false,
        else => false,
    };
}

pub fn clangSupportsNoImplicitFloatArg(target: std.Target) bool {
    return switch (target.cpu.arch) {
        .aarch64,
        .aarch64_be,
        .arm,
        .armeb,
        .thumb,
        .thumbeb,
        .riscv32,
        .riscv64,
        .x86,
        .x86_64,
        => true,
        else => false,
    };
}

pub fn defaultUnwindTables(target: std.Target, libunwind: bool, libtsan: bool) std.builtin.UnwindTables {
    if (target.os.tag == .windows) {
        // The old 32-bit x86 variant of SEH doesn't use tables.
        return if (target.cpu.arch != .x86) .@"async" else .none;
    }
    if (target.os.tag.isDarwin()) return .@"async";
    if (libunwind) return .@"async";
    if (libtsan) return .@"async";
    if (std.debug.Dwarf.abi.supportsUnwinding(target)) return .@"async";
    return .none;
}

pub fn defaultAddressSpace(
    target: std.Target,
    context: enum {
        /// Query the default address space for global constant values.
        global_constant,
        /// Query the default address space for global mutable values.
        global_mutable,
        /// Query the default address space for function-local values.
        local,
        /// Query the default address space for functions themselves.
        function,
    },
) AddressSpace {
    // The default address space for functions on AVR is .flash to produce
    // correct fixups into progmem.
    if (context == .function and target.cpu.arch == .avr) return .flash;
    return .generic;
}

/// Returns true if pointers in `from` can be converted to a pointer in `to`.
pub fn addrSpaceCastIsValid(
    target: std.Target,
    from: AddressSpace,
    to: AddressSpace,
) bool {
    switch (target.cpu.arch) {
        .x86_64, .x86 => return target.cpu.supportsAddressSpace(from, null) and target.cpu.supportsAddressSpace(to, null),
        .nvptx64, .nvptx, .amdgcn => {
            const to_generic = target.cpu.supportsAddressSpace(from, null) and to == .generic;
            const from_generic = target.cpu.supportsAddressSpace(to, null) and from == .generic;
            return to_generic or from_generic;
        },
        else => return from == .generic and to == .generic,
    }
}

/// Under SPIR-V with Vulkan, pointers are not 'real' (physical), but rather 'logical'. Effectively,
/// this means that all such pointers have to be resolvable to a location at compile time, and places
/// a number of restrictions on usage of such pointers. For example, a logical pointer may not be
/// part of a merge (result of a branch) and may not be stored in memory at all. This function returns
/// for a particular architecture and address space wether such pointers are logical.
pub fn arePointersLogical(target: std.Target, as: AddressSpace) bool {
    if (target.os.tag != .vulkan) {
        return false;
    }

    return switch (as) {
        // TODO: Vulkan doesn't support pointers in the generic address space, we
        // should remove this case but this requires a change in defaultAddressSpace().
        // For now, at least disable them from being regarded as physical.
        .generic => true,
        // For now, all global pointers are represented using PhysicalStorageBuffer, so these are real
        // pointers.
        .global => false,
        // TODO: Allowed with VK_KHR_variable_pointers.
        .shared => true,
        .constant, .local, .input, .output, .uniform, .push_constant, .storage_buffer => true,
        else => unreachable,
    };
}

pub fn isDynamicAMDGCNFeature(target: std.Target, feature: std.Target.Cpu.Feature) bool {
    if (target.cpu.arch != .amdgcn) return false;

    const sramecc_only = &[_]*const std.Target.Cpu.Model{
        &std.Target.amdgcn.cpu.gfx1010,
        &std.Target.amdgcn.cpu.gfx1011,
        &std.Target.amdgcn.cpu.gfx1012,
        &std.Target.amdgcn.cpu.gfx1013,
    };
    const xnack_or_sramecc = &[_]*const std.Target.Cpu.Model{
        &std.Target.amdgcn.cpu.gfx1030,
        &std.Target.amdgcn.cpu.gfx1031,
        &std.Target.amdgcn.cpu.gfx1032,
        &std.Target.amdgcn.cpu.gfx1033,
        &std.Target.amdgcn.cpu.gfx1034,
        &std.Target.amdgcn.cpu.gfx1035,
        &std.Target.amdgcn.cpu.gfx1036,
        &std.Target.amdgcn.cpu.gfx1100,
        &std.Target.amdgcn.cpu.gfx1101,
        &std.Target.amdgcn.cpu.gfx1102,
        &std.Target.amdgcn.cpu.gfx1103,
        &std.Target.amdgcn.cpu.gfx1150,
        &std.Target.amdgcn.cpu.gfx1151,
        &std.Target.amdgcn.cpu.gfx1152,
        &std.Target.amdgcn.cpu.gfx1200,
        &std.Target.amdgcn.cpu.gfx1201,
    };
    const feature_tag: std.Target.amdgcn.Feature = @enumFromInt(feature.index);

    if (feature_tag == .sramecc) {
        if (std.mem.indexOfScalar(
            *const std.Target.Cpu.Model,
            sramecc_only ++ xnack_or_sramecc,
            target.cpu.model,
        )) |_| return true;
    }
    if (feature_tag == .xnack) {
        if (std.mem.indexOfScalar(
            *const std.Target.Cpu.Model,
            xnack_or_sramecc,
            target.cpu.model,
        )) |_| return true;
    }

    return false;
}

pub fn llvmMachineAbi(target: std.Target) ?[:0]const u8 {
    // LLD does not support ELFv1. Rather than having LLVM produce ELFv1 code and then linking it
    // into a broken ELFv2 binary, just force LLVM to use ELFv2 as well. This will break when glibc
    // is linked as glibc only supports ELFv2 for little endian, but there's nothing we can do about
    // that. With this hack, `powerpc64-linux-none` will at least work.
    //
    // Once our self-hosted linker can handle both ABIs, this hack should go away.
    if (target.cpu.arch == .powerpc64) return "elfv2";

    return switch (target.cpu.arch) {
        .arm, .armeb, .thumb, .thumbeb => "aapcs",
        // TODO: `muslsf` and `muslf32` in LLVM 20.
        .loongarch64 => switch (target.abi) {
            .gnusf => "lp64s",
            .gnuf32 => "lp64f",
            else => "lp64d",
        },
        .loongarch32 => switch (target.abi) {
            .gnusf => "ilp32s",
            .gnuf32 => "ilp32f",
            else => "ilp32d",
        },
        .mips, .mipsel => "o32",
        .mips64, .mips64el => switch (target.abi) {
            .gnuabin32, .muslabin32 => "n32",
            else => "n64",
        },
        .powerpc64 => switch (target.os.tag) {
            .freebsd => if (target.os.version_range.semver.isAtLeast(.{ .major = 13, .minor = 0, .patch = 0 }) orelse false)
                "elfv2"
            else
                "elfv1",
            .openbsd => "elfv2",
            else => if (target.abi.isMusl()) "elfv2" else "elfv1",
        },
        .powerpc64le => "elfv2",
        .riscv64 => b: {
            const featureSetHas = std.Target.riscv.featureSetHas;
            break :b if (featureSetHas(target.cpu.features, .e))
                "lp64e"
            else if (featureSetHas(target.cpu.features, .d))
                "lp64d"
            else if (featureSetHas(target.cpu.features, .f))
                "lp64f"
            else
                "lp64";
        },
        .riscv32 => b: {
            const featureSetHas = std.Target.riscv.featureSetHas;
            break :b if (featureSetHas(target.cpu.features, .e))
                "ilp32e"
            else if (featureSetHas(target.cpu.features, .d))
                "ilp32d"
            else if (featureSetHas(target.cpu.features, .f))
                "ilp32f"
            else
                "ilp32";
        },
        else => null,
    };
}

/// This function returns 1 if function alignment is not observable or settable. Note that this
/// value will not necessarily match the backend's default function alignment (e.g. for LLVM).
pub fn defaultFunctionAlignment(target: std.Target) Alignment {
    // Overrides of the minimum for performance.
    return switch (target.cpu.arch) {
        .csky,
        .thumb,
        .thumbeb,
        .xcore,
        => .@"4",
        .aarch64,
        .aarch64_be,
        .hexagon,
        .powerpc,
        .powerpcle,
        .powerpc64,
        .powerpc64le,
        .s390x,
        .x86,
        .x86_64,
        => .@"16",
        .loongarch32,
        .loongarch64,
        => .@"32",
        else => minFunctionAlignment(target),
    };
}

/// This function returns 1 if function alignment is not observable or settable.
pub fn minFunctionAlignment(target: std.Target) Alignment {
    return switch (target.cpu.arch) {
        .riscv32,
        .riscv64,
        => if (std.Target.riscv.featureSetHasAny(target.cpu.features, .{ .c, .zca })) .@"2" else .@"4",
        .thumb,
        .thumbeb,
        .csky,
        .m68k,
        .msp430,
        .s390x,
        .xcore,
        => .@"2",
        .arc,
        .arm,
        .armeb,
        .aarch64,
        .aarch64_be,
        .hexagon,
        .lanai,
        .loongarch32,
        .loongarch64,
        .mips,
        .mipsel,
        .powerpc,
        .powerpcle,
        .powerpc64,
        .powerpc64le,
        .sparc,
        .sparc64,
        .xtensa,
        => .@"4",
        .bpfel,
        .bpfeb,
        .mips64,
        .mips64el,
        => .@"8",
        .ve,
        => .@"16",
        else => .@"1",
    };
}

pub fn supportsFunctionAlignment(target: std.Target) bool {
    return switch (target.cpu.arch) {
        .nvptx,
        .nvptx64,
        .spirv,
        .spirv32,
        .spirv64,
        .wasm32,
        .wasm64,
        => false,
        else => true,
    };
}

pub fn functionPointerMask(target: std.Target) ?u64 {
    // 32-bit Arm uses the LSB to mean that the target function contains Thumb code.
    // MIPS uses the LSB to mean that the target function contains MIPS16/microMIPS code.
    return if (target.cpu.arch.isArm() or target.cpu.arch.isMIPS32())
        ~@as(u32, 1)
    else if (target.cpu.arch.isMIPS64())
        ~@as(u64, 1)
    else
        null;
}

pub fn supportsTailCall(target: std.Target, backend: std.builtin.CompilerBackend) bool {
    switch (backend) {
        .stage1, .stage2_llvm => return @import("codegen/llvm.zig").supportsTailCall(target),
        .stage2_c => return true,
        else => return false,
    }
}

pub fn supportsThreads(target: std.Target, backend: std.builtin.CompilerBackend) bool {
    return switch (backend) {
        .stage2_x86_64 => target.ofmt == .macho or target.ofmt == .elf,
        else => true,
    };
}

pub fn libcFloatPrefix(float_bits: u16) []const u8 {
    return switch (float_bits) {
        16, 80 => "__",
        32, 64, 128 => "",
        else => unreachable,
    };
}

pub fn libcFloatSuffix(float_bits: u16) []const u8 {
    return switch (float_bits) {
        16 => "h", // Non-standard
        32 => "f",
        64 => "",
        80 => "x", // Non-standard
        128 => "q", // Non-standard (mimics convention in GCC libquadmath)
        else => unreachable,
    };
}

pub fn compilerRtFloatAbbrev(float_bits: u16) []const u8 {
    return switch (float_bits) {
        16 => "h",
        32 => "s",
        64 => "d",
        80 => "x",
        128 => "t",
        else => unreachable,
    };
}

pub fn compilerRtIntAbbrev(bits: u16) []const u8 {
    return switch (bits) {
        16 => "h",
        32 => "s",
        64 => "d",
        128 => "t",
        else => "o", // Non-standard
    };
}

pub fn fnCallConvAllowsZigTypes(cc: std.builtin.CallingConvention) bool {
    return switch (cc) {
        .auto, .@"async", .@"inline" => true,
        // For now we want to authorize PTX kernel to use zig objects, even if
        // we end up exposing the ABI. The goal is to experiment with more
        // integrated CPU/GPU code.
        .nvptx_kernel => true,
        else => false,
    };
}

pub fn zigBackend(target: std.Target, use_llvm: bool) std.builtin.CompilerBackend {
    if (use_llvm) return .stage2_llvm;
    if (target.ofmt == .c) return .stage2_c;
    return switch (target.cpu.arch) {
        .wasm32, .wasm64 => .stage2_wasm,
        .arm, .armeb, .thumb, .thumbeb => .stage2_arm,
        .x86_64 => .stage2_x86_64,
        .x86 => .stage2_x86,
        .aarch64, .aarch64_be => .stage2_aarch64,
        .riscv64 => .stage2_riscv64,
        .sparc64 => .stage2_sparc64,
        .spirv64 => .stage2_spirv64,
        else => .other,
    };
}

pub inline fn backendSupportsFeature(backend: std.builtin.CompilerBackend, comptime feature: Feature) bool {
    return switch (feature) {
        .panic_fn => switch (backend) {
            .stage2_c,
            .stage2_llvm,
            .stage2_x86_64,
            .stage2_riscv64,
            => true,
            else => false,
        },
        .error_return_trace => switch (backend) {
            .stage2_llvm, .stage2_x86_64 => true,
            else => false,
        },
        .is_named_enum_value => switch (backend) {
            .stage2_llvm, .stage2_x86_64 => true,
            else => false,
        },
        .error_set_has_value => switch (backend) {
            .stage2_llvm, .stage2_wasm, .stage2_x86_64 => true,
            else => false,
        },
        .field_reordering => switch (backend) {
            .stage2_c, .stage2_llvm, .stage2_x86_64 => true,
            else => false,
        },
        .safety_checked_instructions => switch (backend) {
            .stage2_llvm => true,
            else => false,
        },
        .separate_thread => switch (backend) {
            .stage2_llvm => false,
            else => true,
        },
        .all_vector_instructions => switch (backend) {
            .stage2_x86_64 => true,
            else => false,
        },
    };
}
