feat(riscv64): GOROOT instruction shapes, DATA order and offset expressions
Assisted-by: GLM 5.3 Flash
This commit is contained in:
+143
-7
@@ -135,6 +135,43 @@ func assembleRISCV(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, [
|
||||
return out, offsets, relocs, lines, spadj, nil
|
||||
}
|
||||
|
||||
// riscvImmAlias maps the R-type ALU mnemonics onto their I-type immediate
|
||||
// forms: the toolchain accepts ADD $imm, rj, rd and emits addi. Applied
|
||||
// whenever the first operand is an immediate.
|
||||
var riscvImmAlias = map[string]string{
|
||||
"ADD": "ADDI",
|
||||
"ADDW": "ADDIW",
|
||||
"AND": "ANDI",
|
||||
"OR": "ORI",
|
||||
"XOR": "XORI",
|
||||
"SLL": "SLLI",
|
||||
"SRL": "SRLI",
|
||||
"SRA": "SRAI",
|
||||
"SLLW": "SLLIW",
|
||||
"SRLW": "SRLIW",
|
||||
"SRAW": "SRAIW",
|
||||
}
|
||||
|
||||
// riscvNormaliseImmAlias rewrites the mnemonic to its immediate form when the
|
||||
// first operand is an immediate: the toolchain accepts ADD $imm, rj, rd and
|
||||
// emits addi, and SUB $imm becomes addi with the negated immediate. The
|
||||
// second result reports that negation; the operand itself is left untouched
|
||||
// because several passes normalise the same instruction.
|
||||
func riscvNormaliseImmAlias(mnem string, ops []*ast.Operand) (string, bool) {
|
||||
if len(ops) >= 2 && isImmOperand(ops[0]) {
|
||||
switch strings.ToUpper(mnem) {
|
||||
case "SUB":
|
||||
return "ADDI", true
|
||||
case "SUBW":
|
||||
return "ADDIW", true
|
||||
}
|
||||
if alias, ok := riscvImmAlias[strings.ToUpper(mnem)]; ok {
|
||||
return alias, false
|
||||
}
|
||||
}
|
||||
return mnem, false
|
||||
}
|
||||
|
||||
// riscvInstrSize returns the encoded size in bytes of a RISC-V instruction.
|
||||
// Most instructions are 4 bytes; MOV with a large immediate and I-type
|
||||
// arithmetic with a large immediate expand to several (possibly compressed)
|
||||
@@ -142,10 +179,12 @@ func assembleRISCV(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, [
|
||||
func riscvInstrSize(instr *ast.Instr, fi riscvFrameInfo) int {
|
||||
mnem := instr.Mnemonic.Text
|
||||
ops := instr.Operands
|
||||
var immNeg bool
|
||||
mnem, immNeg = riscvNormaliseImmAlias(mnem, ops)
|
||||
if mnem == "RET" {
|
||||
return len(riscvReturn(fi))
|
||||
}
|
||||
if mnem == "MOV" && len(ops) == 2 {
|
||||
if strings.HasPrefix(mnem, "MOV") && len(ops) == 2 {
|
||||
// MOV $sym(SB), rd → 8 bytes (AUIPC + ADDI).
|
||||
if isImmOperand(ops[0]) && ops[0].Imm.Sym != nil && ops[0].Imm.Sym.Pseudo == "SB" {
|
||||
return 8
|
||||
@@ -173,7 +212,11 @@ func riscvInstrSize(instr *ast.Instr, fi riscvFrameInfo) int {
|
||||
}
|
||||
// I-type arithmetic with a large immediate expands to several instructions.
|
||||
if (mnem == "ADDI" || mnem == "ANDI" || mnem == "ORI" || mnem == "XORI") && len(ops) >= 1 && isImmOperand(ops[0]) {
|
||||
return riscvItypeImmediateSize(mnem, immFromOperand(ops[0]))
|
||||
imm := immFromOperand(ops[0])
|
||||
if immNeg {
|
||||
imm = -imm
|
||||
}
|
||||
return riscvItypeImmediateSize(mnem, imm)
|
||||
}
|
||||
return 4
|
||||
}
|
||||
@@ -192,6 +235,8 @@ func isBranchLike(mnem string) bool {
|
||||
func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscvFrameInfo, relocs *[]Reloc) ([]byte, error) {
|
||||
mnem := instr.Mnemonic.Text
|
||||
ops := instr.Operands
|
||||
var immNeg bool
|
||||
mnem, immNeg = riscvNormaliseImmAlias(mnem, ops)
|
||||
var word uint32
|
||||
|
||||
// Handle pseudo-instructions and special cases first.
|
||||
@@ -279,14 +324,50 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
|
||||
return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}, nil
|
||||
|
||||
// MOV is a pseudo-instruction that the Go assembler uses for loads,
|
||||
// stores, register moves and immediate loads.
|
||||
case "MOV":
|
||||
// stores, register moves and immediate loads. The width suffixes
|
||||
// (MOVB/MOVH/MOVW and unsigned forms) select the access width, and
|
||||
// MOVD/MOVF address the FP registers.
|
||||
case "MOV", "MOVB", "MOVBU", "MOVH", "MOVHU", "MOVW", "MOVWU", "MOVF", "MOVD":
|
||||
return encodeRISCVMov(instr, fi, relocs)
|
||||
|
||||
// JALR: indirect jump/call. Plan 9: JALR rs1, rd or JALR offset(rs1).
|
||||
case "JALR":
|
||||
return encodeRISCVJALR(instr, fi)
|
||||
|
||||
// Branch-zero pseudos: BEQZ/BNEZ compare against X0, and BLTZ/BGEZ/
|
||||
// BLEZ/BGTZ reorder the register operands of BLT/BGE accordingly.
|
||||
case "BEQZ", "BNEZ", "BLTZ", "BGEZ", "BLEZ", "BGTZ":
|
||||
if len(ops) != 2 {
|
||||
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
rs := regFromOperand(ops[0])
|
||||
if rs < 0 {
|
||||
return nil, fmt.Errorf("%s: invalid register", mnem)
|
||||
}
|
||||
target := labelFromOperand(ops[1])
|
||||
targetOff, ok := offsets[target]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("undefined label %q%s", target, suggestLabel(target, offsets))
|
||||
}
|
||||
var enc riscvEnc
|
||||
rs1, rs2 := rs, 0
|
||||
switch mnem {
|
||||
case "BEQZ":
|
||||
enc = riscvEnc{0x63, 0x0, 0x00} // beq rs, x0
|
||||
case "BNEZ":
|
||||
enc = riscvEnc{0x63, 0x1, 0x00} // bne rs, x0
|
||||
case "BLTZ":
|
||||
enc = riscvEnc{0x63, 0x4, 0x00} // blt rs, x0
|
||||
case "BGEZ":
|
||||
enc = riscvEnc{0x63, 0x5, 0x00} // bge rs, x0
|
||||
case "BLEZ":
|
||||
enc, rs1, rs2 = riscvEnc{0x63, 0x5, 0x00}, 0, rs // bge x0, rs
|
||||
case "BGTZ":
|
||||
enc, rs1, rs2 = riscvEnc{0x63, 0x4, 0x00}, 0, rs // blt x0, rs
|
||||
}
|
||||
word = riscvBType(enc, rs1, rs2, int32(targetOff-pc))
|
||||
return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}, nil
|
||||
|
||||
// System instructions with no operands.
|
||||
case "FENCE", "ECALL", "EBREAK":
|
||||
enc, ok := riscvInstrTable[mnem]
|
||||
@@ -481,6 +562,9 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
|
||||
// two-operand form INSTR $imm, rd uses rd as the source.
|
||||
case len(ops) == 3 && isITypeInstr(mnem):
|
||||
imm := immFromOperand(ops[0]) // immediate
|
||||
if immNeg {
|
||||
imm = -imm // SUB $imm arrived through the ADDI alias
|
||||
}
|
||||
rs1 := regFromOperand(ops[1]) // source register
|
||||
rd := regFromOperand(ops[2]) // destination
|
||||
if rd < 0 || rs1 < 0 {
|
||||
@@ -490,6 +574,9 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
|
||||
|
||||
case len(ops) == 2 && isITypeInstr(mnem):
|
||||
imm := immFromOperand(ops[0])
|
||||
if immNeg {
|
||||
imm = -imm
|
||||
}
|
||||
rd := regFromOperand(ops[1])
|
||||
if rd < 0 {
|
||||
return nil, fmt.Errorf("invalid register in %s", mnem)
|
||||
@@ -626,7 +713,7 @@ func encodeRISCVMov(instr *ast.Instr, fi riscvFrameInfo, relocs *[]Reloc) ([]byt
|
||||
if rd < 0 || rs1 < 0 {
|
||||
return nil, fmt.Errorf("MOV load: invalid operand")
|
||||
}
|
||||
return riscvFrameMemOp(riscvEnc{0x03, 0x3, 0x00}, false, rd, rs1, off), nil
|
||||
return riscvFrameMemOp(riscvMovEnc(strings.ToUpper(instr.Mnemonic.Text), false), false, rd, rs1, off), nil
|
||||
}
|
||||
|
||||
// Register → memory (store).
|
||||
@@ -643,21 +730,70 @@ func encodeRISCVMov(instr *ast.Instr, fi riscvFrameInfo, relocs *[]Reloc) ([]byt
|
||||
if rs2 < 0 || rs1 < 0 {
|
||||
return nil, fmt.Errorf("MOV store: invalid operand")
|
||||
}
|
||||
return riscvFrameMemOp(riscvEnc{0x23, 0x3, 0x00}, true, rs2, rs1, off), nil
|
||||
return riscvFrameMemOp(riscvMovEnc(strings.ToUpper(instr.Mnemonic.Text), true), true, rs2, rs1, off), nil
|
||||
}
|
||||
|
||||
// Register → register (ADDI $0, src, dst).
|
||||
// Register → register: MOVD/MOVF are FP moves (fsgnj with rs2 = rs1),
|
||||
// everything else is ADDI $0, src, dst.
|
||||
{
|
||||
rs1 := regFromOperand(src)
|
||||
rd := regFromOperand(dst)
|
||||
if rd < 0 || rs1 < 0 {
|
||||
return nil, fmt.Errorf("MOV: invalid register operand")
|
||||
}
|
||||
mnem := strings.ToUpper(instr.Mnemonic.Text)
|
||||
if mnem == "MOVD" || mnem == "MOVF" {
|
||||
op := uint32(0x20000053) // FSGNJ.S
|
||||
if mnem == "MOVD" {
|
||||
op = 0x22000053 // FSGNJ.D
|
||||
}
|
||||
return wordLE(op | uint32(rs1)<<15 | uint32(rs1)<<20 | uint32(rd)<<7), nil
|
||||
}
|
||||
word := riscvIType(riscvEnc{0x13, 0x0, 0x00}, rd, rs1, 0)
|
||||
return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// riscvMovEnc returns the load (store=false) or store (store=true) opcode for
|
||||
// a MOV-family mnemonic: the suffix selects the access width, MOVD and MOVF
|
||||
// select the FP load/store opcodes, and bare MOV is the 64-bit integer form.
|
||||
func riscvMovEnc(mnem string, store bool) riscvEnc {
|
||||
if store {
|
||||
switch mnem {
|
||||
case "MOVB":
|
||||
return riscvEnc{0x23, 0x0, 0x00} // SB
|
||||
case "MOVH":
|
||||
return riscvEnc{0x23, 0x1, 0x00} // SH
|
||||
case "MOVW":
|
||||
return riscvEnc{0x23, 0x2, 0x00} // SW
|
||||
case "MOVF":
|
||||
return riscvEnc{0x27, 0x2, 0x00} // FSW
|
||||
case "MOVD":
|
||||
return riscvEnc{0x27, 0x3, 0x00} // FSD
|
||||
}
|
||||
return riscvEnc{0x23, 0x3, 0x00} // SD
|
||||
}
|
||||
switch mnem {
|
||||
case "MOVB":
|
||||
return riscvEnc{0x03, 0x0, 0x00} // LB
|
||||
case "MOVBU":
|
||||
return riscvEnc{0x03, 0x4, 0x00} // LBU
|
||||
case "MOVH":
|
||||
return riscvEnc{0x03, 0x1, 0x00} // LH
|
||||
case "MOVHU":
|
||||
return riscvEnc{0x03, 0x5, 0x00} // LHU
|
||||
case "MOVW":
|
||||
return riscvEnc{0x03, 0x2, 0x00} // LW
|
||||
case "MOVWU":
|
||||
return riscvEnc{0x03, 0x6, 0x00} // LWU
|
||||
case "MOVF":
|
||||
return riscvEnc{0x07, 0x2, 0x00} // FLW
|
||||
case "MOVD":
|
||||
return riscvEnc{0x07, 0x3, 0x00} // FLD
|
||||
}
|
||||
return riscvEnc{0x03, 0x3, 0x00} // LD
|
||||
}
|
||||
|
||||
// riscvFrameMemOp encodes a register-relative load (store=false, I-type
|
||||
// width 0x03) or store (store=true, S-type width 0x23) of the 64-bit width
|
||||
// at off(rs1). Offsets beyond the signed 12-bit range materialise the
|
||||
|
||||
Reference in New Issue
Block a user