From 914acf13cba7adc4e55734d85f60ab09e7fdf8ac Mon Sep 17 00:00:00 2001 From: GasInfinity Date: Mon, 27 Oct 2025 11:13:25 +0100 Subject: [PATCH 1/4] chore: make `std.zig.target.intByteSize` return an `u16` --- lib/std/zig/target.zig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/std/zig/target.zig b/lib/std/zig/target.zig index 651adfcfe2..13eb7f8e28 100644 --- a/lib/std/zig/target.zig +++ b/lib/std/zig/target.zig @@ -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 { From 55c0693c4afb8afab9ce97ef0ad40450e6156d0c Mon Sep 17 00:00:00 2001 From: GasInfinity Date: Mon, 27 Oct 2025 11:17:48 +0100 Subject: [PATCH 2/4] fix: make `compiler_rt` and `std.Io.Writer` compile on 16-bit platforms. --- lib/compiler_rt/int_from_float.zig | 2 +- lib/std/Io/Writer.zig | 2 +- lib/std/os/windows.zig | 41 ++++++++++++++++-------------- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/lib/compiler_rt/int_from_float.zig b/lib/compiler_rt/int_from_float.zig index 0c2c73bb42..83de968a2e 100644 --- a/lib/compiler_rt/int_from_float.zig +++ b/lib/compiler_rt/int_from_float.zig @@ -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), diff --git a/lib/std/Io/Writer.zig b/lib/std/Io/Writer.zig index 7ad466d5cb..a525a028d7 100644 --- a/lib/std/Io/Writer.zig +++ b/lib/std/Io/Writer.zig @@ -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 = ""; diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 2e18e8ec16..27b40fcd60 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -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); + } } } From 9d3bd3c502c4a2413c1c83a581b0711eceb37838 Mon Sep 17 00:00:00 2001 From: GasInfinity Date: Mon, 27 Oct 2025 11:19:08 +0100 Subject: [PATCH 3/4] feat: init 16-bit x86 support in `zig.h` --- lib/zig.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/zig.h b/lib/zig.h index e303fef3a1..eebe4cf818 100644 --- a/lib/zig.h +++ b/lib/zig.h @@ -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) From 104c272ae5dc0ab2fc6f81da5918f1b8abf7e131 Mon Sep 17 00:00:00 2001 From: GasInfinity Date: Mon, 27 Oct 2025 11:19:51 +0100 Subject: [PATCH 4/4] feat: init x86_16 arch via CBE --- lib/std/Target.zig | 30 +++++++++++++++++++++++------- lib/std/Target/x86.zig | 5 +++++ lib/std/builtin.zig | 11 +++++++++++ lib/std/builtin/assembly.zig | 2 +- lib/std/zig/system.zig | 5 +++++ src/Sema.zig | 1 + src/Zcu.zig | 4 ++++ src/codegen/c.zig | 5 ++++- src/codegen/llvm.zig | 7 +++++++ src/codegen/spirv/Module.zig | 1 + src/target.zig | 1 + 11 files changed, 63 insertions(+), 9 deletions(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 4cdbddcaec..03fc79b55f 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -1101,7 +1101,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, @@ -1172,6 +1172,7 @@ pub fn toCoffMachine(target: *const Target) std.coff.IMAGE.FILE.MACHINE { .ve, .wasm32, .wasm64, + .x86_16, .xcore, .xtensa, .xtensaeb, @@ -1394,6 +1395,7 @@ pub const Cpu = struct { ve, wasm32, wasm64, + x86_16, x86, x86_64, xcore, @@ -1485,7 +1487,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, }; @@ -1493,7 +1495,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, }; } @@ -1687,6 +1689,7 @@ pub const Cpu = struct { .ve, .wasm32, .wasm64, + .x86_16, .x86, .x86_64, .xcore, @@ -1807,6 +1810,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, @@ -1989,6 +1998,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, @@ -2260,7 +2270,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), @@ -2833,6 +2846,7 @@ pub fn ptrBitWidth_arch_abi(cpu_arch: Cpu.Arch, abi: Abi) u16 { return switch (cpu_arch) { .avr, .msp430, + .x86_16, => 16, .arc, @@ -3046,7 +3060,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, @@ -3404,6 +3418,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, @@ -3511,7 +3526,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, @@ -3583,7 +3598,7 @@ pub fn cMaxIntAlignment(target: *const Target) u16 { return switch (target.cpu.arch) { .avr => 1, - .msp430 => 2, + .msp430, .x86_16 => 2, .arc, .arceb, @@ -3660,6 +3675,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) { diff --git a/lib/std/Target/x86.zig b/lib/std/Target/x86.zig index c589cbcb4b..fb76bb757f 100644 --- a/lib/std/Target/x86.zig +++ b/lib/std/Target/x86.zig @@ -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", diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 3e1413cd99..c0be44b939 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -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, diff --git a/lib/std/builtin/assembly.zig b/lib/std/builtin/assembly.zig index 12a0757ebf..286b671810 100644 --- a/lib/std/builtin/assembly.zig +++ b/lib/std/builtin/assembly.zig @@ -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, diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index f5fae5581a..5f953cf16f 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -386,6 +386,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", diff --git a/src/Sema.zig b/src/Sema.zig index b94e172b45..341cb1c855 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -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, diff --git a/src/Zcu.zig b/src/Zcu.zig index 0eb37668cf..4431948781 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -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, diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 86669aea93..b86e0b583f 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -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", diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 50fed0c2d4..dace4f91e8 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -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(). }; @@ -11919,6 +11921,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, @@ -13148,6 +13154,7 @@ pub fn initializeLLVMTarget(arch: std.Target.Cpu.Arch) void { .propeller, .sh, .sheb, + .x86_16, .xtensaeb, => unreachable, } diff --git a/src/codegen/spirv/Module.zig b/src/codegen/spirv/Module.zig index d728360d7e..c54bc1e5e7 100644 --- a/src/codegen/spirv/Module.zig +++ b/src/codegen/spirv/Module.zig @@ -927,6 +927,7 @@ pub fn storageClass(module: *Module, as: std.builtin.AddressSpace) spec.StorageC .gs, .fs, .ss, + .far, .param, .flash, .flash1, diff --git a/src/target.zig b/src/target.zig index f878f8e6bc..0a2ff00cbf 100644 --- a/src/target.zig +++ b/src/target.zig @@ -227,6 +227,7 @@ pub fn hasLlvmSupport(target: *const std.Target, ofmt: std.Target.ObjectFormat) .propeller, .sh, .sheb, + .x86_16, .xtensaeb, => false, };