fix(asm): encode RISC-V CALL sym(SB) as JAL
Assisted-by: DeepSeek V4 Pro
This commit is contained in:
+21
-29
@@ -100,13 +100,14 @@ func assembleRISCV(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, [
|
||||
if c16, ok := tryCompressRVC(r.instr, fi); ok {
|
||||
code = []byte{byte(c16), byte(c16 >> 8)}
|
||||
}
|
||||
// Make newly added relocation offsets absolute (subtract prologue to make
|
||||
// them function-relative, then the caller adds fn.Offset). After
|
||||
// points just past the AUIPC+second-instruction pair, which is
|
||||
// always 8 bytes wide for these static-symbol references.
|
||||
// Make newly added relocation offsets function-relative. Each
|
||||
// instruction records its reloc offset relative to its own start;
|
||||
// the current pc is that instruction's offset from the function
|
||||
// start (which includes the prologue). After is the address just
|
||||
// past the relocated field, shifted by the same amount.
|
||||
for j := preCount; j < len(relocs); j++ {
|
||||
relocs[j].Off += pc - len(prologue)
|
||||
relocs[j].After = relocs[j].Off + 8
|
||||
relocs[j].Off += pc
|
||||
relocs[j].After += pc
|
||||
}
|
||||
preCount = len(relocs)
|
||||
// The RET's epilogue closes the frame: the SP delta returns to zero
|
||||
@@ -175,32 +176,23 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
|
||||
// Handle pseudo-instructions and special cases first.
|
||||
switch mnem {
|
||||
case "RET":
|
||||
// RET = epilogue (restore LR and close the frame when present) + C.JR ra.
|
||||
// RET = epilogue (restore LR and close the frame when present) +
|
||||
// uncompressed JALR X0, 0(X1) (the toolchain never compresses RET).
|
||||
return riscvReturn(fi), nil
|
||||
case "CALL":
|
||||
// CALL target → AUIPC X1, %pcrel_hi + JALR X1, %pcrel_lo(X1).
|
||||
// For now, emit AUIPC X1, 0 + JALR X1, 0(X1) with zero offsets.
|
||||
// The relocation system will fill the actual offsets.
|
||||
if len(ops) >= 1 {
|
||||
target := labelFromOperand(ops[0])
|
||||
targetOff, ok := offsets[target]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("undefined label %q%s", target, suggestLabel(target, offsets))
|
||||
}
|
||||
offset := int32(targetOff - pc)
|
||||
// AUIPC X1, upper 20 bits
|
||||
hi := (offset + 0x800) >> 12
|
||||
word1 := riscvUType(riscvEnc{0x17, 0x0, 0x00}, 1, hi<<12)
|
||||
// JALR X1, lower 12 bits(X1)
|
||||
lo := offset - (hi << 12)
|
||||
word2 := riscvIType(riscvEnc{0x67, 0x0, 0x00}, 1, 1, lo)
|
||||
var out []byte
|
||||
out = append(out, byte(word1), byte(word1>>8), byte(word1>>16), byte(word1>>24))
|
||||
out = append(out, byte(word2), byte(word2>>8), byte(word2>>16), byte(word2>>24))
|
||||
return out, nil
|
||||
// CALL sym(SB) → JAL X1, sym(SB) with a single R_RISCV_JAL
|
||||
// relocation. The Go assembler rejects CALL to a local branch label.
|
||||
if len(ops) != 1 {
|
||||
return nil, fmt.Errorf("CALL expects 1 operand, got %d", len(ops))
|
||||
}
|
||||
// CALL with no target: encode as NOP (unsupported).
|
||||
word = riscvIType(riscvEnc{0x13, 0x0, 0x00}, 0, 0, 0)
|
||||
op := ops[0]
|
||||
if op.Addr.Sym == nil || op.Addr.Sym.Pseudo != "SB" {
|
||||
return nil, fmt.Errorf("CALL: local branch target is not supported (use CALL sym(SB))")
|
||||
}
|
||||
if relocs != nil {
|
||||
*relocs = append(*relocs, Reloc{Off: 0, After: 4, Name: op.Addr.Sym.Name, Kind: RelRISCVJal, Addend: op.Addr.Sym.Offset})
|
||||
}
|
||||
word = riscvJType(1, 0) // JAL X1, 0 — the linker fills the offset
|
||||
return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}, nil
|
||||
case "JMP":
|
||||
// JMP = JAL X0, target. The Go assembler never compresses this to
|
||||
|
||||
Reference in New Issue
Block a user