// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause package asm import ( "encoding/binary" "fmt" ) // LoongArch ELF64 relocatable object emission. const ( emLOONGARCH = 258 // EM_LOONGARCH // LoongArch relocation types (the ELF psABI). rLarchPCALAHI20 = 71 // R_LARCH_PCALA_HI20 (pcalau12i) rLarchPCALALO12 = 72 // R_LARCH_PCALA_LO12 (addi.d/ld/st) rLarchB26 = 66 // R_LARCH_B26 (b/bl, matches the Go linker's mapping) ) // ELFLOONG64Object returns the image as an ELF64 relocatable object file for // LoongArch (EM_LOONGARCH, 64-bit, little-endian). The structure mirrors the // amd64 and RISC-V ELF emitters: .text, .data, .symtab, .strtab and an // optional .rela.text. func (img *Image) ELFLOONG64Object() ([]byte, error) { le := binary.LittleEndian const ( secText = 1 secData = 2 ) // Build symbol table. var locals, globals []elfSym for _, fn := range img.Funcs { s := elfSym{ name: objectName(fn.Pkg, fn.Name), info: sttFunc, shndx: secText, value: uint64(fn.Offset), size: uint64(fn.Size), } if fn.Static { locals = append(locals, s) } else { s.info |= stbGlobal << stInfoShift globals = append(globals, s) } } for _, d := range img.DataSyms { s := elfSym{ name: objectName(d.Pkg, d.Name), info: sttObject, shndx: secData, value: uint64(d.Offset), size: uint64(d.Size), } if d.Static { locals = append(locals, s) } else { s.info |= stbGlobal << stInfoShift globals = append(globals, s) } } for _, name := range img.Externals { globals = append(globals, elfSym{name: name, info: stbGlobal << stInfoShift}) } syms := []elfSym{ {}, {name: ".text", info: sttSection, shndx: secText}, {name: ".data", info: sttSection, shndx: secData}, } syms = append(syms, locals...) shInfo := len(syms) syms = append(syms, globals...) symIdx := map[string]int{} for i, s := range syms { symIdx[s.name] = i } // Build relocations. Each SB reference is a pcalau12i pair: // pcalau12i rd, 0 → R_LARCH_PCALA_HI20 // addi.d/ld/st → R_LARCH_PCALA_LO12 type elfRela struct { off uint64 typ uint32 sym int addend int64 } var relas []elfRela for _, fn := range img.Funcs { for _, r := range fn.Relocs { idx, ok := symIdx[r.Name] if !ok { return nil, fmt.Errorf("relocation references unknown symbol %q", r.Name) } typ := uint32(rLarchPCALAHI20) switch r.Kind { case RelLoong64AddrLo: typ = rLarchPCALALO12 case RelLoong64Branch: typ = rLarchB26 } relas = append(relas, elfRela{ off: uint64(fn.Offset + r.Off), typ: typ, sym: idx, addend: r.Addend, }) } } // String tables. stNames := newElfStrtab() for _, s := range syms { stNames.add(s.name) } stSections := newElfStrtab() for _, n := range []string{".text", ".data", ".symtab", ".strtab", ".rela.text", ".shstrtab"} { stSections.add(n) } for _, n := range dwarfSectionNames { stSections.add(n) } hasRela := len(relas) > 0 nSections := 6 if hasRela { nSections = 7 } secSymtab, secStrtab := 3, 4 secShstr := nSections - 1 // Layout. var out []byte out = append(out, make([]byte, 64)...) align := func(n int) { for len(out)%n != 0 { out = append(out, 0) } } align(16) textOff := len(out) out = append(out, img.Code...) align(16) dataOff := len(out) out = append(out, img.Data...) align(8) symtabOff := len(out) for _, s := range syms { var b [24]byte le.PutUint32(b[0:], uint32(stNames.at(s.name))) b[4] = s.info b[5] = 0 le.PutUint16(b[6:], s.shndx) le.PutUint64(b[8:], s.value) le.PutUint64(b[16:], s.size) out = append(out, b[:]...) } strtabOff := len(out) out = append(out, stNames.bytes()...) var relaOff int if hasRela { align(8) relaOff = len(out) for _, r := range relas { var b [24]byte le.PutUint64(b[0:], r.off) le.PutUint64(b[8:], uint64(r.sym)<<32|uint64(r.typ)) le.PutUint64(b[16:], uint64(r.addend)) out = append(out, b[:]...) } } shstrOff := len(out) out = append(out, stSections.bytes()...) dwAlign := func(n int) { for len(out)%n != 0 { out = append(out, 0) } } dw := appendDWARFSections(&out, img, "gasm.s", symIdx, dwAlign) if dw != nil { nSections += 4 } align(8) shoff := len(out) putSh := func(name string, typ int, flags uint64, off, size int, link, info int, alignV, entsize uint64) { var b [64]byte le.PutUint32(b[0:], uint32(stSections.at(name))) le.PutUint32(b[4:], uint32(typ)) le.PutUint64(b[8:], flags) le.PutUint64(b[16:], 0) le.PutUint64(b[24:], uint64(off)) le.PutUint64(b[32:], uint64(size)) le.PutUint32(b[40:], uint32(link)) le.PutUint32(b[44:], uint32(info)) le.PutUint64(b[48:], alignV) le.PutUint64(b[56:], entsize) out = append(out, b[:]...) } putSh("", shtNull, 0, 0, 0, 0, 0, 0, 0) putSh(".text", shtProgbits, shfAlloc|shfExecInstr, textOff, len(img.Code), 0, 0, 16, 0) putSh(".data", shtProgbits, shfAlloc|shfWrite, dataOff, len(img.Data), 0, 0, 16, 0) putSh(".symtab", shtSymtab, 0, symtabOff, 24*len(syms), secStrtab, shInfo, 8, 24) putSh(".strtab", shtStrtab, 0, strtabOff, len(stNames.bytes()), 0, 0, 1, 0) if hasRela { putSh(".rela.text", shtRela, 0, relaOff, 24*len(relas), secSymtab, secText, 8, 24) } putSh(".shstrtab", shtStrtab, 0, shstrOff, len(stSections.bytes()), 0, 0, 1, 0) if dw != nil { putSh(".debug_abbrev", shtProgbits, 0, dw.abbrevOff, dw.abbrevSize, 0, 0, 1, 0) putSh(".debug_info", shtProgbits, 0, dw.infoOff, dw.infoSize, 0, 0, 1, 0) putSh(".debug_line", shtProgbits, 0, dw.lineOff, dw.lineSize, 0, 0, 1, 0) putSh(".debug_line_str", shtProgbits, 0, dw.lineStrOff, dw.lineStrSize, 0, 0, 1, 0) if dw.frameSize > 0 { putSh(".debug_frame", shtProgbits, 0, dw.frameOff, dw.frameSize, 0, 0, 8, 0) } } // ELF header. hdr := out[:64] copy(hdr[0:], []byte{0x7f, 'E', 'L', 'F', elfClass64, elfDataLSB, elfVersion, 0}) le.PutUint16(hdr[16:], etREL) le.PutUint16(hdr[18:], emLOONGARCH) le.PutUint32(hdr[20:], elfVersion) le.PutUint64(hdr[24:], 0) le.PutUint64(hdr[32:], 0) le.PutUint64(hdr[40:], uint64(shoff)) le.PutUint32(hdr[48:], 0) le.PutUint16(hdr[52:], 64) le.PutUint16(hdr[54:], 0) le.PutUint16(hdr[56:], 0) le.PutUint16(hdr[58:], 64) le.PutUint16(hdr[60:], uint16(nSections)) le.PutUint16(hdr[62:], uint16(secShstr)) return out, nil }