From c96d1cc0c112263883eb649ab3237321538d193d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 26 May 2026 08:06:32 +0200 Subject: [PATCH 1/8] std.elf: add DT_XTENSA_GOT_LOC_{OFF,SZ} --- lib/std/elf.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/std/elf.zig b/lib/std/elf.zig index 2db34ed943ed4aa38ccc4346f00ff7b2f26d59a1..fcf5152a2b7ec8a12e9bddb954829d71f5e1bb5b 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -224,6 +224,9 @@ pub const DT_PPC64_NUM = 4; pub const DT_IA_64_PLT_RESERVE = (DT_LOPROC + 0); pub const DT_IA_64_NUM = 1; +pub const DT_XTENSA_GOT_LOC_OFF = 0x70000000; +pub const DT_XTENSA_GOT_LOC_SZ = 0x70000001; + pub const DT_NIOS2_GP = 0x70000002; pub const DF_ORIGIN = 0x00000001; -- 2.54.0 From 68d2bef8c0e956f0ce1dcee8284687c24230c26b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 26 May 2026 08:05:53 +0200 Subject: [PATCH 2/8] std.debug: FP unwinding is useless on xtensa Like MIPS, we'd have to do backwards code scanning for it to be useful, and at least for now, we're not willing to do that. --- lib/std/debug.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 263610c3b47c6efc3291bdf94f461652a51f4202..dd6de989cbff6b9a6f0c12a8b3e857b765b03a9b 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -994,6 +994,8 @@ const StackIterator = union(enum) { .sh, .sheb, .xcore, + .xtensa, + .xtensaeb, => .useless, .hexagon, // The PowerPC ABIs don't actually strictly require a backchain pointer; they allow omitting -- 2.54.0 From 5143239e2bcc41f6aba8a56b73810f2f9965feb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 26 May 2026 08:08:53 +0200 Subject: [PATCH 3/8] std.start: add xtensa support --- lib/std/start.zig | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/std/start.zig b/lib/std/start.zig index 9491ef2045ce81f2d46966979c58cdc04833e8ea..e05208731ec4de30feee8edf80eb72f8c442b5c4 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -183,6 +183,7 @@ fn _start() callconv(.naked) noreturn { .sparc, .sparc64 => ".cfi_undefined %%i7", .x86 => ".cfi_undefined %%eip", .x86_64 => ".cfi_undefined %%rip", + .xtensa, .xtensaeb => "", // No CFI support. else => @compileError("unsupported arch"), }); @@ -486,6 +487,15 @@ fn _start() callconv(.naked) noreturn { \\ sub %%sp, 2047, %%sp \\ ba,a %[posixCallMainAndExit] , + .xtensa, .xtensaeb => + // a0 = LR, a7 = FP, a1 = SP + \\ movi a0, 0 + \\ movi a7, 0 + \\ mov a2, sp + \\ movi a8, -16 + \\ and sp, sp, a8 + \\ callx0 %[posixCallMainAndExit] + , else => @compileError("unsupported arch"), } : -- 2.54.0 From a6ab35353293531d1b04cc18b667729fd5ee280e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 26 May 2026 08:07:45 +0200 Subject: [PATCH 4/8] std.pie: add xtensa support This might need more work to handle the DT_XTENSA_GOT_LOC_{OFF,SZ} insanity, but this is at least a start. --- lib/std/pie.zig | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/std/pie.zig b/lib/std/pie.zig index 42bc64218116659d535e90802a458b90afc4658a..504bff815c7a1c55e25a3a4d9b8e700dbe338188 100644 --- a/lib/std/pie.zig +++ b/lib/std/pie.zig @@ -22,6 +22,7 @@ const R_RISCV_RELATIVE = 3; const R_390_RELATIVE = 12; const R_SH_RELATIVE = 165; const R_SPARC_RELATIVE = 22; +const R_XTENSA_RELATIVE = 5; const R_RELATIVE = switch (builtin.cpu.arch) { .x86 => R_386_RELATIVE, @@ -43,6 +44,7 @@ const R_RELATIVE = switch (builtin.cpu.arch) { .s390x => R_390_RELATIVE, .sh, .sheb => R_SH_RELATIVE, .sparc, .sparc64 => R_SPARC_RELATIVE, + .xtensa, .xtensaeb => R_XTENSA_RELATIVE, else => @compileError("Missing R_RELATIVE definition for this target"), }; @@ -261,6 +263,25 @@ inline fn getDynamicSymbol() [*]const elf.Dyn { : [ret] "=r" (-> [*]const elf.Dyn), : : .{ .l7 = true }), + .xtensa, .xtensaeb => asm volatile ( + \\ .weak _DYNAMIC + \\ .hidden _DYNAMIC + // Set things up such that after the `call0`, `a0` will point 1 byte before the + // embedded constant. Note that `call0` is a 3-byte instruction, so we need both + // `.balign` directives to be safe. + \\ .balign 4 + \\ .begin no-transform + \\ call0 1f + \\ .end no-transform + \\ .balign 4 + \\ .word _DYNAMIC - . + \\1: + \\ add a0, a0, 1 + \\ l32i a8, a0, 0 + \\ add %[ret], a0, a8 + : [ret] "=a" (-> [*]const elf.Dyn), + : + : .{ .a0 = true, .a8 = true }), else => { @compileError("PIE startup is not yet supported for this target!"); }, -- 2.54.0 From 0142405f8643d9e464991fb7606b0c5849cf64a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 26 May 2026 08:09:11 +0200 Subject: [PATCH 5/8] std.os.linux.tls: add xtensa support --- lib/std/os/linux/tls.zig | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/std/os/linux/tls.zig b/lib/std/os/linux/tls.zig index e188f954626cb7b6ad9b78faf5d723ffc2eae4cd..ef87253cec80f6fb526994bcca2570cb998789aa 100644 --- a/lib/std/os/linux/tls.zig +++ b/lib/std/os/linux/tls.zig @@ -78,6 +78,8 @@ const current_variant: Variant = switch (native_arch) { .sheb, .thumb, .thumbeb, + .xtensa, + .xtensaeb, => .I_original, .loongarch32, .loongarch64, @@ -152,6 +154,8 @@ const AbiTcb = switch (current_variant) { .sheb, .thumb, .thumbeb, + .xtensa, + .xtensaeb, => extern struct { /// This is offset by `current_dtv_offset`. dtv: usize, @@ -364,6 +368,13 @@ pub fn setThreadPointer(addr: usize) void { : [addr] "r" (addr), ); }, + .xtensa, .xtensaeb => { + asm volatile ( + \\ wur %[addr], threadptr + : + : [addr] "a" (addr), + ); + }, else => @compileError("Unsupported architecture"), } } -- 2.54.0 From ecc47695efd1a81e8d713376a54944f5312cd6eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 26 May 2026 08:09:43 +0200 Subject: [PATCH 6/8] std.os.linux: add xtensa arch bits closes https://codeberg.org/ziglang/zig/issues/30947 --- lib/std/os/linux.zig | 32 +++++++ lib/std/os/linux/xtensa.zig | 171 ++++++++++++++++++++++++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 lib/std/os/linux/xtensa.zig diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 8f9e7c178307bff79c98356155549c8bbd9ff360..33b4f640591c6591b276d9cbf5c7e443b586e957 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -56,6 +56,7 @@ const arch_bits = switch (native_arch) { .gnux32, .muslx32 => @import("linux/x32.zig"), else => @import("linux/x86_64.zig"), }, + .xtensa, .xtensaeb => @import("linux/xtensa.zig"), else => struct {}, }; @@ -338,6 +339,29 @@ pub const MAP = switch (native_arch) { _20: u1 = 0, _: u5 = 0, }, + .xtensa, .xtensaeb => packed struct(u32) { + TYPE: MAP_TYPE, + FIXED: bool = false, + _RENAME: bool = false, + _AUTOGROW: bool = false, + _LOCAL: bool = false, + _AUTORSRV: bool = false, + _1: u1 = 0, + NORESERVE: bool = false, + ANONYMOUS: bool = false, + GROWSDOWN: bool = false, + DENYWRITE: bool = false, + EXECUTABLE: bool = false, + LOCKED: bool = false, + POPULATE: bool = false, + NONBLOCK: bool = false, + STACK: bool = false, + HUGETLB: bool = false, + FIXED_NOREPLACE: bool = false, + _2: u5 = 0, + UNINITIALIZED: bool = false, + _3: u5 = 0, + }, else => @compileError("missing std.os.linux.MAP constants for this architecture"), }; @@ -497,6 +521,8 @@ pub const O = switch (native_arch) { .hexagon, .or1k, .s390x, + .xtensa, + .xtensaeb, => packed struct(u32) { ACCMODE: ACCMODE = .RDONLY, _2: u4 = 0, @@ -6617,6 +6643,12 @@ pub const k_sigaction = switch (native_arch) { mask: sigset_t, flags: c_int, }, + .xtensa, .xtensaeb => extern struct { + handler: k_sigaction_funcs.handler, + mask: sigset_t, + flags: c_ulong, + restorer: k_sigaction_funcs.restorer, + }, else => extern struct { handler: k_sigaction_funcs.handler, flags: c_ulong, diff --git a/lib/std/os/linux/xtensa.zig b/lib/std/os/linux/xtensa.zig new file mode 100644 index 0000000000000000000000000000000000000000..7a4c065c7328fc48fdd64e512e5471f7a3dbf7e8 --- /dev/null +++ b/lib/std/os/linux/xtensa.zig @@ -0,0 +1,171 @@ +const builtin = @import("builtin"); +const std = @import("../../std.zig"); +const SYS = std.os.linux.SYS; + +pub const syscall_arg_t = u32; + +pub fn syscall0( + number: SYS, +) u32 { + return asm volatile ("syscall" + : [ret] "={a2}" (-> u32), + : [number] "{a2}" (@intFromEnum(number)), + : .{ .memory = true }); +} + +pub fn syscall1( + number: SYS, + arg1: syscall_arg_t, +) u32 { + return asm volatile ("syscall" + : [ret] "={a2}" (-> u32), + : [number] "{a2}" (@intFromEnum(number)), + [arg1] "{a6}" (arg1), + : .{ .memory = true }); +} + +pub fn syscall2( + number: SYS, + arg1: syscall_arg_t, + arg2: syscall_arg_t, +) u32 { + return asm volatile ("syscall" + : [ret] "={a2}" (-> u32), + : [number] "{a2}" (@intFromEnum(number)), + [arg1] "{a6}" (arg1), + [arg2] "{a3}" (arg2), + : .{ .memory = true }); +} + +pub fn syscall3( + number: SYS, + arg1: syscall_arg_t, + arg2: syscall_arg_t, + arg3: syscall_arg_t, +) u32 { + return asm volatile ("syscall" + : [ret] "={a2}" (-> u32), + : [number] "{a2}" (@intFromEnum(number)), + [arg1] "{a6}" (arg1), + [arg2] "{a3}" (arg2), + [arg3] "{a4}" (arg3), + : .{ .memory = true }); +} + +pub fn syscall4( + number: SYS, + arg1: syscall_arg_t, + arg2: syscall_arg_t, + arg3: syscall_arg_t, + arg4: syscall_arg_t, +) u32 { + return asm volatile ("syscall" + : [ret] "={a2}" (-> u32), + : [number] "{a2}" (@intFromEnum(number)), + [arg1] "{a6}" (arg1), + [arg2] "{a3}" (arg2), + [arg3] "{a4}" (arg3), + [arg4] "{a5}" (arg4), + : .{ .memory = true }); +} + +pub fn syscall5( + number: SYS, + arg1: syscall_arg_t, + arg2: syscall_arg_t, + arg3: syscall_arg_t, + arg4: syscall_arg_t, + arg5: syscall_arg_t, +) u32 { + return asm volatile ("syscall" + : [ret] "={a2}" (-> u32), + : [number] "{a2}" (@intFromEnum(number)), + [arg1] "{a6}" (arg1), + [arg2] "{a3}" (arg2), + [arg3] "{a4}" (arg3), + [arg4] "{a5}" (arg4), + [arg5] "{a8}" (arg5), + : .{ .memory = true }); +} + +pub fn syscall6( + number: SYS, + arg1: syscall_arg_t, + arg2: syscall_arg_t, + arg3: syscall_arg_t, + arg4: syscall_arg_t, + arg5: syscall_arg_t, + arg6: syscall_arg_t, +) u32 { + return asm volatile ("syscall" + : [ret] "={a2}" (-> u32), + : [number] "{a2}" (@intFromEnum(number)), + [arg1] "{a6}" (arg1), + [arg2] "{a3}" (arg2), + [arg3] "{a4}" (arg3), + [arg4] "{a5}" (arg4), + [arg5] "{a8}" (arg5), + [arg6] "{a9}" (arg6), + : .{ .memory = true }); +} + +pub fn clone() callconv(.naked) u32 { + // __clone(func, stack, flags, arg, ptid, tls, ctid) + // a2, a3, a4, a5, a6, a7, +16 + // + // syscall(SYS_clone, flags, stack, ptid, tls, ctid) + // a2 a6, a3, a4, a5, a8 + asm volatile ( + \\ entry sp, 16 + \\ + \\ movi a8, -16 + \\ and a3, a3, a8 + \\ + \\ mov a9, a2 + \\ mov a10, a5 + \\ + \\ mov a5, a7 + \\ mov a8, a6 + \\ mov a6, a4 + \\ mov a4, a8 + \\ l32i a8, sp, 16 + \\ movi a2, 116 // SYS_clone + \\ syscall + \\ + \\ beqz a2, 1f + \\ // parent + \\ retw + \\ + \\ // child + \\1: + \\ movi a7, 0 + \\ movi a0, 0 + \\ + \\ mov a2, a10 + \\ callx0 a9 + \\ movi a2, 118 // SYS_exit + \\ syscall + ); +} + +pub const restore = restore_rt; + +pub fn restore_rt() callconv(.naked) noreturn { + switch (builtin.zig_backend) { + .stage2_c => asm volatile ( + \\ movi a2, %[number] + \\ syscall + : + : [number] "I" (@intFromEnum(SYS.rt_sigreturn)), + ), + else => asm volatile ( + \\ syscall + : + : [number] "{a2}" (@intFromEnum(SYS.rt_sigreturn)), + ), + } +} + +pub const VDSO = void; + +pub const time_t = i32; -- 2.54.0 From b0bda2d744f2f68a3edb7d948f7dc40cec2fef5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 26 May 2026 08:05:09 +0200 Subject: [PATCH 7/8] std.Thread: implement LinuxThreadImpl.freeAndExit() for xtensa --- lib/std/Thread.zig | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index caa22ea4cb4548363d40007e4e2b58a4e829cec8..25f9146bb0aa6ce8cb6ae4d3744788002bdf2413 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -1411,6 +1411,16 @@ const LinuxThreadImpl = struct { : [ptr] "{r4}" (@intFromPtr(self.mapped.ptr)), [len] "{r5}" (self.mapped.len), : .{ .memory = true }), + .xtensa, .xtensaeb => asm volatile ( + \\ movi a2, 81 // SYS_munmap + \\ syscall + \\ movi a6, 0 + \\ movi a2, 118 // SYS_exit + \\ syscall + : + : [ptr] "{a6}" (@intFromPtr(self.mapped.ptr)), + [len] "{a3}" (self.mapped.len), + : .{ .memory = true }), else => |cpu_arch| @compileError("Unsupported linux arch: " ++ @tagName(cpu_arch)), } unreachable; -- 2.54.0 From 84a100b3e06afec33d4077052a50f8126396021f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 26 May 2026 08:06:46 +0200 Subject: [PATCH 8/8] std.os: no longer require libc for xtensa-linux Not that any of our vendored libcs support xtensa anyway. --- lib/std/os.zig | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/std/os.zig b/lib/std/os.zig index 065ca0fd2dcb459cc35b591154b1f271a3b44269..fa811ef0601f0c3b150600db8c2a09238beda085 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -28,9 +28,6 @@ pub fn targetRequiresLibC(target: *const std.Target) bool { .sheb, // https://codeberg.org/ziglang/zig/issues/30945 .sparc, - // https://codeberg.org/ziglang/zig/issues/30947 - .xtensa, - .xtensaeb, => true, else => false, }, -- 2.54.0