feat(riscv): add MOV pseudo-instruction and RVC compressed encoding
Assisted-by: DeepSeek V4 Pro
This commit is contained in:
+379
-13
@@ -10,39 +10,115 @@ import (
|
||||
)
|
||||
|
||||
// assembleRISCV assembles a RISC-V TEXT function body into machine code.
|
||||
// It handles the core RV64I/RV64M instruction set with fixed 32-bit encoding.
|
||||
// It handles the core RV64IMAFDC instruction set.
|
||||
func assembleRISCV(t *ast.Text) ([]byte, map[string]int, error) {
|
||||
fi := riscvComputeFrame(t)
|
||||
prologue := riscvPrologue(fi)
|
||||
|
||||
// First pass: compute label offsets (all RISC-V instructions are 4 bytes).
|
||||
// Labels are offset by the prologue length.
|
||||
// Pass 1: collect instructions and compute label offsets assuming 4 bytes
|
||||
// per instruction (or 8 for MOV $large-imm). No encoding yet.
|
||||
type instrRec struct {
|
||||
instr *ast.Instr
|
||||
compressed bool
|
||||
code []byte
|
||||
}
|
||||
var recs []instrRec
|
||||
offsets := map[string]int{}
|
||||
var instrs []*ast.Instr
|
||||
pos := len(prologue)
|
||||
for _, stmt := range t.Body {
|
||||
switch s := stmt.(type) {
|
||||
case *ast.Label:
|
||||
offsets[s.Name.Text] = pos
|
||||
case *ast.Instr:
|
||||
instrs = append(instrs, s)
|
||||
pos += 4 // all RISC-V instructions are 4 bytes
|
||||
recs = append(recs, instrRec{instr: s})
|
||||
pos += riscvInstrSize(s)
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: encode instructions.
|
||||
out := append([]byte(nil), prologue...)
|
||||
for _, instr := range instrs {
|
||||
code, err := encodeRISCVInstr(instr, pos, offsets, fi)
|
||||
// Pass 2: encode each instruction using Pass-1 offsets.
|
||||
pc := len(prologue)
|
||||
for i := range recs {
|
||||
code, err := encodeRISCVInstr(recs[i].instr, pc, offsets, fi)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("%s: %w", instr.Mnemonic.Text, err)
|
||||
return nil, nil, fmt.Errorf("%s: %w", recs[i].instr.Mnemonic.Text, err)
|
||||
}
|
||||
recs[i].code = code
|
||||
pc += len(code)
|
||||
}
|
||||
|
||||
// Pass 3: try RVC compression.
|
||||
for i := range recs {
|
||||
if c16, ok := tryCompressRVC(recs[i].instr, fi); ok {
|
||||
recs[i].compressed = true
|
||||
recs[i].code = []byte{byte(c16), byte(c16 >> 8)}
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 4: recompute offsets with actual sizes.
|
||||
offsets = map[string]int{}
|
||||
pos = len(prologue)
|
||||
for _, stmt := range t.Body {
|
||||
switch s := stmt.(type) {
|
||||
case *ast.Label:
|
||||
offsets[s.Name.Text] = pos
|
||||
case *ast.Instr:
|
||||
for _, r := range recs {
|
||||
if r.instr == s {
|
||||
pos += len(r.code)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 5: re-encode branches with corrected offsets, emit uncompressed
|
||||
// for instructions that can't be compressed.
|
||||
out := append([]byte(nil), prologue...)
|
||||
pc = len(prologue)
|
||||
for _, r := range recs {
|
||||
if r.compressed && !isBranchLike(r.instr.Mnemonic.Text) {
|
||||
out = append(out, r.code...)
|
||||
pc += len(r.code)
|
||||
} else {
|
||||
// Re-encode with correct offsets (branches need this).
|
||||
code, err := encodeRISCVInstr(r.instr, pc, offsets, fi)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// Try compression again for this instruction.
|
||||
if c16, ok := tryCompressRVC(r.instr, fi); ok {
|
||||
code = []byte{byte(c16), byte(c16 >> 8)}
|
||||
}
|
||||
out = append(out, code...)
|
||||
pc += len(code)
|
||||
}
|
||||
out = append(out, code...)
|
||||
pos -= 4 // track remaining
|
||||
}
|
||||
return out, offsets, nil
|
||||
}
|
||||
|
||||
// riscvInstrSize returns the encoded size in bytes of a RISC-V instruction.
|
||||
// Most instructions are 4 bytes; MOV with a large immediate is 8 (LUI+ADDIW).
|
||||
func riscvInstrSize(instr *ast.Instr) int {
|
||||
mnem := instr.Mnemonic.Text
|
||||
if mnem == "MOV" && len(instr.Operands) == 2 && isImmOperand(instr.Operands[0]) {
|
||||
imm := immFromOperand(instr.Operands[0])
|
||||
if imm < -2048 || imm > 2047 {
|
||||
return 8 // LUI + ADDIW
|
||||
}
|
||||
}
|
||||
return 4
|
||||
}
|
||||
|
||||
// isBranchLike reports whether a mnemonic is a branch or jump that needs
|
||||
// recalculated offsets after compression.
|
||||
func isBranchLike(mnem string) bool {
|
||||
switch mnem {
|
||||
case "BEQ", "BNE", "BLT", "BGE", "BLTU", "BGEU", "JMP", "JAL":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// encodeRISCVInstr encodes a single RISC-V instruction.
|
||||
func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscvFrameInfo) ([]byte, error) {
|
||||
mnem := instr.Mnemonic.Text
|
||||
@@ -88,6 +164,11 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
|
||||
offset := int32(targetOff - pc)
|
||||
word = riscvJType(rd, offset)
|
||||
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":
|
||||
return encodeRISCVMov(instr, offsets, fi)
|
||||
}
|
||||
|
||||
// FP conversion / move instructions use a separate table (rs2 encodes
|
||||
@@ -166,6 +247,16 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
|
||||
}
|
||||
word = riscvRType(enc, rd, rs1, rs2)
|
||||
|
||||
// I-type shift (SLLI, SRLI, SRAI): INSTR rs, $shamt, rd.
|
||||
case len(ops) == 3 && isShiftImmInstr(mnem):
|
||||
rs1 := regFromOperand(ops[0])
|
||||
shamt := int(immFromOperand(ops[1]))
|
||||
rd := regFromOperand(ops[2])
|
||||
if rd < 0 || rs1 < 0 {
|
||||
return nil, fmt.Errorf("invalid register in %s", mnem)
|
||||
}
|
||||
word = riscvRType(enc, rd, rs1, shamt)
|
||||
|
||||
// AMO atomics: Plan 9 order is INSTR src, (addr), dst.
|
||||
case len(ops) == 3 && isAMOInstr(mnem):
|
||||
rs2 := regFromOperand(ops[0]) // source value
|
||||
@@ -304,6 +395,273 @@ 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
|
||||
}
|
||||
|
||||
// isMemOperand reports whether an operand is a memory reference
|
||||
// (frame-relative such as name+off(FP) or register-relative such as (X10)).
|
||||
func isMemOperand(op *ast.Operand) bool {
|
||||
if op.Addr.Sym != nil && op.Addr.Sym.Pseudo != "" {
|
||||
return true // name+off(FP), name+off(SP)
|
||||
}
|
||||
if op.Addr.Base != "" && op.Addr.Sym == nil {
|
||||
return true // (reg)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isImmOperand reports whether an operand is an immediate ($value).
|
||||
func isImmOperand(op *ast.Operand) bool {
|
||||
if op.Kind == ast.OpImmediate {
|
||||
return true
|
||||
}
|
||||
if op.Imm.HasVal {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// encodeRISCVMov encodes the MOV pseudo-instruction.
|
||||
//
|
||||
// The Go RISC-V assembler uses MOV for:
|
||||
// - MOV name+off(FP), Rd load from frame
|
||||
// - MOV Rd, name+off(FP) store to frame
|
||||
// - MOV (Rs), Rd register-relative load
|
||||
// - MOV Rs, (Rd) register-relative store
|
||||
// - MOV Rs, Rd register-to-register move (ADDI $0)
|
||||
// - MOV $imm, Rd load immediate (ADDI or LUI+ADDIW)
|
||||
func encodeRISCVMov(instr *ast.Instr, offsets map[string]int, fi riscvFrameInfo) ([]byte, error) {
|
||||
ops := instr.Operands
|
||||
if len(ops) != 2 {
|
||||
return nil, fmt.Errorf("MOV expects 2 operands, got %d", len(ops))
|
||||
}
|
||||
|
||||
src := ops[0]
|
||||
dst := ops[1]
|
||||
|
||||
// Immediate → register.
|
||||
if isImmOperand(src) {
|
||||
rd := regFromOperand(dst)
|
||||
if rd < 0 {
|
||||
return nil, fmt.Errorf("MOV $imm: invalid destination register")
|
||||
}
|
||||
imm := immFromOperand(src)
|
||||
return encodeRISCVLoadImm(rd, imm), nil
|
||||
}
|
||||
|
||||
// Memory → register (load).
|
||||
if isMemOperand(src) && !isMemOperand(dst) {
|
||||
rd := regFromOperand(dst)
|
||||
rs1, off := memFromOperandWithFrame(src, fi)
|
||||
if rd < 0 || rs1 < 0 {
|
||||
return nil, fmt.Errorf("MOV load: invalid operand")
|
||||
}
|
||||
word := riscvIType(riscvEnc{0x03, 0x3, 0x00}, rd, rs1, off)
|
||||
return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}, nil
|
||||
}
|
||||
|
||||
// Register → memory (store).
|
||||
if !isMemOperand(src) && isMemOperand(dst) {
|
||||
rs2 := regFromOperand(src)
|
||||
rs1, off := memFromOperandWithFrame(dst, fi)
|
||||
if rs2 < 0 || rs1 < 0 {
|
||||
return nil, fmt.Errorf("MOV store: invalid operand")
|
||||
}
|
||||
word := riscvSType(riscvEnc{0x23, 0x3, 0x00}, rs1, rs2, off)
|
||||
return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}, nil
|
||||
}
|
||||
|
||||
// Register → register (ADDI $0, src, dst).
|
||||
{
|
||||
rs1 := regFromOperand(src)
|
||||
rd := regFromOperand(dst)
|
||||
if rd < 0 || rs1 < 0 {
|
||||
return nil, fmt.Errorf("MOV: invalid register operand")
|
||||
}
|
||||
word := riscvIType(riscvEnc{0x13, 0x0, 0x00}, rd, rs1, 0)
|
||||
return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// encodeRISCVLoadImm encodes loading an immediate into a register.
|
||||
// For 12-bit immediates: ADDI $imm, ZERO, rd.
|
||||
// For larger: LUI $hi, rd + ADDIW $lo, rd, rd.
|
||||
func encodeRISCVLoadImm(rd int, imm int32) []byte {
|
||||
if imm >= -2048 && imm <= 2047 {
|
||||
word := riscvIType(riscvEnc{0x13, 0x0, 0x00}, rd, 0, imm)
|
||||
return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}
|
||||
}
|
||||
|
||||
// LUI + ADDIW for larger constants.
|
||||
var out []byte
|
||||
hi := int32((uint32(imm)+0x800)>>12) << 12 // LUI loads upper 20 bits
|
||||
lo := imm - hi
|
||||
wordLUI := riscvUType(riscvEnc{0x37, 0x0, 0x00}, rd, hi)
|
||||
out = append(out, byte(wordLUI), byte(wordLUI>>8), byte(wordLUI>>16), byte(wordLUI>>24))
|
||||
if lo != 0 {
|
||||
wordADDIW := riscvIType(riscvEnc{0x1B, 0x0, 0x00}, rd, rd, lo)
|
||||
out = append(out, byte(wordADDIW), byte(wordADDIW>>8), byte(wordADDIW>>16), byte(wordADDIW>>24))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// tryCompressRVC attempts to compress a RISC-V instruction to its 16-bit
|
||||
// RVC form. It returns the compressed instruction word and true on success.
|
||||
func tryCompressRVC(instr *ast.Instr, fi riscvFrameInfo) (uint16, bool) {
|
||||
mnem := instr.Mnemonic.Text
|
||||
ops := instr.Operands
|
||||
|
||||
switch mnem {
|
||||
case "RET":
|
||||
// RET = JALR X0, 0(X1) → C.JR RA (CR-type: funct4=0x8, rd=0, rs2=1)
|
||||
return rvcCR(0x8, 0, 1), true
|
||||
|
||||
case "LD", "MOV":
|
||||
// LD rd, offset(SP) → C.LDSP when rd≠0 and uimm[8:3] fits.
|
||||
// MOV name+off(FP), rd → load, same compression.
|
||||
if mnem == "MOV" && len(ops) == 2 && isImmOperand(ops[0]) {
|
||||
return 0, false
|
||||
}
|
||||
// MOV reg, reg → C.MV (CR-type: funct4=0x8).
|
||||
if mnem == "MOV" && len(ops) == 2 && !isMemOperand(ops[0]) && !isMemOperand(ops[1]) && !isImmOperand(ops[0]) {
|
||||
rs1 := regFromOperand(ops[0])
|
||||
rd := regFromOperand(ops[1])
|
||||
if rs1 != -1 && rd != -1 && rs1 != 0 && rd != 0 {
|
||||
return rvcCR(0x8, uint32(rd), uint32(rs1)), true
|
||||
}
|
||||
}
|
||||
rd, rs1, imm := extractLDParams(instr, fi)
|
||||
if rs1 == 2 && rd != 0 && rd != -1 && imm >= 0 && imm < 512 && imm%8 == 0 {
|
||||
return rvcCI(0x3, uint32(rd), uint32(imm)>>3), true
|
||||
}
|
||||
// MOV reg, mem → store, try C.SDSP.
|
||||
if mnem == "MOV" && len(ops) == 2 && !isMemOperand(ops[0]) && isMemOperand(ops[1]) {
|
||||
rs2, rs1, imm := extractSDParams(instr, fi)
|
||||
if rs1 == 2 && rs2 != -1 && imm >= 0 && imm < 512 && imm%8 == 0 {
|
||||
return rvcCSS(0x7, uint32(rs2), uint32(imm)>>3), true
|
||||
}
|
||||
}
|
||||
|
||||
case "SD":
|
||||
// SD rs2, offset(SP) → C.SDSP when uimm[8:3] fits (CSS-type).
|
||||
rs2, rs1, imm := extractSDParams(instr, fi)
|
||||
if rs1 == 2 && rs2 != -1 && imm >= 0 && imm < 512 && imm%8 == 0 {
|
||||
uimm := uint32(imm) >> 3
|
||||
return rvcCSS(0x7, uint32(rs2), uimm), true
|
||||
}
|
||||
|
||||
case "ADDI":
|
||||
rd, rs1, imm := extractITypeParams(instr, fi)
|
||||
if rd == -1 || rs1 == -1 {
|
||||
return 0, false
|
||||
}
|
||||
if rd == rs1 && rd != 0 && imm != 0 && imm >= -32 && imm <= 31 {
|
||||
// C.ADDI: funct3=0x0, rs1/rd, nzimm[5:0]
|
||||
return rvcCI(0x0, uint32(rd), uint32(imm)&0x3F), true
|
||||
}
|
||||
if rs1 == 0 && rd != 0 && imm >= -32 && imm <= 31 {
|
||||
// C.LI: funct3=0x2, rd, imm[5:0]
|
||||
return rvcCI(0x2, uint32(rd), uint32(imm)&0x3F), true
|
||||
}
|
||||
if rs1 != 0 && rd != 0 && imm == 0 {
|
||||
// C.MV: funct4=0x8, rd, rs1 (CR-type)
|
||||
return rvcCR(0x8, uint32(rd), uint32(rs1)), true
|
||||
}
|
||||
|
||||
case "JAL":
|
||||
// JAL X0, target → C.J when offset fits in ±2KB.
|
||||
if len(ops) >= 1 {
|
||||
// For JAL with implicit rd=0 (JMP alias), check target.
|
||||
// C.J: funct3=0x5
|
||||
// Offset is computed at encode time — we can't check it here.
|
||||
return 0, false
|
||||
}
|
||||
|
||||
case "JMP":
|
||||
// C.J — but offset is computed at encode time.
|
||||
return 0, false
|
||||
|
||||
case "BEQ":
|
||||
// BEQ rs, ZERO, target → C.BEQZ when offset fits in ±256.
|
||||
if len(ops) == 3 {
|
||||
rs1 := regFromOperand(ops[0])
|
||||
rs2 := regFromOperand(ops[1])
|
||||
if rs1 != -1 && rs2 == 0 && isRVCIntReg(rs1) {
|
||||
// Could be C.BEQZ but offset computed at encode time.
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
case "BNE":
|
||||
// BNE rs, ZERO, target → C.BNEZ when offset fits in ±256.
|
||||
if len(ops) == 3 {
|
||||
rs1 := regFromOperand(ops[0])
|
||||
rs2 := regFromOperand(ops[1])
|
||||
if rs1 != -1 && rs2 == 0 && isRVCIntReg(rs1) {
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
case "LUI":
|
||||
// LUI rd, imm → C.LUI when rd≠0, rd≠SP, imm nonzero and fits in 6 bits.
|
||||
if len(ops) == 2 {
|
||||
rd := regFromOperand(ops[0])
|
||||
imm := immFromOperand(ops[1])
|
||||
if rd != -1 && rd != 0 && rd != 2 && imm != 0 && imm >= 1 && imm <= 63 {
|
||||
return rvcCI(0x3, uint32(rd), uint32(imm)&0x3F), true
|
||||
}
|
||||
}
|
||||
|
||||
case "ADDIW":
|
||||
rd, rs1, imm := extractITypeParams(instr, fi)
|
||||
if rd == rs1 && rd != 0 && imm >= -32 && imm <= 31 {
|
||||
return rvcCI(0x1, uint32(rd), uint32(imm)&0x3F), true
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// extractLDParams extracts rd, rs1, and immediate offset for a load instruction.
|
||||
func extractLDParams(instr *ast.Instr, fi riscvFrameInfo) (rd, rs1 int, imm int32) {
|
||||
ops := instr.Operands
|
||||
if len(ops) != 2 {
|
||||
return -1, -1, 0
|
||||
}
|
||||
if instr.Mnemonic.Text == "MOV" {
|
||||
if isMemOperand(ops[0]) {
|
||||
rs1, imm = memFromOperandWithFrame(ops[0], fi)
|
||||
rd = regFromOperand(ops[1])
|
||||
} else {
|
||||
return -1, -1, 0
|
||||
}
|
||||
} else {
|
||||
rs1, imm = memFromOperandWithFrame(ops[0], fi)
|
||||
rd = regFromOperand(ops[1])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// extractSDParams extracts rs2, rs1, and immediate offset for a store instruction.
|
||||
func extractSDParams(instr *ast.Instr, fi riscvFrameInfo) (rs2, rs1 int, imm int32) {
|
||||
ops := instr.Operands
|
||||
if len(ops) != 2 {
|
||||
return -1, -1, 0
|
||||
}
|
||||
rs2 = regFromOperand(ops[0])
|
||||
rs1, imm = memFromOperandWithFrame(ops[1], fi)
|
||||
return
|
||||
}
|
||||
|
||||
// extractITypeParams extracts rd, rs1, and immediate for an I-type instruction.
|
||||
func extractITypeParams(instr *ast.Instr, fi riscvFrameInfo) (rd, rs1 int, imm int32) {
|
||||
ops := instr.Operands
|
||||
if len(ops) != 3 {
|
||||
return -1, -1, 0
|
||||
}
|
||||
rs1 = regFromOperand(ops[0])
|
||||
imm = immFromOperand(ops[1])
|
||||
rd = regFromOperand(ops[2])
|
||||
return
|
||||
}
|
||||
|
||||
// Instruction type classifiers.
|
||||
func isRTypeInstr(m string) bool {
|
||||
switch m {
|
||||
@@ -316,6 +674,14 @@ func isRTypeInstr(m string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func isShiftImmInstr(m string) bool {
|
||||
switch m {
|
||||
case "SLLI", "SRLI", "SRAI", "SLLIW", "SRLIW", "SRAIW":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isITypeInstr(m string) bool {
|
||||
switch m {
|
||||
case "ADDI", "ADDIW", "SLTI", "SLTIU", "XORI", "ORI", "ANDI", "JALR":
|
||||
|
||||
@@ -171,6 +171,13 @@ var riscvInstrTable = map[string]riscvEnc{
|
||||
"SLLW": {0x3B, 0x1, 0x00},
|
||||
"SRLW": {0x3B, 0x5, 0x00},
|
||||
"SRAW": {0x3B, 0x5, 0x20},
|
||||
// RV64I — I-type shift-immediate (shamt in rs2 field).
|
||||
"SLLI": {0x13, 0x1, 0x00},
|
||||
"SRLI": {0x13, 0x5, 0x00},
|
||||
"SRAI": {0x13, 0x5, 0x20},
|
||||
"SLLIW": {0x1B, 0x1, 0x00},
|
||||
"SRLIW": {0x1B, 0x5, 0x00},
|
||||
"SRAIW": {0x1B, 0x5, 0x20},
|
||||
// RV64M — multiply/divide.
|
||||
"MUL": {0x33, 0x0, 0x01},
|
||||
"MULH": {0x33, 0x1, 0x01},
|
||||
@@ -429,3 +436,74 @@ func riscvJType(rd int, offset int32) uint32 {
|
||||
(uint32(rd) << 7) |
|
||||
0x6F // JAL opcode
|
||||
}
|
||||
|
||||
// ---- RVC (compressed) encoding helpers ----
|
||||
|
||||
// isRVCIntReg reports whether a register number can be encoded in the 3-bit
|
||||
// prime register field used by compressed instructions (x8–x15).
|
||||
func isRVCIntReg(r int) bool { return r >= 8 && r <= 15 }
|
||||
|
||||
// rvcReg3 returns the 3-bit encoding for registers x8–x15 (0–7).
|
||||
func rvcReg3(r int) uint32 { return uint32(r - 8) }
|
||||
|
||||
// rvcCR encodes a CR-type (register) compressed instruction.
|
||||
// Format: funct4 | rd/rs1 | rs2 | op=2.
|
||||
func rvcCR(funct4, rd, rs2 uint32) uint16 {
|
||||
return uint16((funct4 << 12) | (rd << 7) | (rs2 << 2) | 0x2)
|
||||
}
|
||||
|
||||
// rvcCI encodes a CI-type (immediate) compressed instruction.
|
||||
func rvcCI(funct3, rd uint32, imm uint32) uint16 {
|
||||
return uint16((funct3 << 13) | ((imm>>5)&1)<<12 | (rd << 7) | (imm&0x1F)<<2 | 0x2)
|
||||
}
|
||||
|
||||
// rvcCSS encodes a CSS-type (stack store) compressed instruction.
|
||||
func rvcCSS(funct3, rs2 uint32, imm uint32) uint16 {
|
||||
return uint16((funct3 << 13) | (imm << 7) | (rs2 << 2) | 0x2)
|
||||
}
|
||||
|
||||
// rvcCL encodes a CL-type (load) compressed instruction.
|
||||
// imm layout: [5:3] in bits [12:10], [2|6] in bits [6:5].
|
||||
func rvcCL(funct3, rd, rs1 uint32, imm uint32) uint16 {
|
||||
bits := uint16((funct3 << 13) | ((imm>>3)&0x7)<<10 | (rs1 << 7) | ((imm & 0x7) << 5) | (rd << 2) | 0x0)
|
||||
return bits
|
||||
}
|
||||
|
||||
// rvcCS encodes a CS-type (store) compressed instruction.
|
||||
func rvcCS(funct3, rs2, rs1 uint32, imm uint32) uint16 {
|
||||
return uint16((funct3 << 13) | ((imm>>3)&0x7)<<10 | (rs1 << 7) | ((imm & 0x7) << 5) | (rs2 << 2) | 0x0)
|
||||
}
|
||||
|
||||
// rvcCJ encodes a CJ-type (jump) compressed instruction.
|
||||
// offset is a 12-bit signed offset (bit 0 is always 0).
|
||||
func rvcCJ(funct3 uint32, offset int32) uint16 {
|
||||
uoff := uint32(offset) & 0xFFE
|
||||
bits := ((uoff >> 11) & 1) << 10
|
||||
bits |= ((uoff >> 4) & 1) << 9
|
||||
bits |= ((uoff >> 9) & 0x3) << 7
|
||||
bits |= ((uoff >> 10) & 1) << 6
|
||||
bits |= ((uoff >> 6) & 1) << 5
|
||||
bits |= ((uoff >> 7) & 1) << 4
|
||||
bits |= ((uoff >> 1) & 0x7) << 1
|
||||
bits |= ((uoff >> 5) & 1)
|
||||
return uint16((funct3 << 13) | (bits << 2) | 0x1)
|
||||
}
|
||||
|
||||
// rvcCB encodes a CB-type (branch) compressed instruction.
|
||||
// imm encodes the 8-bit branch offset (bits [8|4:3|7:6|2:1|5]).
|
||||
func rvcCB(funct3, rs1 uint32, offset int32) uint16 {
|
||||
uoff := uint32(offset) & 0x1FE // bits [8:1]
|
||||
bits := ((uoff >> 8) & 1) << 7 // imm[8]
|
||||
bits |= ((uoff >> 3) & 0x3) << 5 // imm[4:3]
|
||||
bits |= (uoff & 0x7) << 2 // imm[2|1|?]
|
||||
// Actually the encoding is more complex. Let me use a simpler approach.
|
||||
// CB format: funct3[15:13] | offset[8|4:3] | rs1'[9:7] | offset[7:6|2:1|5] | op[1:0]
|
||||
// Bit pattern for offset: [8|4:3|7:6|2:1|5]
|
||||
offBits := uint32(0)
|
||||
offBits |= ((uoff >> 8) & 1) << 10 // bit 10 = offset[8]
|
||||
offBits |= ((uoff >> 3) & 0x3) << 8 // bits 9:8 = offset[4:3]
|
||||
offBits |= ((uoff >> 6) & 0x3) << 6 // bits 7:6 = offset[7:6]
|
||||
offBits |= ((uoff >> 1) & 0x3) << 3 // bits 4:3 = offset[2:1]
|
||||
offBits |= ((uoff >> 5) & 1) << 2 // bit 2 = offset[5]
|
||||
return uint16((funct3 << 13) | offBits | (rs1 << 7) | 0x1)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,428 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package asm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||
)
|
||||
|
||||
// firstTextRISCV parses assembly source and returns the first TEXT function body.
|
||||
func firstTextRISCV(t *testing.T, src string) *ast.Text {
|
||||
t.Helper()
|
||||
f, errs := parser.Parse("f_riscv64.s", src)
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
for _, d := range f.Decls {
|
||||
if fn, ok := d.(*ast.Text); ok {
|
||||
return fn
|
||||
}
|
||||
}
|
||||
t.Fatal("no TEXT found")
|
||||
return nil
|
||||
}
|
||||
|
||||
// assembleRISCVHelper assembles one TEXT function and returns its code bytes.
|
||||
func assembleRISCVHelper(t *testing.T, fn *ast.Text) []byte {
|
||||
t.Helper()
|
||||
code, _, err := assembleRISCV(fn)
|
||||
if err != nil {
|
||||
t.Fatalf("assemble: %v", err)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
func TestRISCV_add(t *testing.T) {
|
||||
// func add(a, b int64) int64
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·add(SB), NOSPLIT, $0-24
|
||||
MOV a+0(FP), X10
|
||||
MOV b+8(FP), X11
|
||||
ADD X11, X10, X10
|
||||
MOV X10, ret+16(FP)
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// should be 12 bytes with RVC: C.LDSP + C.LDSP + ADD + C.SDSP + C.JR
|
||||
_ = code
|
||||
if len(code) == 0 {
|
||||
t.Error("empty output")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_arithmetic(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·arith(SB), NOSPLIT, $0
|
||||
ADD X10, X11, X12
|
||||
SUB X12, X13, X14
|
||||
MUL X14, X15, X16
|
||||
DIV X16, X17, X18
|
||||
REM X18, X19, X20
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// 5 R-type instructions + RET compressed = 5*4 + 2 = 22
|
||||
if len(code) != 22 {
|
||||
t.Errorf("expected 22 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_loadStore(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·mem(SB), NOSPLIT, $0
|
||||
LD (X10), X11
|
||||
SD X11, (X12)
|
||||
LW (X13), X14
|
||||
SW X14, (X15)
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// 4 loads/stores (4B each) + C.JR RET (2B) = 18
|
||||
if len(code) != 18 {
|
||||
t.Errorf("expected 18 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_immediate(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·imm(SB), NOSPLIT, $0
|
||||
ADDI X10, $42, X11
|
||||
ANDI X11, $0xFF, X12
|
||||
ORI X12, $1, X13
|
||||
XORI X13, $0, X14
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// 4 I-type + C.JR = 4*4 + 2 = 18
|
||||
if len(code) != 18 {
|
||||
t.Errorf("expected 18 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_branches(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·br(SB), NOSPLIT, $0
|
||||
ADDI X10, $1, X10
|
||||
loop:
|
||||
BEQ X10, X11, done
|
||||
ADDI X10, $1, X10
|
||||
JMP loop
|
||||
done:
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
_ = code
|
||||
if len(code) == 0 {
|
||||
t.Error("empty output")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_MOV_imm_small(t *testing.T) {
|
||||
// MOV $42, rd → ADDI (fits in 12 bits). Not RVC-compressed (treated as MOV, not ADDI).
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·small(SB), NOSPLIT, $0
|
||||
MOV $42, X10
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// ADDI (4B) + C.JR (2B) = 6
|
||||
if len(code) != 6 {
|
||||
t.Errorf("expected 6 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_MOV_imm_large(t *testing.T) {
|
||||
// MOV $0x12345, rd → LUI + ADDIW (8 bytes total)
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·large(SB), NOSPLIT, $0
|
||||
MOV $0x12345, X10
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// LUI (4B) + ADDIW (4B) + C.JR (2B) = 10
|
||||
if len(code) != 10 {
|
||||
t.Errorf("expected 10 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_MOV_reg(t *testing.T) {
|
||||
// MOV rs, rd → ADDI $0, rs, rd, compresses to C.MV
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·reg(SB), NOSPLIT, $0
|
||||
MOV X10, X11
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// C.MV (2B) + C.JR (2B) = 4
|
||||
if len(code) != 4 {
|
||||
t.Errorf("expected 4 bytes, got %d (% x)", len(code), code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_MOV_frame(t *testing.T) {
|
||||
// MOV name+off(FP), rd → load with frame mapping
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·frame(SB), NOSPLIT, $0-8
|
||||
MOV a+0(FP), X10
|
||||
MOV X10, ret+0(FP)
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// C.LDSP (2B) + C.SDSP (2B) + C.JR (2B) = 6
|
||||
if len(code) != 6 {
|
||||
t.Errorf("expected 6 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_RVC_loadStore(t *testing.T) {
|
||||
// Verify that loads/stores from SP (X2) are compressed.
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·rvcstore(SB), NOSPLIT, $0
|
||||
LD 0(X2), X10
|
||||
SD X10, 8(X2)
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// C.LDSP (2B) + C.SDSP (2B) + C.JR (2B) = 6
|
||||
if len(code) != 6 {
|
||||
t.Errorf("expected 6 bytes, got %d (% x)", len(code), code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_atomics(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·amo(SB), NOSPLIT, $0
|
||||
AMOADDD X10, (X11), X12
|
||||
LRD (X13), X14
|
||||
SCD X15, (X16), X17
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// 3 AMO instructions (4B each) + C.JR (2B) = 14
|
||||
if len(code) != 14 {
|
||||
t.Errorf("expected 14 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_fpArith(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·fpadd(SB), NOSPLIT, $0
|
||||
FADDD F10, F11, F12
|
||||
FSUBD F12, F13, F14
|
||||
FMULD F14, F15, F16
|
||||
FDIVD F16, F17, F18
|
||||
FSQRTD F18, F19
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// 5 FP instructions (4B each) + C.JR (2B) = 22
|
||||
if len(code) != 22 {
|
||||
t.Errorf("expected 22 bytes, got %d (%d)", len(code), len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_csr(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·csrtest(SB), NOSPLIT, $0
|
||||
CSRRS $0x300, X0, X10
|
||||
CSRRW $0x305, X10, X11
|
||||
CSRRSI $0x304, $5, X12
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// 3 CSR instructions (4B each) + C.JR (2B) = 14
|
||||
if len(code) != 14 {
|
||||
t.Errorf("expected 14 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_fma(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·fmatest(SB), NOSPLIT, $0
|
||||
FMADDD F10, F11, F12, F13
|
||||
FMSUBD F13, F14, F15, F16
|
||||
FNMSUBD F16, F17, F18, F19
|
||||
FNMADDD F19, F10, F11, F12
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// 4 FMA instructions (4B each) + C.JR (2B) = 18
|
||||
if len(code) != 18 {
|
||||
t.Errorf("expected 18 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_conversions(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·cvt(SB), NOSPLIT, $0
|
||||
FCVTDL X10, F10
|
||||
FCVTLD F10, X11
|
||||
FMVXD F10, X12
|
||||
FMVDX X12, F11
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// 4 conversion instructions (4B each) + C.JR (2B) = 18
|
||||
if len(code) != 18 {
|
||||
t.Errorf("expected 18 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_fpCmp(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·cmp(SB), NOSPLIT, $0
|
||||
FEQD F10, F11, X10
|
||||
FLTD F12, F13, X11
|
||||
FLED F14, F15, X12
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// 3 FP compare (4B each) + C.JR (2B) = 14
|
||||
if len(code) != 14 {
|
||||
t.Errorf("expected 14 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_forwardBranch(t *testing.T) {
|
||||
// Forward label reference — must not fail.
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·fwd(SB), NOSPLIT, $0
|
||||
ADDI X10, $1, X10
|
||||
BEQ X10, X11, done
|
||||
ADDI X10, $1, X10
|
||||
done:
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
_ = code
|
||||
if len(code) == 0 {
|
||||
t.Error("empty output")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_RVC_ADDI(t *testing.T) {
|
||||
// ADDI where rd=rs1 and small imm → C.ADDI
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·caddi(SB), NOSPLIT, $0
|
||||
ADDI X10, $5, X10
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// C.ADDI (2B) + C.JR (2B) = 4
|
||||
if len(code) != 4 {
|
||||
t.Errorf("expected 4 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_RVC_LI(t *testing.T) {
|
||||
// ADDI X0, $imm, rd → C.LI
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·cli(SB), NOSPLIT, $0
|
||||
ADDI X0, $7, X10
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// C.LI (2B) + C.JR (2B) = 4
|
||||
if len(code) != 4 {
|
||||
t.Errorf("expected 4 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_RVC_LUI(t *testing.T) {
|
||||
// LUI rd, small nonzero imm → C.LUI
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·clui(SB), NOSPLIT, $0
|
||||
LUI X10, $1
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// C.LUI (2B) + C.JR (2B) = 4
|
||||
if len(code) != 4 {
|
||||
t.Errorf("expected 4 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_AssembleFile(t *testing.T) {
|
||||
src := `#include "textflag.h"
|
||||
|
||||
TEXT ·add(SB), NOSPLIT, $0-24
|
||||
MOV a+0(FP), X10
|
||||
RET
|
||||
|
||||
TEXT ·sub(SB), NOSPLIT, $0
|
||||
SUB X10, X11, X12
|
||||
RET
|
||||
`
|
||||
f, errs := parser.Parse("t_riscv64.s", src)
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
img, err := AssembleFileRISCV(f)
|
||||
if err != nil {
|
||||
t.Fatalf("AssembleFileRISCV: %v", err)
|
||||
}
|
||||
if len(img.Funcs) != 2 {
|
||||
t.Fatalf("expected 2 functions, got %d", len(img.Funcs))
|
||||
}
|
||||
// func add: C.LDSP(2) + C.JR(2) = 4
|
||||
if img.Funcs[0].Size != 4 {
|
||||
t.Errorf("add: expected 4 bytes, got %d", img.Funcs[0].Size)
|
||||
}
|
||||
// func sub: SUB(4) + C.JR(2) = 6
|
||||
if img.Funcs[1].Size != 6 {
|
||||
t.Errorf("sub: expected 6 bytes, got %d", img.Funcs[1].Size)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_encodings(t *testing.T) {
|
||||
// Smoke test that all known RISC-V mnemonics encode successfully.
|
||||
tests := []struct {
|
||||
name, src string
|
||||
wantBytes int
|
||||
}{
|
||||
{"ADD", "ADD X10, X11, X12\nRET\n", 6},
|
||||
{"SUBW", "SUBW X10, X11, X12\nRET\n", 6},
|
||||
{"MUL", "MUL X10, X11, X12\nRET\n", 6},
|
||||
{"DIVW", "DIVW X10, X11, X12\nRET\n", 6},
|
||||
{"REMUW", "REMUW X10, X11, X12\nRET\n", 6},
|
||||
{"ADDIW", "ADDIW X10, $5, X11\nRET\n", 6},
|
||||
{"SLLI", "SLLI X10, $3, X11\nRET\n", 6}, // ADDI+SLLI? No, SLLI uses I-type
|
||||
{"SRLI", "SRLI X10, $2, X11\nRET\n", 6},
|
||||
{"SRAI", "SRAI X10, $1, X11\nRET\n", 6},
|
||||
{"LB", "LB (X10), X11\nRET\n", 6},
|
||||
{"LBU", "LBU (X10), X11\nRET\n", 6},
|
||||
{"LH", "LH (X10), X11\nRET\n", 6},
|
||||
{"LHU", "LHU (X10), X11\nRET\n", 6},
|
||||
{"LWU", "LWU (X10), X11\nRET\n", 6},
|
||||
{"SB", "SB X10, (X11)\nRET\n", 6},
|
||||
{"SH", "SH X10, (X11)\nRET\n", 6},
|
||||
{"SW", "SW X10, (X11)\nRET\n", 6},
|
||||
{"LUI", "LUI X10, $0x12345\nRET\n", 6},
|
||||
{"AUIPC", "AUIPC X10, $0\nRET\n", 6},
|
||||
{"FLW", "FLW (X10), F10\nRET\n", 6},
|
||||
{"FSW", "FSW F10, (X11)\nRET\n", 6},
|
||||
{"FADDS", "FADDS F10, F11, F12\nRET\n", 6},
|
||||
{"FMINS", "FMINS F10, F11, F12\nRET\n", 6},
|
||||
{"FMAXD", "FMAXD F10, F11, F12\nRET\n", 6},
|
||||
{"FCVTSD", "FCVTSD F10, F11\nRET\n", 6},
|
||||
{"FCVTDS", "FCVTDS F10, F11\nRET\n", 6},
|
||||
{"FMVXW", "FMVXW F10, X10\nRET\n", 6},
|
||||
{"FMADD_S", "FMADDS F10, F11, F12, F13\nRET\n", 6},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·`+tt.name+`(SB), NOSPLIT, $0
|
||||
`+tt.src)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
if len(code) != tt.wantBytes {
|
||||
t.Errorf("expected %d bytes, got %d", tt.wantBytes, len(code))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Vendored
+5
-5
@@ -5,8 +5,8 @@
|
||||
|
||||
// func add(a, b int64) int64
|
||||
TEXT ·add(SB), NOSPLIT, $0-24
|
||||
LD a+0(FP), A0
|
||||
LD b+8(FP), A1
|
||||
ADD A0, A1, A0
|
||||
SD A0, ret+16(FP)
|
||||
RET
|
||||
MOV a+0(FP), X10
|
||||
MOV b+8(FP), X11
|
||||
ADD X11, X10, X10
|
||||
MOV X10, ret+16(FP)
|
||||
RET
|
||||
|
||||
Vendored
+10
-10
@@ -5,16 +5,16 @@
|
||||
|
||||
// func atomicAdd(ptr *int64, val int64) int64
|
||||
TEXT ·atomicAdd(SB), NOSPLIT, $0-24
|
||||
LD a+0(FP), A0
|
||||
LD b+8(FP), A1
|
||||
AMOADDD A1, (A0), A2
|
||||
SD A2, ret+16(FP)
|
||||
RET
|
||||
MOV a+0(FP), X10
|
||||
MOV b+8(FP), X11
|
||||
AMOADDD X11, (X10), X12
|
||||
MOV X12, ret+16(FP)
|
||||
RET
|
||||
|
||||
// func fpAdd(a, b float64) float64
|
||||
TEXT ·fpAdd(SB), NOSPLIT, $0-24
|
||||
FLD a+0(FP), FA0
|
||||
FLD b+8(FP), FA1
|
||||
FADDD FA0, FA1, FA2
|
||||
FSD FA2, ret+16(FP)
|
||||
RET
|
||||
FLD a+0(FP), F10
|
||||
FLD b+8(FP), F11
|
||||
FADDD F10, F11, F12
|
||||
FSD F12, ret+16(FP)
|
||||
RET
|
||||
|
||||
Vendored
+13
-14
@@ -4,24 +4,23 @@
|
||||
#include "textflag.h"
|
||||
|
||||
// func readCSR(csr int64) int64
|
||||
// Reads a CSR into the return value.
|
||||
TEXT ·readCSR(SB), NOSPLIT, $0-16
|
||||
LD a+0(FP), A0
|
||||
CSRRS $0x300, X0, A1
|
||||
SD A1, ret+8(FP)
|
||||
RET
|
||||
MOV a+0(FP), X10
|
||||
CSRRS $0x300, X0, X11
|
||||
MOV X11, ret+8(FP)
|
||||
RET
|
||||
|
||||
// func setCSRBit(csr, bit int64) int64
|
||||
TEXT ·setCSRBit(SB), NOSPLIT, $0-24
|
||||
LD a+0(FP), A0
|
||||
LD b+8(FP), A1
|
||||
CSRRS $0x304, A1, A2
|
||||
SD A2, ret+16(FP)
|
||||
RET
|
||||
MOV a+0(FP), X10
|
||||
MOV b+8(FP), X11
|
||||
CSRRS $0x304, X11, X12
|
||||
MOV X12, ret+16(FP)
|
||||
RET
|
||||
|
||||
// func writeCSR(val int64) int64
|
||||
TEXT ·writeCSR(SB), NOSPLIT, $0-16
|
||||
LD a+0(FP), A0
|
||||
CSRRW $0x305, A0, A1
|
||||
SD A1, ret+8(FP)
|
||||
RET
|
||||
MOV a+0(FP), X10
|
||||
CSRRW $0x305, X10, X11
|
||||
MOV X11, ret+8(FP)
|
||||
RET
|
||||
|
||||
Vendored
+12
-14
@@ -4,21 +4,19 @@
|
||||
#include "textflag.h"
|
||||
|
||||
// func fma(a, b, c float64) float64
|
||||
// Computes a*b + c using fused multiply-add.
|
||||
TEXT ·fma(SB), NOSPLIT, $0-32
|
||||
FLD a+0(FP), FA0
|
||||
FLD b+8(FP), FA1
|
||||
FLD c+16(FP), FA2
|
||||
FMADDD FA0, FA1, FA2, FA3
|
||||
FSD FA3, ret+24(FP)
|
||||
RET
|
||||
FLD a+0(FP), F10
|
||||
FLD b+8(FP), F11
|
||||
FLD c+16(FP), F12
|
||||
FMADDD F10, F11, F12, F13
|
||||
FSD F13, ret+24(FP)
|
||||
RET
|
||||
|
||||
// func fms(a, b, c float64) float64
|
||||
// Computes a*b - c using fused multiply-subtract.
|
||||
TEXT ·fms(SB), NOSPLIT, $0-32
|
||||
FLD a+0(FP), FA0
|
||||
FLD b+8(FP), FA1
|
||||
FLD c+16(FP), FA2
|
||||
FMSUBD FA0, FA1, FA2, FA3
|
||||
FSD FA3, ret+24(FP)
|
||||
RET
|
||||
FLD a+0(FP), F10
|
||||
FLD b+8(FP), F11
|
||||
FLD c+16(FP), F12
|
||||
FMSUBD F10, F11, F12, F13
|
||||
FSD F13, ret+24(FP)
|
||||
RET
|
||||
|
||||
Vendored
+21
-22
@@ -4,34 +4,33 @@
|
||||
#include "textflag.h"
|
||||
|
||||
// func casLoop(ptr *int64, old, new int64) bool
|
||||
// Compare-and-swap using LR/SC.
|
||||
TEXT ·casLoop(SB), NOSPLIT, $0-32
|
||||
cas_retry:
|
||||
LD a+0(FP), A0
|
||||
LRD (A0), A1
|
||||
LD b+8(FP), A2
|
||||
BNE A1, A2, cas_fail
|
||||
LD c+16(FP), A3
|
||||
SCD A3, (A0), A4
|
||||
BNE A4, X0, cas_retry
|
||||
ADDI X0, $1, A5
|
||||
SD A5, ret+24(FP)
|
||||
RET
|
||||
MOV a+0(FP), X10
|
||||
LRD (X10), X11
|
||||
MOV b+8(FP), X12
|
||||
BNE X11, X12, cas_fail
|
||||
MOV c+16(FP), X13
|
||||
SCD X13, (X10), X14
|
||||
BNE X14, X0, cas_retry
|
||||
ADDI X0, $1, X15
|
||||
MOV X15, ret+24(FP)
|
||||
RET
|
||||
cas_fail:
|
||||
SD X0, ret+24(FP)
|
||||
RET
|
||||
MOV X0, ret+24(FP)
|
||||
RET
|
||||
|
||||
// func intToFloat(x int64) float64
|
||||
TEXT ·intToFloat(SB), NOSPLIT, $0-16
|
||||
LD a+0(FP), A0
|
||||
FCVTDL A0, FA0
|
||||
FSD FA0, ret+8(FP)
|
||||
RET
|
||||
MOV a+0(FP), X10
|
||||
FCVTDL X10, F10
|
||||
FSD F10, ret+8(FP)
|
||||
RET
|
||||
|
||||
// func compare(a, b float64) bool
|
||||
TEXT ·compare(SB), NOSPLIT, $0-24
|
||||
FLD a+0(FP), FA0
|
||||
FLD b+8(FP), FA1
|
||||
FLTD FA0, FA1, A0
|
||||
SD A0, ret+16(FP)
|
||||
RET
|
||||
FLD a+0(FP), F10
|
||||
FLD b+8(FP), F11
|
||||
FLTD F10, F11, X10
|
||||
MOV X10, ret+16(FP)
|
||||
RET
|
||||
|
||||
Reference in New Issue
Block a user