Merge pull request #25691 from GasInfinity-Forks/x86_16-gcc

feat: init x86_16 arch via CBE
This commit is contained in:
Alex Rønne Petersen 2025-10-28 10:19:21 +01:00 committed by GitHub
commit 06d9e3bc06
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 96 additions and 33 deletions

View file

@ -74,7 +74,7 @@ pub inline fn bigIntFromFloat(comptime signedness: std.builtin.Signedness, resul
const parts = math.frexp(a);
const significand_bits_adjusted_to_handle_smin = @as(i32, significand_bits) +
@intFromBool(signedness == .signed and parts.exponent == 32 * result.len);
const exponent = @max(parts.exponent - significand_bits_adjusted_to_handle_smin, 0);
const exponent: usize = @intCast(@max(parts.exponent - significand_bits_adjusted_to_handle_smin, 0));
const int: I = @intFromFloat(switch (exponent) {
0 => a,
else => math.ldexp(parts.significand, significand_bits_adjusted_to_handle_smin),

View file

@ -604,7 +604,7 @@ pub fn print(w: *Writer, comptime fmt: []const u8, args: anytype) Error!void {
@compileError("32 arguments max are supported per format call");
}
@setEvalBranchQuota(fmt.len * 1000);
@setEvalBranchQuota(@as(comptime_int, fmt.len) * 1000); // NOTE: We're upcasting as 16-bit usize overflows.
comptime var arg_state: std.fmt.ArgState = .{ .args_len = fields_info.len };
comptime var i = 0;
comptime var literal: []const u8 = "";

View file

@ -1083,7 +1083,7 @@ pub fn toElfMachine(target: *const Target) std.elf.EM {
.sparc => if (target.cpu.has(.sparc, .v9)) .SPARC32PLUS else .SPARC,
.sparc64 => .SPARCV9,
.ve => .VE,
.x86 => .@"386",
.x86_16, .x86 => .@"386",
.x86_64 => .X86_64,
.xcore => .XCORE,
.xtensa, .xtensaeb => .XTENSA,
@ -1154,6 +1154,7 @@ pub fn toCoffMachine(target: *const Target) std.coff.IMAGE.FILE.MACHINE {
.ve,
.wasm32,
.wasm64,
.x86_16,
.xcore,
.xtensa,
.xtensaeb,
@ -1376,6 +1377,7 @@ pub const Cpu = struct {
ve,
wasm32,
wasm64,
x86_16,
x86,
x86_64,
xcore,
@ -1467,7 +1469,7 @@ pub const Cpu = struct {
.spirv32, .spirv64 => .spirv,
.ve => .ve,
.wasm32, .wasm64 => .wasm,
.x86, .x86_64 => .x86,
.x86_16, .x86, .x86_64 => .x86,
.xcore => .xcore,
.xtensa, .xtensaeb => .xtensa,
};
@ -1475,7 +1477,7 @@ pub const Cpu = struct {
pub inline fn isX86(arch: Arch) bool {
return switch (arch) {
.x86, .x86_64 => true,
.x86_16, .x86, .x86_64 => true,
else => false,
};
}
@ -1669,6 +1671,7 @@ pub const Cpu = struct {
.ve,
.wasm32,
.wasm64,
.x86_16,
.x86,
.x86_64,
.xcore,
@ -1789,6 +1792,12 @@ pub const Cpu = struct {
.x86_interrupt,
=> &.{.x86},
.x86_16_cdecl,
.x86_16_stdcall,
.x86_16_regparmcall,
.x86_16_interrupt,
=> &.{.x86_16},
.aarch64_aapcs,
.aarch64_aapcs_darwin,
.aarch64_aapcs_win,
@ -1971,6 +1980,7 @@ pub const Cpu = struct {
.riscv64, .riscv64be => &riscv.cpu.generic_rv64,
.sparc64 => &sparc.cpu.v9, // SPARC can only be 64-bit from v9 and up.
.wasm32, .wasm64 => &wasm.cpu.mvp,
.x86_16 => &x86.cpu.i86,
.x86 => &x86.cpu.i386,
.x86_64 => &x86.cpu.x86_64,
inline else => |a| &@field(Target, @tagName(a.family())).cpu.generic,
@ -2237,7 +2247,10 @@ pub fn supportsAddressSpace(
return switch (address_space) {
.generic => true,
.fs, .gs, .ss => (arch == .x86_64 or arch == .x86) and (context == null or context == .pointer),
.fs, .gs, .ss => (arch == .x86_64 or arch == .x86 or arch == .x86_16) and (context == null or context == .pointer),
// Technically x86 can use segmentation...
.far => (arch == .x86_16),
.flash, .flash1, .flash2, .flash3, .flash4, .flash5 => arch == .avr, // TODO this should also check how many flash banks the cpu has
.cog, .hub => arch == .propeller,
.lut => arch == .propeller and std.Target.propeller.featureSetHas(target.cpu.features, .p2),
@ -2800,6 +2813,7 @@ pub fn ptrBitWidth_arch_abi(cpu_arch: Cpu.Arch, abi: Abi) u16 {
return switch (cpu_arch) {
.avr,
.msp430,
.x86_16,
=> 16,
.arc,
@ -3013,7 +3027,7 @@ pub fn cTypeByteSize(t: *const Target, c_type: CType) u16 {
pub fn cTypeBitSize(target: *const Target, c_type: CType) u16 {
switch (target.os.tag) {
.freestanding, .other => switch (target.cpu.arch) {
.msp430 => switch (c_type) {
.msp430, .x86_16 => switch (c_type) {
.char => return 8,
.short, .ushort, .int, .uint => return 16,
.float, .long, .ulong => return 32,
@ -3369,6 +3383,7 @@ pub fn cTypeAlignment(target: *const Target, c_type: CType) u16 {
std.math.ceilPowerOfTwoAssert(u16, (cTypeBitSize(target, c_type) + 7) / 8),
@as(u16, switch (target.cpu.arch) {
.msp430,
.x86_16,
=> 2,
.arc,
@ -3476,7 +3491,7 @@ pub fn cTypePreferredAlignment(target: *const Target, c_type: CType) u16 {
return @min(
std.math.ceilPowerOfTwoAssert(u16, (cTypeBitSize(target, c_type) + 7) / 8),
@as(u16, switch (target.cpu.arch) {
.msp430 => 2,
.x86_16, .msp430 => 2,
.arc,
.arceb,
@ -3548,7 +3563,7 @@ pub fn cMaxIntAlignment(target: *const Target) u16 {
return switch (target.cpu.arch) {
.avr => 1,
.msp430 => 2,
.msp430, .x86_16 => 2,
.arc,
.arceb,
@ -3625,6 +3640,7 @@ pub fn cCallingConvention(target: *const Target) ?std.builtin.CallingConvention
.windows, .uefi => .{ .x86_win = .{} },
else => .{ .x86_sysv = .{} },
},
.x86_16 => .{ .x86_16_cdecl = .{} },
.aarch64, .aarch64_be => if (target.os.tag.isDarwin())
.{ .aarch64_aapcs_darwin = .{} }
else switch (target.os.tag) {

View file

@ -3081,6 +3081,11 @@ pub const cpu = struct {
.xsaveopt,
}),
};
pub const @"i86": CpuModel = .{
.name = "i86",
.llvm_name = null,
.features = featureSet(&[_]Feature{}),
};
pub const @"i386": CpuModel = .{
.name = "i386",
.llvm_name = "i386",

View file

@ -223,6 +223,13 @@ pub const CallingConvention = union(enum(u8)) {
x86_vectorcall: CommonOptions,
x86_interrupt: CommonOptions,
// Calling conventions for the `x86_16` architecture.
x86_16_cdecl: CommonOptions,
x86_16_stdcall: CommonOptions,
x86_16_regparmcall: CommonOptions,
x86_16_interrupt: CommonOptions,
// Calling conventions for the `aarch64` and `aarch64_be` architectures.
aarch64_aapcs: CommonOptions,
aarch64_aapcs_darwin: CommonOptions,
@ -523,6 +530,10 @@ pub const AddressSpace = enum(u5) {
fs,
ss,
// x86_16 extra address spaces.
/// Allows addressing the entire address space by storing both segment and offset.
far,
// GPU address spaces.
global,
constant,

View file

@ -1,5 +1,5 @@
pub const Clobbers = switch (@import("builtin").cpu.arch) {
.x86, .x86_64 => packed struct {
.x86_16, .x86, .x86_64 => packed struct {
/// Whether the inline assembly code may perform stores to memory
/// addresses other than those derived from input pointer provenance.
memory: bool = false,

View file

@ -4634,25 +4634,28 @@ pub const TEB = extern struct {
};
comptime {
// Offsets taken from WinDbg info and Geoff Chappell[1] (RIP)
// [1]: https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/teb/index.htm
assert(@offsetOf(TEB, "NtTib") == 0x00);
if (@sizeOf(usize) == 4) {
assert(@offsetOf(TEB, "EnvironmentPointer") == 0x1C);
assert(@offsetOf(TEB, "ClientId") == 0x20);
assert(@offsetOf(TEB, "ActiveRpcHandle") == 0x28);
assert(@offsetOf(TEB, "ThreadLocalStoragePointer") == 0x2C);
assert(@offsetOf(TEB, "ProcessEnvironmentBlock") == 0x30);
assert(@offsetOf(TEB, "LastErrorValue") == 0x34);
assert(@offsetOf(TEB, "TlsSlots") == 0xe10);
} else if (@sizeOf(usize) == 8) {
assert(@offsetOf(TEB, "EnvironmentPointer") == 0x38);
assert(@offsetOf(TEB, "ClientId") == 0x40);
assert(@offsetOf(TEB, "ActiveRpcHandle") == 0x50);
assert(@offsetOf(TEB, "ThreadLocalStoragePointer") == 0x58);
assert(@offsetOf(TEB, "ProcessEnvironmentBlock") == 0x60);
assert(@offsetOf(TEB, "LastErrorValue") == 0x68);
assert(@offsetOf(TEB, "TlsSlots") == 0x1480);
// XXX: Without this check we cannot use `std.Io.Writer` on 16-bit platforms. `std.fmt.bufPrint` will hit the unreachable in `PEB.GdiHandleBuffer` without this guard.
if (builtin.os.tag == .windows) {
// Offsets taken from WinDbg info and Geoff Chappell[1] (RIP)
// [1]: https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/teb/index.htm
assert(@offsetOf(TEB, "NtTib") == 0x00);
if (@sizeOf(usize) == 4) {
assert(@offsetOf(TEB, "EnvironmentPointer") == 0x1C);
assert(@offsetOf(TEB, "ClientId") == 0x20);
assert(@offsetOf(TEB, "ActiveRpcHandle") == 0x28);
assert(@offsetOf(TEB, "ThreadLocalStoragePointer") == 0x2C);
assert(@offsetOf(TEB, "ProcessEnvironmentBlock") == 0x30);
assert(@offsetOf(TEB, "LastErrorValue") == 0x34);
assert(@offsetOf(TEB, "TlsSlots") == 0xe10);
} else if (@sizeOf(usize) == 8) {
assert(@offsetOf(TEB, "EnvironmentPointer") == 0x38);
assert(@offsetOf(TEB, "ClientId") == 0x40);
assert(@offsetOf(TEB, "ActiveRpcHandle") == 0x50);
assert(@offsetOf(TEB, "ThreadLocalStoragePointer") == 0x58);
assert(@offsetOf(TEB, "ProcessEnvironmentBlock") == 0x60);
assert(@offsetOf(TEB, "LastErrorValue") == 0x68);
assert(@offsetOf(TEB, "TlsSlots") == 0x1480);
}
}
}

View file

@ -374,6 +374,11 @@ pub fn resolveTargetQuery(query: Target.Query) DetectError!Target {
// However, the "mode" flags can be used as overrides, so if the user explicitly
// sets one of them, that takes precedence.
switch (query_cpu_arch) {
.x86_16 => {
cpu.features.addFeature(
@intFromEnum(Target.x86.Feature.@"16bit_mode"),
);
},
.x86 => {
if (!Target.x86.featureSetHasAny(query.cpu_features_add, .{
.@"16bit_mode", .@"32bit_mode",

View file

@ -472,8 +472,9 @@ fn eqlIgnoreCase(ignore_case: bool, a: []const u8, b: []const u8) bool {
}
}
pub fn intByteSize(target: *const std.Target, bits: u16) u19 {
return std.mem.alignForward(u19, @intCast((@as(u17, bits) + 7) / 8), intAlignment(target, bits));
pub fn intByteSize(target: *const std.Target, bits: u16) u16 {
const previous_aligned = std.mem.alignBackward(u16, bits, 8);
return std.mem.alignForward(u16, @divExact(previous_aligned, 8) + @intFromBool(previous_aligned != bits), intAlignment(target, bits));
}
pub fn intAlignment(target: *const std.Target, bits: u16) u16 {

View file

@ -74,6 +74,9 @@
#elif defined (__x86_64__) || (defined(zig_msvc) && defined(_M_X64))
#define zig_x86_64
#define zig_x86
#elif defined(__I86__)
#define zig_x86_16
#define zig_x86
#endif
#if defined(zig_msvc) || __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
@ -400,6 +403,8 @@
#define zig_trap() __asm__ volatile("j 0x2")
#elif defined(zig_sparc)
#define zig_trap() __asm__ volatile("illtrap")
#elif defined(zig_x86_16)
#define zig_trap() __asm__ volatile("int $0x3")
#elif defined(zig_x86)
#define zig_trap() __asm__ volatile("ud2")
#else
@ -4219,7 +4224,7 @@ static inline void zig_loongarch_cpucfg(uint32_t word, uint32_t* result) {
#endif
}
#elif defined(zig_x86)
#elif defined(zig_x86) && !defined(zig_x86_16)
static inline void zig_x86_cpuid(uint32_t leaf_id, uint32_t subid, uint32_t* eax, uint32_t* ebx, uint32_t* ecx, uint32_t* edx) {
#if defined(zig_msvc)

View file

@ -9034,6 +9034,7 @@ pub fn handleExternLibName(
/// Any calling conventions not included here are either not yet verified to work with variadic
/// functions or there are no more other calling conventions that support variadic functions.
const calling_conventions_supporting_var_args = [_]std.builtin.CallingConvention.Tag{
.x86_16_cdecl,
.x86_64_sysv,
.x86_64_x32,
.x86_64_win,

View file

@ -4406,6 +4406,10 @@ pub fn callconvSupported(zcu: *Zcu, cc: std.builtin.CallingConvention) union(enu
}
}
break :ok switch (cc) {
.x86_16_cdecl,
.x86_16_stdcall,
.x86_16_regparmcall,
.x86_16_interrupt,
.x86_64_sysv,
.x86_64_win,
.x86_64_vectorcall,

View file

@ -8055,9 +8055,11 @@ fn toCallingConvention(cc: std.builtin.CallingConvention, zcu: *Zcu) ?[]const u8
return switch (cc) {
.auto, .naked => null,
.x86_16_cdecl => "cdecl",
.x86_16_regparmcall => "regparmcall",
.x86_64_sysv, .x86_sysv => "sysv_abi",
.x86_64_win, .x86_win => "ms_abi",
.x86_stdcall => "stdcall",
.x86_16_stdcall, .x86_stdcall => "stdcall",
.x86_fastcall => "fastcall",
.x86_thiscall => "thiscall",
@ -8127,6 +8129,7 @@ fn toCallingConvention(cc: std.builtin.CallingConvention, zcu: *Zcu) ?[]const u8
.csky_interrupt,
.m68k_interrupt,
.msp430_interrupt,
.x86_16_interrupt,
.x86_interrupt,
.x86_64_interrupt,
=> "interrupt",

View file

@ -117,6 +117,7 @@ pub fn targetTriple(allocator: Allocator, target: *const std.Target) ![]const u8
.propeller,
.sh,
.sheb,
.x86_16,
.xtensaeb,
=> unreachable, // Gated by hasLlvmSupport().
};
@ -493,6 +494,7 @@ pub fn dataLayout(target: *const std.Target) []const u8 {
.propeller,
.sh,
.sheb,
.x86_16,
.xtensaeb,
=> unreachable, // Gated by hasLlvmSupport().
};
@ -11902,6 +11904,10 @@ fn toLlvmCallConvTag(cc_tag: std.builtin.CallingConvention.Tag, target: *const s
// All the calling conventions which LLVM does not have a general representation for.
// Note that these are often still supported through the `cCallingConvention` path above via `ccc`.
.x86_16_cdecl,
.x86_16_stdcall,
.x86_16_regparmcall,
.x86_16_interrupt,
.x86_sysv,
.x86_win,
.x86_thiscall_mingw,
@ -13131,6 +13137,7 @@ pub fn initializeLLVMTarget(arch: std.Target.Cpu.Arch) void {
.propeller,
.sh,
.sheb,
.x86_16,
.xtensaeb,
=> unreachable,
}

View file

@ -927,6 +927,7 @@ pub fn storageClass(module: *Module, as: std.builtin.AddressSpace) spec.StorageC
.gs,
.fs,
.ss,
.far,
.param,
.flash,
.flash1,

View file

@ -227,6 +227,7 @@ pub fn hasLlvmSupport(target: *const std.Target, ofmt: std.Target.ObjectFormat)
.propeller,
.sh,
.sheb,
.x86_16,
.xtensaeb,
=> false,
};