From e048c90545c4f7df1377f9fcdce14ce5e5a2056f Mon Sep 17 00:00:00 2001 From: xtex Date: Sun, 21 Jun 2026 21:50:47 +0800 Subject: [PATCH 1/4] std.elf: add R_LARCH relocations --- lib/std/elf.zig | 135 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/lib/std/elf.zig b/lib/std/elf.zig index 81de3f7f501d4c54e3f9048921a6a9a90cc21a77..d3e5bb1dd24cbd2c386db3fe9a7af73a6edb043d 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -3001,6 +3001,141 @@ pub const R_PPC64 = enum(u32) { _, }; +/// LoongArch relocations, as of v2.50 of the ABI specs. +pub const R_LARCH = enum(u32) { + NONE = 0, + @"32" = 1, + @"64" = 2, + RELATIVE = 3, + COPY = 4, + JUMP_SLOT = 5, + TLS_DTPMOD32 = 6, + TLS_DTPMOD64 = 7, + TLS_DTPREL32 = 8, + TLS_DTPREL64 = 9, + TLS_TPREL32 = 10, + TLS_TPREL64 = 11, + IRELATIVE = 12, + TLS_DESC32 = 13, + TLS_DESC64 = 14, + MARK_LA = 20, + MARK_PCREL = 21, + SOP_PUSH_PCREL = 22, + SOP_PUSH_ABSOLUTE = 23, + SOP_PUSH_DUP = 24, + SOP_PUSH_GPREL = 25, + SOP_PUSH_TLS_TPREL = 26, + SOP_PUSH_TLS_GOT = 27, + SOP_PUSH_TLS_GD = 28, + SOP_PUSH_PLT_PCREL = 29, + SOP_ASSERT = 30, + SOP_NOT = 31, + SOP_SUB = 32, + SOP_SL = 33, + SOP_SR = 34, + SOP_ADD = 35, + SOP_AND = 36, + SOP_IF_ELSE = 37, + SOP_POP_32_S_10_5 = 38, + SOP_POP_32_U_10_12 = 39, + SOP_POP_32_S_10_12 = 40, + SOP_POP_32_S_10_16 = 41, + SOP_POP_32_S_10_16_S2 = 42, + SOP_POP_32_S_5_20 = 43, + SOP_POP_32_S_0_5_10_16_S2 = 44, + SOP_POP_32_S_0_10_10_16_S2 = 45, + SOP_POP_32_U = 46, + ADD8 = 47, + ADD16 = 48, + ADD24 = 49, + ADD32 = 50, + ADD64 = 51, + SUB8 = 52, + SUB16 = 53, + SUB24 = 54, + SUB32 = 55, + SUB64 = 56, + GNU_VTINHERIT = 57, + GNU_VTENTRY = 58, + B16 = 64, + B21 = 65, + B26 = 66, + ABS_HI20 = 67, + ABS_LO12 = 68, + ABS64_LO20 = 69, + ABS64_HI12 = 70, + PCALA_HI20 = 71, + PCALA_LO12 = 72, + PCALA64_LO20 = 73, + PCALA64_HI12 = 74, + GOT_PC_HI20 = 75, + GOT_PC_LO12 = 76, + GOT64_PC_LO20 = 77, + GOT64_PC_HI12 = 78, + GOT_HI20 = 79, + GOT_LO12 = 80, + GOT64_LO20 = 81, + GOT64_HI12 = 82, + TLS_LE_HI20 = 83, + TLS_LE_LO12 = 84, + TLS_LE64_LO20 = 85, + TLS_LE64_HI12 = 86, + TLS_IE_PC_HI20 = 87, + TLS_IE_PC_LO12 = 88, + TLS_IE64_PC_LO20 = 89, + TLS_IE64_PC_HI12 = 90, + TLS_IE_HI20 = 91, + TLS_IE_LO12 = 92, + TLS_IE64_LO20 = 93, + TLS_IE64_HI12 = 94, + TLS_LD_PC_HI20 = 95, + TLS_LD_HI20 = 96, + TLS_GD_PC_HI20 = 97, + TLS_GD_HI20 = 98, + @"32_PCREL" = 99, + RELAX = 100, + DELETE = 101, + ALIGN = 102, + PCREL20_S2 = 103, + CFA = 104, + ADD6 = 105, + SUB6 = 106, + ADD_ULEB128 = 107, + SUB_ULEB128 = 108, + @"64_PCREL" = 109, + CALL36 = 110, + TLS_DESC_PC_HI20 = 111, + TLS_DESC_PC_LO12 = 112, + TLS_DESC64_PC_LO20 = 113, + TLS_DESC64_PC_HI12 = 114, + TLS_DESC_HI20 = 115, + TLS_DESC_LO12 = 116, + TLS_DESC64_LO20 = 117, + TLS_DESC64_HI12 = 118, + TLS_DESC_LD = 119, + TLS_DESC_CALL = 120, + TLS_LE_HI20_R = 121, + TLS_LE_ADD_R = 122, + TLS_LE_LO12_R = 123, + TLS_LD_PCREL20_S2 = 124, + TLS_GD_PCREL20_S2 = 125, + TLS_DESC_PCREL20_S2 = 126, + CALL30 = 127, + PCADD_HI20 = 128, + PCADD_LO12 = 129, + GOT_PCADD_HI20 = 130, + GOT_PCADD_LO12 = 131, + TLS_IE_PCADD_HI20 = 132, + TLS_IE_PCADD_LO12 = 133, + TLS_LD_PCADD_HI20 = 134, + TLS_LD_PCADD_LO12 = 135, + TLS_GD_PCADD_HI20 = 136, + TLS_GD_PCADD_LO12 = 137, + TLS_DESC_PCADD_HI20 = 138, + TLS_DESC_PCADD_LO12 = 139, + _, +}; + pub const ar_hdr = extern struct { /// Member file name, sometimes / terminated. ar_name: [16]u8, -- 2.54.0 From 360deedcf15c9e8cc0179f9e18b8562aadc8ba09 Mon Sep 17 00:00:00 2001 From: xtex Date: Sun, 21 Jun 2026 21:52:50 +0800 Subject: [PATCH 2/4] std.elf: add LoongArch e_flags definition Link: https://github.com/loongson/la-abi-specs/blob/44ef5f63e1755e1a165915ea3b2d98d35f68eed0/laelf.adoc?plain=1#L74-L93 --- lib/std/elf.zig | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/std/elf.zig b/lib/std/elf.zig index d3e5bb1dd24cbd2c386db3fe9a7af73a6edb043d..d3215291852537cd82a03abf0f8b33fc1e2a6ea6 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -3263,3 +3263,21 @@ pub const gnu_hash = struct { try std.testing.expectEqual(0x8ae9f18e, calculate("flapenguin.me")); } }; + +pub const loongarch = struct { + /// Ehdr.e_flags bits of LoongArch + pub const EFlags = packed struct(Word) { + base_abi_modifier: BaseAbiModifier, + abi_extension: AbiExtension, + abi_version: u2, + reserved: u24 = 0, + + pub const BaseAbiModifier = enum(u3) { + s = 1, + f = 2, + d = 3, + _, + }; + pub const AbiExtension = enum(u3) { base = 0, _ }; + }; +}; -- 2.54.0 From 75b232ab2b47220d3ca01e66cfbf64ccb06cf101 Mon Sep 17 00:00:00 2001 From: xtex Date: Sun, 21 Jun 2026 21:55:05 +0800 Subject: [PATCH 3/4] link.Elf2: add support for LoongArch --- src/link.zig | 2 + src/link/Elf2.zig | 403 +++++++++++++++++++++++++++++++++++++++-- src/link/loongarch.zig | 55 ++++++ 3 files changed, 449 insertions(+), 11 deletions(-) create mode 100644 src/link/loongarch.zig diff --git a/src/link.zig b/src/link.zig index 844eef3c407f3aecbc27b764a822808c5d1433e3..f2fe15b032c8078fda969e86fbf6db51eb95be87 100644 --- a/src/link.zig +++ b/src/link.zig @@ -31,6 +31,8 @@ pub const LdScript = @import("link/LdScript.zig"); pub const Queue = @import("link/Queue.zig"); pub const ConstPool = @import("link/ConstPool.zig"); +pub const loongarch = @import("link/loongarch.zig"); + pub const Error = Allocator.Error || Io.Cancelable || error{ /// An error message has already been stored in persistent state on `Compilation` or `Zcu`, for /// instance in `Compilation.link_diags`. diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index 241ceef582f3ba3a8ae51540b958ad020f9e065d..91aefff2092172b4a320a1f24c052d68a1cb3844 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -749,6 +749,14 @@ const GotReloc = struct { offset32, rel64, rel32, + + rel32_hi20, + rel64_lo20, + rel64_hi12, + abs32_lo12, + abs32_hi20, + abs64_lo20, + abs64_hi12, }; const Index = enum(u32) { @@ -817,6 +825,41 @@ const GotReloc = struct { @intCast(@as(i64, @bitCast(got_vaddr +% got_offset +% addend -% dest_vaddr))), target_endian, ), + .rel32_hi20 => { + assert(elf.ehdrField(.machine) == .LOONGARCH); + const target_value = got_vaddr +% got_offset +% addend; + link.loongarch.writeJ20(dest_slice[0..4], link.loongarch.toPcalaHi20(target_value, dest_vaddr)); + }, + .rel64_lo20 => { + assert(elf.ehdrField(.machine) == .LOONGARCH); + const target_value = got_vaddr +% got_offset +% addend; + link.loongarch.writeJ20(dest_slice[0..4], link.loongarch.toPcala64Lo20(target_value, dest_vaddr)); + }, + .rel64_hi12 => { + assert(elf.ehdrField(.machine) == .LOONGARCH); + const target_value = got_vaddr +% got_offset +% addend; + link.loongarch.writeK12(dest_slice[0..4], link.loongarch.toPcala64Hi12(target_value, dest_vaddr)); + }, + .abs32_lo12 => { + assert(elf.ehdrField(.machine) == .LOONGARCH); + const target_value = got_vaddr +% got_offset +% addend; + link.loongarch.writeK12(dest_slice[0..4], @truncate(target_value)); + }, + .abs32_hi20 => { + assert(elf.ehdrField(.machine) == .LOONGARCH); + const target_value = got_vaddr +% got_offset +% addend; + link.loongarch.writeJ20(dest_slice[0..4], @truncate(target_value >> 12)); + }, + .abs64_lo20 => { + assert(elf.ehdrField(.machine) == .LOONGARCH); + const target_value = got_vaddr +% got_offset +% addend; + link.loongarch.writeJ20(dest_slice[0..4], @truncate(target_value >> 32)); + }, + .abs64_hi12 => { + assert(elf.ehdrField(.machine) == .LOONGARCH); + const target_value = got_vaddr +% got_offset +% addend; + link.loongarch.writeK12(dest_slice[0..4], @truncate(target_value >> 52)); + }, } } }; @@ -824,6 +867,7 @@ const GotReloc = struct { pub const MachineRelocType = union { X86_64: std.elf.R_X86_64, AARCH64: std.elf.R_AARCH64, + LOONGARCH: std.elf.R_LARCH, RISCV: std.elf.R_RISCV, PPC64: std.elf.R_PPC64, @@ -831,6 +875,7 @@ pub const MachineRelocType = union { return switch (elf.ehdrField(.machine)) { else => unreachable, .AARCH64 => .{ .AARCH64 = .NONE }, + .LOONGARCH => .{ .LOONGARCH = .NONE }, .PPC64 => .{ .PPC64 = .NONE }, .RISCV => .{ .RISCV = .NONE }, .X86_64 => .{ .X86_64 = .NONE }, @@ -840,6 +885,7 @@ pub const MachineRelocType = union { return switch (elf.ehdrField(.machine)) { else => unreachable, .AARCH64 => .{ .AARCH64 = .COPY }, + .LOONGARCH => .{ .LOONGARCH = .COPY }, .PPC64 => .{ .PPC64 = .COPY }, .RISCV => .{ .RISCV = .COPY }, .X86_64 => .{ .X86_64 = .COPY }, @@ -849,24 +895,28 @@ pub const MachineRelocType = union { return switch (elf.ehdrField(.machine)) { else => unreachable, .X86_64 => .{ .X86_64 = .JUMP_SLOT }, + .LOONGARCH => .{ .LOONGARCH = .JUMP_SLOT }, }; } pub fn globDat(elf: *Elf) MachineRelocType { return switch (elf.ehdrField(.machine)) { else => unreachable, .X86_64 => .{ .X86_64 = .GLOB_DAT }, + .LOONGARCH => .{ .LOONGARCH = if (elf.identClass() == .@"64") .@"64" else .@"32" }, }; } pub fn dtpOffAddr(elf: *Elf) MachineRelocType { return switch (elf.ehdrField(.machine)) { else => unreachable, .X86_64 => .{ .X86_64 = .DTPOFF64 }, + .LOONGARCH => .{ .LOONGARCH = if (elf.identClass() == .@"64") .TLS_DTPREL64 else .TLS_DTPREL32 }, }; } pub fn absAddr(elf: *Elf) MachineRelocType { return switch (elf.ehdrField(.machine)) { else => unreachable, .AARCH64 => .{ .AARCH64 = .ABS64 }, + .LOONGARCH => .{ .LOONGARCH = if (elf.identClass() == .@"64") .@"64" else .@"32" }, .PPC64 => .{ .PPC64 = .ADDR64 }, .RISCV => .{ .RISCV = .@"64" }, .X86_64 => .{ .X86_64 = .@"64" }, @@ -883,6 +933,7 @@ pub const MachineRelocType = union { return switch (elf.ehdrField(.machine)) { else => unreachable, inline .AARCH64, + .LOONGARCH, .PPC64, .RISCV, .X86_64, @@ -893,6 +944,7 @@ pub const MachineRelocType = union { return switch (elf.ehdrField(.machine)) { else => unreachable, inline .AARCH64, + .LOONGARCH, .PPC64, .RISCV, .X86_64, @@ -984,9 +1036,23 @@ const SymbolReloc = struct { size64, size32, + abs32_lo12, + rel32_hi20, + rel64_lo20, + rel64_hi12, + branch_rel18, + branch_rel23, + branch_rel28, + call_rel38, + tpoff32_lo12, + tpoff32_hi20, + tpoff64_lo20, + tpoff64_hi12, + fn dependsOnTlsSize(t: SymbolReloc.Type) bool { return switch (t) { .tpoff32, .tpoff64 => true, + .tpoff32_lo12, .tpoff32_hi20, .tpoff64_lo20, .tpoff64_hi12 => true, else => false, }; } @@ -1145,6 +1211,67 @@ const SymbolReloc = struct { target_endian, ); }, + .abs32_lo12 => { + assert(elf.ehdrField(.machine) == .LOONGARCH); + link.loongarch.writeK12(dest_slice[0..4], @truncate(target_value)); + }, + .rel32_hi20 => { + assert(elf.ehdrField(.machine) == .LOONGARCH); + link.loongarch.writeJ20(dest_slice[0..4], link.loongarch.toPcalaHi20(target_value, dest_vaddr)); + }, + .rel64_lo20 => { + assert(elf.ehdrField(.machine) == .LOONGARCH); + link.loongarch.writeJ20(dest_slice[0..4], link.loongarch.toPcala64Lo20(target_value, dest_vaddr)); + }, + .rel64_hi12 => { + assert(elf.ehdrField(.machine) == .LOONGARCH); + link.loongarch.writeK12(dest_slice[0..4], link.loongarch.toPcala64Hi12(target_value, dest_vaddr)); + }, + // TODO: handle bad alignment and overflow gracefully + .branch_rel18 => { + assert(elf.ehdrField(.machine) == .LOONGARCH); + const target_rel: i64 = @bitCast(target_value -% dest_vaddr); + const slot_target: i16 = @intCast(@shrExact(target_rel, 2)); + link.loongarch.writeK16(dest_slice[0..4], @bitCast(slot_target)); + }, + .branch_rel23 => { + assert(elf.ehdrField(.machine) == .LOONGARCH); + const target_rel: i64 = @bitCast(target_value -% dest_vaddr); + const slot_target: i21 = @intCast(@shrExact(target_rel, 2)); + link.loongarch.writeD5K16(dest_slice[0..4], @bitCast(slot_target)); + }, + .branch_rel28 => { + assert(elf.ehdrField(.machine) == .LOONGARCH); + const target_rel: i64 = @bitCast(target_value -% dest_vaddr); + const slot_target: i26 = @intCast(@shrExact(target_rel, 2)); + link.loongarch.writeD10K16(dest_slice[0..4], @bitCast(slot_target)); + }, + .call_rel38 => { + assert(elf.ehdrField(.machine) == .LOONGARCH); + const target_rel: i64 = @bitCast(target_value -% dest_vaddr); + // We use i64 instead of i36 here because the allowed range is + // [PC - 128 GiB - 0x20000, PC + 128GiB - 0x20000 - 4]. + // The intCast in writeJ20 will do the final check. + const slot_target: i64 = @intCast(@shrExact(target_rel, 2)); + link.loongarch.writeJ20(dest_slice[0..4], @bitCast(@as(i20, @intCast((slot_target +% 0x8000) >> 16)))); + link.loongarch.writeK16(dest_slice[4..8], @bitCast(@as(i16, @truncate(slot_target)))); + }, + .tpoff32_lo12 => { + assert(elf.ehdrField(.machine) == .LOONGARCH); + link.loongarch.writeK12(dest_slice[0..4], @truncate(target_value)); + }, + .tpoff32_hi20 => { + assert(elf.ehdrField(.machine) == .LOONGARCH); + link.loongarch.writeJ20(dest_slice[0..4], @truncate(target_value >> 12)); + }, + .tpoff64_lo20 => { + assert(elf.ehdrField(.machine) == .LOONGARCH); + link.loongarch.writeJ20(dest_slice[0..4], @truncate(target_value >> 32)); + }, + .tpoff64_hi12 => { + assert(elf.ehdrField(.machine) == .LOONGARCH); + link.loongarch.writeK12(dest_slice[0..4], @truncate(target_value >> 52)); + }, } } @@ -1259,6 +1386,18 @@ fn ensureUnusedPltCapacity(elf: *Elf, len: u32) Error!void { const plt_sec_need_size: usize = 16 * need_plt_capacity; try elf.ensureNodeSize(elf.shndx.plt_sec.get(elf).ni, plt_sec_need_size); }, + .LOONGARCH => { + // Ensure the `.plt` section's node is big enough + const plt_need_size: usize = 32 + 16 * need_plt_capacity; + try elf.ensureNodeSize(elf.shndx.plt.get(elf).ni, plt_need_size); + + // Ensure the `.got.plt` section's node is big enough + const got_plt_need_size: usize = switch (elf.identClass()) { + .NONE, _ => unreachable, + inline else => |class| @sizeOf(class.ElfN().Addr) * (2 + need_plt_capacity), + }; + try elf.ensureNodeSize(elf.shndx.got_plt.get(elf).ni, got_plt_need_size); + }, } } /// Given an index into the PLT, returns whether that PLT entry is dead, meaning it may be reused at @@ -1874,12 +2013,18 @@ fn addPltEntry(elf: *Elf, global_name: String(.strtab), dynsym_index: u32) void .addend = 0, })); + const reserved_got_plt_entries: u32 = switch (elf.ehdrField(.machine)) { + else => |machine| @panic(@tagName(machine)), + .X86_64 => 3, + .LOONGARCH => 2, + }; + // Now that we know the index, we can set the relocation's offset. const got_plt_addr = switch (elf.shdrPtr(elf.shndx.got_plt)) { inline else => |shdr, class| got_plt_addr: { const ent_size = @sizeOf(class.ElfN().Addr); assert(elf.targetLoad(&shdr.entsize) == ent_size); - const offset = ent_size * @as(u64, 3 + plt_index); + const offset = ent_size * @as(u64, reserved_got_plt_entries + plt_index); assert(offset <= elf.targetLoad(&shdr.size)); break :got_plt_addr elf.targetLoad(&shdr.addr) + offset; }, @@ -1961,6 +2106,55 @@ fn addPltEntry(elf: *Elf, global_name: String(.strtab), dynsym_index: u32) void }, } }, + .LOONGARCH => { + // add a .PLT entry, writing the template + const plt_ni = elf.shndx.plt.get(elf).ni; + const plt_addr, const plt_slice = plt_entry: switch (elf.shdrPtr(elf.shndx.plt)) { + inline else => |shdr| { + const old_size = 16 * (1 + plt_index); + assert(elf.targetLoad(&shdr.size) == old_size); + elf.targetStore(&shdr.size, old_size + 16); + const plt_slice = plt_ni.slice(&elf.mf)[old_size..][0..16]; + @memcpy(plt_slice, source: switch (elf.identClass()) { + .NONE, _ => unreachable, + inline .@"32", .@"64" => |elf_class| { + const ld_byte = if (elf_class == .@"64") 0xc0 else 0x80; + break :source &[16]u8{ + 0x1a, 0x00, 0x00, 0x0f, // pcalau12i $t3, %pc_hi20(func@.got.plt) + 0x28, ld_byte, 0x01, 0xef, // ld.w/d $t3, $t3, %lo12(func@.got.plt) + 0x4c, 0x00, 0x01, 0xed, // jirl $t1, $t3, 0 + 0x00, 0x2a, 0x00, 0x00, // break + }; + }, + }); + break :plt_entry .{ elf.targetLoad(&shdr.addr) + old_size, plt_slice }; + }, + }; + + // add a .GOT.PLT entry, writing the address of the corresponding .PLT entry + const got_plt_ni = elf.shndx.got_plt.get(elf).ni; + switch (elf.shdrPtr(elf.shndx.got_plt)) { + inline else => |shdr, class| { + const ent_size = @sizeOf(class.ElfN().Addr); + const old_size = ent_size * (2 + plt_index); + assert(elf.targetLoad(&shdr.size) == old_size); + elf.targetStore(&shdr.size, old_size + ent_size); + std.mem.writeInt( + class.ElfN().Addr, + got_plt_ni.slice(&elf.mf)[old_size..][0..ent_size], + @intCast(plt_addr), + target_endian, + ); + assert(got_plt_addr == (elf.targetLoad(&shdr.addr) + old_size)); + }, + } + + // relocate the PLT entry to point to the .GOT.PLT entry + const got_plt_abs: u64 = @as(u64, got_plt_addr); + // TODO: handle overflow gracefully + link.loongarch.writeJ20(plt_slice[0..4], link.loongarch.toPcalaHi20(got_plt_abs, plt_addr)); + link.loongarch.writeK12(plt_slice[4..8], @truncate(got_plt_abs)); + }, } } @@ -2715,6 +2909,12 @@ fn initHeaders( const relro_phndx = phnum; phnum += 1; + const init_plt_size: std.elf.Xword, const plt_align: std.mem.Alignment, const plt_sec = + switch (machine) { + else => @panic(@tagName(machine)), + .X86_64 => .{ 16, .@"16", true }, + .LOONGARCH => .{ 32, .@"4", false }, + }; const expected_nodes_len = expected_nodes_len: switch (@"type") { .NONE, .CORE, _ => unreachable, .REL => { @@ -2722,9 +2922,10 @@ fn initHeaders( defer phnum = 0; break :expected_nodes_len 5 + phnum; }, - .EXEC, .DYN => break :expected_nodes_len 10 + + .EXEC, .DYN => break :expected_nodes_len 9 + phnum * 2 - 1 + // each phdr also has a matching shdr, except for the PT_PHDR phdr - @as(usize, 4) * @intFromBool(have_dynamic_section), // .dynstr, .dynsym, .rela.dyn, .rela.plt + @as(usize, 4) * @intFromBool(have_dynamic_section) + // .dynstr, .dynsym, .rela.dyn, .rela.plt + @intFromBool(plt_sec), }; try elf.nodes.ensureTotalCapacity(gpa, expected_nodes_len); try elf.shdrs.ensureTotalCapacity(gpa, shnum); @@ -3051,6 +3252,7 @@ fn initHeaders( .size = switch (machine) { else => @panic(@tagName(machine)), .X86_64 => 3 * 8, + .LOONGARCH => if (elf.identClass() == .@"64") 8 else 4, }, .flags = .{ .WRITE = true, .ALLOC = true }, .addralign = addr_align, @@ -3066,21 +3268,17 @@ fn initHeaders( else => @panic(@tagName(machine)), .@"386" => 3 * 4, .X86_64 => 3 * 8, + .LOONGARCH => if (elf.identClass() == .@"64") 2 * 8 else 2 * 4, }, .addralign = addr_align, .entsize = @intCast(addr_align.toByteUnits()), }, ); - const plt_size: std.elf.Xword, const plt_align: std.mem.Alignment, const plt_sec = - switch (machine) { - else => @panic(@tagName(machine)), - .X86_64 => .{ 16, .@"16", true }, - }; elf.shndx.plt = try elf.addSection(elf.ni.text, .{ .name = ".plt", .type = .PROGBITS, .flags = .{ .ALLOC = true, .EXECINSTR = true }, - .size = plt_size, + .size = init_plt_size, .addralign = plt_align, .node_align = elf.mf.flags.block_size, }); @@ -3218,6 +3416,38 @@ fn initHeaders( .{ .X86_64 = .PC32 }, ); }, + .LOONGARCH => { + const plt_ni = elf.shndx.plt.get(elf).ni; + const got_plt_sym: Symbol.Id = .local(elf.shndx.got_plt.get(elf).lsi); + @memcpy(plt_ni.slice(&elf.mf)[0..32], switch (class) { + .NONE, _ => unreachable, + .@"32" => &[32]u8{ + 0x1a, 0x00, 0x00, 0x0e, // pcalau12i $t2, %pc_hi20(.got.plt) + 0x00, 0x11, 0x3d, 0xad, // sub.w $t1, $t1, $t3 + 0x28, 0x80, 0x01, 0xcf, // ld.w $t3, $t2, %lo12(.got.plt) # _dl_runtime_resolve + 0x02, 0xbf, 0x51, 0xad, // addi.w $t1, $t1, -44 # .plt entry + 0x02, 0x80, 0x01, 0xcc, // addi.w $t0, $t2, %lo12(.got.plt) # &.got.plt + 0x00, 0x44, 0x89, 0xad, // srli.w $t1, $t1, 2 # .plt entry offset + 0x28, 0x80, 0x11, 0x8c, // ld.w $t0, $t0, 4 # link map + 0x4c, 0x00, 0x01, 0xe0, // jr $t3 + }, + .@"64" => &[32]u8{ + 0x1a, 0x00, 0x00, 0x0e, // pcalau12i $t2, %pc_hi20(.got.plt) + 0x00, 0x11, 0xbd, 0xad, // sub.d $t1, $t1, $t3 + 0x28, 0xc0, 0x01, 0xcf, // ld.d $t3, $t2, %lo12(.got.plt) # _dl_runtime_resolve + 0x02, 0xff, 0x51, 0xad, // addi.d $t1, $t1, -44 # .plt entry + 0x02, 0xc0, 0x01, 0xcc, // addi.d $t0, $t2, %lo12(.got.plt) # &.got.plt + 0x00, 0x45, 0x05, 0xad, // srli.d $t1, $t1, 1 # .plt entry offset + 0x28, 0xc0, 0x21, 0x8c, // ld.d $t0, $t0, 8 # link map + 0x4c, 0x00, 0x01, 0xe0, // jr $t3 + }, + }); + elf.plt_first_symbol_reloc = @enumFromInt(elf.symbol_relocs.items.len); + try elf.ensureUnusedRelocCapacity(plt_ni, 3); + try elf.addRelocAssumeCapacity(plt_ni, 0, got_plt_sym, 0, .{ .LOONGARCH = .PCALA_HI20 }); + try elf.addRelocAssumeCapacity(plt_ni, 8, got_plt_sym, 0, .{ .LOONGARCH = .PCALA_LO12 }); + try elf.addRelocAssumeCapacity(plt_ni, 16, got_plt_sym, 0, .{ .LOONGARCH = .PCALA_LO12 }); + }, } } if (comp.config.any_non_single_threaded) { @@ -3242,6 +3472,13 @@ fn initHeaders( elf.got.putAssumeCapacityNoClobber(.{ .reserved = 1 }, .none); elf.got.putAssumeCapacityNoClobber(.{ .reserved = 2 }, .none); }, + .LOONGARCH => { + try elf.got.ensureUnusedCapacity(gpa, 1); + elf.got.putAssumeCapacityNoClobber(switch (have_dynamic_section) { + true => .{ .symbol = .local(elf.shndx.dynamic.get(elf).lsi) }, + false => .{ .reserved = 0 }, + }, .none); + }, } switch (elf.shdrPtr(elf.shndx.got)) { inline else => |shdr, ct_class| { @@ -5375,6 +5612,52 @@ fn addRelocAssumeCapacity( .TLSLD => elf.addGotRelocAssumeCapacity(node, offset, .tlsld0, addend, .rel32), .GOTTPOFF => elf.addGotRelocAssumeCapacity(node, offset, .{ .tpoff = target }, addend, .rel32), }, + .LOONGARCH => switch (@"type".LOONGARCH) { + else => std.debug.panic("TODO: unsupported input relocation, {t}", .{@"type".LOONGARCH}), + _, + .NONE, + .COPY, + .JUMP_SLOT, + .RELATIVE, + .IRELATIVE, + => std.debug.panic("TODO: error for illegal or unsupported input relocation, {t}", .{@"type".LOONGARCH}), + + .RELAX => {}, // TODO: relaxation is not yet implemented + + // Relocations targeting a symbol + .@"64" => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .abs64), + .@"32" => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .abs32), + .@"64_PCREL" => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .rel64), + .@"32_PCREL" => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .rel32), + + .PCALA_LO12 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .abs32_lo12), + .PCALA_HI20 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .rel32_hi20), + .PCALA64_HI12 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .rel64_hi12), + .PCALA64_LO20 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .rel64_lo20), + + .B16 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .branch_rel18), + .B21 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .branch_rel23), + .B26 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .branch_rel28), + .CALL36 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .call_rel38), + + // Relocations targeting a TLS symbol + .TLS_LE_LO12, .TLS_LE_LO12_R => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .tpoff32_lo12), + .TLS_LE_HI20, .TLS_LE_HI20_R => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .tpoff32_hi20), + .TLS_LE64_LO20 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .tpoff64_lo20), + .TLS_LE64_HI12 => try elf.addSymbolRelocAssumeCapacity(node, offset, target, addend, .tpoff64_hi12), + .TLS_LE_ADD_R => {}, // TODO: relaxation is not yet implemented + + // Relocations targeting a GOT entry + .GOT_PC_LO12 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .abs32_lo12), + .GOT_PC_HI20 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .rel32_hi20), + .GOT64_PC_LO20 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .rel64_lo20), + .GOT64_PC_HI12 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .rel64_hi12), + + .GOT_LO12 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .abs32_lo12), + .GOT_HI20 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .abs32_hi20), + .GOT64_LO20 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .abs64_lo20), + .GOT64_HI12 => elf.addGotRelocAssumeCapacity(node, offset, .{ .symbol = target }, addend, .abs64_hi12), + }, }, } } @@ -5412,7 +5695,47 @@ fn addSymbolRelocAssumeCapacity( .tpoff32 => .TPOFF32, .size64 => .SIZE64, .size32 => .SIZE32, + .abs32_lo12, + .rel32_hi20, + .rel64_lo20, + .rel64_hi12, + .branch_rel18, + .branch_rel23, + .branch_rel28, + .call_rel38, + .tpoff32_lo12, + .tpoff32_hi20, + .tpoff64_lo20, + .tpoff64_hi12, + => unreachable, } }, + .LOONGARCH => .{ + .LOONGARCH = switch (@"type") { + .write_rela => unreachable, + .abs64 => .@"64", + .abs32 => .@"32", + .abs32s, .size64, .size32 => unreachable, + .rel64 => .@"64_PCREL", + .rel32 => .@"32_PCREL", + .pltrel64, .pltrel32 => break :r .none, + .dtpoff64 => .TLS_DTPREL64, + .dtpoff32 => .TLS_DTPREL32, + .tpoff64 => .TLS_TPREL64, + .tpoff32 => .TLS_TPREL32, + .abs32_lo12 => .PCALA_LO12, + .rel32_hi20 => .PCALA_HI20, + .rel64_lo20 => .PCALA64_LO20, + .rel64_hi12 => .PCALA64_HI12, + .branch_rel18 => .B16, + .branch_rel23 => .B21, + .branch_rel28 => .B26, + .call_rel38 => .CALL36, + .tpoff32_lo12 => .TLS_LE_LO12, + .tpoff32_hi20 => .TLS_LE_HI20, + .tpoff64_lo20 => .TLS_LE64_LO20, + .tpoff64_hi12 => .TLS_LE64_HI12, + }, + }, }; // TODO: even if the symbol is locally defined, preemption/interposition is a // possibility, which this condition does not currently consider! @@ -5583,6 +5906,7 @@ fn updateGotEntry(elf: *Elf, got_index: usize) void { .type = switch (elf.ehdrField(.machine)) { else => |machine| @panic(@tagName(machine)), .X86_64 => .{ .X86_64 = .TPOFF64 }, + .LOONGARCH => .{ .LOONGARCH = if (elf.identClass() == .@"64") .TLS_TPREL64 else .TLS_TPREL32 }, }, .dynsym_index = switch (sym_id.unwrap()) { .global => |name| elf.globalByName(name).?.dynsym_index, @@ -5639,7 +5963,11 @@ fn updateGotEntry(elf: *Elf, got_index: usize) void { .UNDEF => .{ .unsigned = 1 }, // TLS module ID for exexcutable else => .{ .reloc = .{ - .type = .{ .X86_64 = .DTPMOD64 }, + .type = switch (elf.ehdrField(.machine)) { + else => |machine| @panic(@tagName(machine)), + .X86_64 => .{ .X86_64 = .DTPMOD64 }, + .LOONGARCH => .{ .LOONGARCH = if (elf.identClass() == .@"64") .TLS_DTPMOD64 else .TLS_DTPMOD32 }, + }, .dynsym_index = switch (sym.unwrap()) { .local => 0, .global => |name| dsi: { @@ -5671,7 +5999,11 @@ fn updateGotEntry(elf: *Elf, got_index: usize) void { .tlsld0 => switch (elf.shndx.dynamic) { .UNDEF => .{ .unsigned = 1 }, // TLS module ID for exexcutable else => .{ .reloc = .{ - .type = .{ .X86_64 = .DTPMOD64 }, + .type = switch (elf.ehdrField(.machine)) { + else => |machine| @panic(@tagName(machine)), + .X86_64 => .{ .X86_64 = .DTPMOD64 }, + .LOONGARCH => .{ .LOONGARCH = if (elf.identClass() == .@"64") .TLS_DTPMOD64 else .TLS_DTPMOD32 }, + }, .dynsym_index = 0, } }, }, @@ -6618,6 +6950,55 @@ fn flushMovedPltSection(elf: *Elf, which: enum { plt, plt_sec, got_plt }, old_ad }, } }, + .LOONGARCH => { + switch (which) { + .plt => { + // We also need to update all of the references from `.plt` to `.got.plt`. + // However, if there's also a flush pending for `.got.plt`, don't bother doing + // this now, because we'll do it when `.got.plt` is flushed anyway. + if (elf.shndx.got_plt.get(elf).ni.hasMoved(&elf.mf)) { + return; + } + // Exit this `switch` to update those references. + }, + .plt_sec => unreachable, + .got_plt => { + // Update the offsets of the relocation entries in `.rela.plt`. + const rela_plt_shndx = elf.shndx.rela_plt; + for (0..elf.plt.count()) |plt_index| { + if (elf.pltEntryIsDead(plt_index)) continue; + rela_plt_shndx.relaAdjustOffset(elf, @enumFromInt(plt_index), old_addr, addr); + } + // We also need to update all of the references from `.plt` to `.got.plt`. + // However, if there's also a flush pending for `.plt`, don't bother doing + // this now, because we'll do it when `.plt` is flushed anyway. + if (elf.shndx.plt.get(elf).ni.hasMoved(&elf.mf)) { + return; + } + // Exit this `switch` to update those references. + }, + } + // We are updating the references from `.plt` to `.got.plt`. + const got_plt_addr = elf.shndx.got_plt.vaddr(elf); + const plt_addr = elf.shndx.plt.vaddr(elf); + const plt_slice = elf.shndx.plt.get(elf).ni.slice(&elf.mf); + switch (elf.identClass()) { + .NONE, _ => unreachable, + inline else => |class| { + const Addr = class.ElfN().Addr; + for (0..elf.plt.count()) |plt_index| { + const plt_offset = 16 * plt_index; + const got_plt_offset = @sizeOf(Addr) * (2 + plt_index); + const target_slice = plt_slice[plt_offset..]; + + const got_plt_abs: u64 = got_plt_addr + got_plt_offset; + // TODO: handle overflow gracefully + link.loongarch.writeJ20(target_slice[0..4], link.loongarch.toPcalaHi20(got_plt_abs, plt_addr + plt_offset)); + link.loongarch.writeK12(target_slice[4..8], @truncate(got_plt_abs)); + } + }, + } + }, } } diff --git a/src/link/loongarch.zig b/src/link/loongarch.zig new file mode 100644 index 0000000000000000000000000000000000000000..be460a0c19e7f49094324c004bdd912b56474cea --- /dev/null +++ b/src/link/loongarch.zig @@ -0,0 +1,55 @@ +const std = @import("std"); +const mem = std.mem; + +pub fn writeK12(code: *[4]u8, target_value: u12) void { + var inst = std.mem.readInt(u32, code, .little); + inst &= 0b11111111110000000000001111111111; + inst |= (@as(u32, target_value) << 10); + std.mem.writeInt(u32, code, inst, .little); +} + +pub fn writeK16(code: *[4]u8, target_value: u16) void { + var inst = std.mem.readInt(u32, code, .little); + inst &= 0b11111100000000000000001111111111; + inst |= (@as(u32, target_value) << 10); + std.mem.writeInt(u32, code, inst, .little); +} + +pub fn writeJ20(code: *[4]u8, target_value: u20) void { + var inst = std.mem.readInt(u32, code, .little); + inst &= 0b11111110000000000000000000011111; + inst |= (@as(u32, target_value) << 5); + std.mem.writeInt(u32, code, inst, .little); +} + +pub fn writeD5K16(code: *[4]u8, target_value: u21) void { + var inst = std.mem.readInt(u32, code, .little); + inst &= 0b11111100000000000000001111100000; + inst |= @as(u32, target_value >> 16); + inst |= (@as(u32, target_value << 5) << 5); + std.mem.writeInt(u32, code, inst, .little); +} + +pub fn writeD10K16(code: *[4]u8, target_value: u26) void { + var inst = std.mem.readInt(u32, code, .little); + inst &= 0b11111100000000000000000000000000; + inst |= @as(u32, target_value >> 16); + inst |= @as(u32, target_value << 10); + std.mem.writeInt(u32, code, inst, .little); +} + +pub fn toPcalaHi20(target: u64, pc: u64) u20 { + return @truncate(((target +% 0x800) >> 12) -% (pc >> 12)); +} + +pub fn toPcala64Lo20(target: u64, pc: u64) u20 { + const fixup = if (target & 0x800 != 0) (@as(u64, 0x1000) -% @as(u64, 0x100000000)) else 0; + const hi32 = (((target +% 0x80000000 +% fixup) >> 12) -% ((pc -% 8) >> 12)) >> 20; + return @truncate(hi32); +} + +pub fn toPcala64Hi12(target: u64, pc: u64) u12 { + const fixup = if (target & 0x800 != 0) (@as(u64, 0x1000) -% @as(u64, 0x100000000)) else 0; + const hi32 = (((target +% 0x80000000 +% fixup) >> 12) -% ((pc -% 12) >> 12)) >> 20; + return @truncate(hi32 >> 20); +} -- 2.54.0 From f1e3b67b1fae868fed3f13a18642607c777fc087 Mon Sep 17 00:00:00 2001 From: xtex Date: Sun, 21 Jun 2026 21:56:00 +0800 Subject: [PATCH 4/4] link.Elf2: write LoongArch e_flags The base ABI modifier is determined by the target features of the root module. However, ideally, it should be determined by the superset of the feature sets of all linked modules. Setting ABI version to v1. Both the LLVM backend and the self-hosted backend in my branch supports only ABI v1 ("new world"). --- src/link/Elf2.zig | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index 91aefff2092172b4a320a1f24c052d68a1cb3844..a2f13bd06742315e192fe96da21974561d75eb04 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -2958,7 +2958,24 @@ fn initHeaders( ehdr.entry = 0; ehdr.phoff = 0; ehdr.shoff = 0; - ehdr.flags = 0; + ehdr.flags = switch (machine) { + .X86_64 => 0, + .LOONGARCH => e_flags: { + const target_cpu = &elf.base.comp.getTarget().cpu; + const e_flags: std.elf.loongarch.EFlags = .{ + .base_abi_modifier = if (target_cpu.has(.loongarch, .d)) + .d + else if (target_cpu.has(.loongarch, .f)) + .f + else + .s, + .abi_extension = .base, + .abi_version = 1, + }; + break :e_flags @bitCast(e_flags); + }, + else => @panic(@tagName(machine)), + }; ehdr.ehsize = @sizeOf(ElfN.Ehdr); ehdr.phentsize = @sizeOf(ElfN.Phdr); ehdr.phnum = @min(phnum, std.elf.PN_XNUM); -- 2.54.0