zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit 1999d80d6e9fc28fde7e50cc35d879e2467756ae (tree)
parent 1526ae4e5112b388209f0a0d7352c27da28cec74
Author: Alex Rønne Petersen <alex@alexrp.com>
Date:   Fri, 26 Jun 2026 02:08:14 +0200

Merge pull request 'Elf2: add initial LoongArch support' (#35875) from AstraFall/zig:loongarch/elf2/init into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/35875
Reviewed-by: mlugg <mlugg@noreply.codeberg.org>

Diffstat:
Mlib/std/elf.zig | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/link.zig | 2++
Msrc/link/Elf2.zig | 422++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Asrc/link/loongarch.zig | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 620 insertions(+), 12 deletions(-)

diff --git 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, @@ -3128,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, _ }; + }; +}; diff --git a/src/link.zig b/src/link.zig @@ -32,6 +32,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 @@ -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); @@ -2757,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); @@ -3051,6 +3269,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 +3285,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 +3433,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 +3489,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 +5629,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 +5712,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 +5923,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 +5980,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 +6016,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 +6967,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 @@ -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); +}