zig

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

commit 75b232ab2b47220d3ca01e66cfbf64ccb06cf101 (tree)
parent 360deedcf15c9e8cc0179f9e18b8562aadc8ba09
Author: xtex <xtex@astrafall.org>
Date:   Sun, 21 Jun 2026 21:55:05 +0800

link.Elf2: add support for LoongArch

Diffstat:
Msrc/link.zig | 2++
Msrc/link/Elf2.zig | 403++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Asrc/link/loongarch.zig | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 449 insertions(+), 11 deletions(-)

diff --git 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 @@ -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 @@ -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); +}