feat(asm): encode the riscv64 privileged instructions
Assisted-by: GLM 5.3 Flash
This commit is contained in:
1 parent
778c297214
commit
82e8919208
2 files changed
+83
-1
No files matched your search
+37
-1
@@ -1273,6 +1273,34 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
|
|||||||
}
|
}
|
||||||
word = riscvIType(enc, 0, 0, imm)
|
word = riscvIType(enc, 0, 0, imm)
|
||||||
return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}, nil
|
return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}, nil
|
||||||
|
|
||||||
|
case "SRET", "MRET", "WFI", "DRET":
|
||||||
|
// The privileged traps and the wait instruction: fixed funct7 and
|
||||||
|
// rs2 fields packed into the I-type immediate. The toolchain's
|
||||||
|
// object table carries the first three (its assembler accepts no
|
||||||
|
// mnemonic for them); DRET is the debug specification's own, so the
|
||||||
|
// golden vector pins it: SRET 0x10200073, MRET 0x30200073,
|
||||||
|
// WFI 0x10500073, DRET 0x7b200073.
|
||||||
|
imm := map[string]int32{"SRET": 0x102, "MRET": 0x302, "WFI": 0x105, "DRET": 0x7B2}[mnem]
|
||||||
|
word = riscvIType(riscvEnc{0x73, 0x0, 0x00}, 0, 0, imm)
|
||||||
|
return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}, nil
|
||||||
|
|
||||||
|
case "SFENCEVMA":
|
||||||
|
// INSTR rs1, rs2: the memory-management fence, funct7 0x09 and an
|
||||||
|
// all-zero rd. The toolchain's object table carries the encoding
|
||||||
|
// (ASFENCEVMA, funct7 9) but its assembler accepts no mnemonic for
|
||||||
|
// it, so the privileged specification's form pins it:
|
||||||
|
// SFENCEVMA X10, X11 is 0x12b50073.
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return nil, fmt.Errorf("SFENCEVMA expects 2 operands, got %d", len(ops))
|
||||||
|
}
|
||||||
|
rs1 := regFromOperand(ops[0])
|
||||||
|
rs2 := regFromOperand(ops[1])
|
||||||
|
if rs1 < 0 || rs2 < 0 {
|
||||||
|
return nil, fmt.Errorf("SFENCEVMA: invalid register operand")
|
||||||
|
}
|
||||||
|
word = riscvRType(riscvEnc{0x73, 0x0, 0x09}, 0, rs1, rs2)
|
||||||
|
return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FP conversion / move instructions use a separate table (rs2 encodes
|
// FP conversion / move instructions use a separate table (rs2 encodes
|
||||||
@@ -1990,11 +2018,19 @@ func splitRISCV32Imm(imm int32) (low, high int32) {
|
|||||||
// riscvNormalisePseudo rewrites the toolchain's UNDEF spelling onto EBREAK:
|
// riscvNormalisePseudo rewrites the toolchain's UNDEF spelling onto EBREAK:
|
||||||
// the assembler accepts UNDEF where the hardware wants the trap instruction
|
// the assembler accepts UNDEF where the hardware wants the trap instruction
|
||||||
// and emits ebreak (compressed to C.EBREAK under RVC), so every pass sees the
|
// and emits ebreak (compressed to C.EBREAK under RVC), so every pass sees the
|
||||||
// canonical name.
|
// canonical name. The privileged aliases fold the same way: SCALL and
|
||||||
|
// SBREAK are the supervisor spellings of ECALL and EBREAK and encode
|
||||||
|
// identically.
|
||||||
func riscvNormalisePseudo(mnem string) string {
|
func riscvNormalisePseudo(mnem string) string {
|
||||||
if strings.EqualFold(mnem, "UNDEF") {
|
if strings.EqualFold(mnem, "UNDEF") {
|
||||||
return "EBREAK"
|
return "EBREAK"
|
||||||
}
|
}
|
||||||
|
switch mnem {
|
||||||
|
case "SCALL":
|
||||||
|
return "ECALL"
|
||||||
|
case "SBREAK":
|
||||||
|
return "EBREAK"
|
||||||
|
}
|
||||||
return mnem
|
return mnem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -785,6 +787,50 @@ TEXT ·sys(SB), NOSPLIT, $0
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestRISCV_privilegedWords pins the privileged ISA slice. The toolchain's
|
||||||
|
// object table carries the encodings (its assembler accepts no mnemonic for
|
||||||
|
// them), so the golden vectors come from the privileged and debug
|
||||||
|
// specifications: SFENCEVMA X10, X11 is 0x12b50073, SRET 0x10200073,
|
||||||
|
// MRET 0x30200073, WFI 0x10500073 and DRET 0x7b200073.
|
||||||
|
func TestRISCV_privilegedWords(t *testing.T) {
|
||||||
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||||
|
TEXT ·priv(SB), NOSPLIT, $0
|
||||||
|
SFENCEVMA X10, X11
|
||||||
|
SRET
|
||||||
|
MRET
|
||||||
|
WFI
|
||||||
|
DRET
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
code := assembleRISCVHelper(t, fn)
|
||||||
|
riscvWants(t, code,
|
||||||
|
0x12B50073, // sfence.vma x10, x11
|
||||||
|
0x10200073, // sret
|
||||||
|
0x30200073, // mret
|
||||||
|
0x10500073, // wfi
|
||||||
|
0x7B200073, // dret
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRISCV_privilegedAliases checks the supervisor spellings SCALL and
|
||||||
|
// SBREAK against the toolchain: both alias ECALL and EBREAK and must come
|
||||||
|
// out byte-identical.
|
||||||
|
func TestRISCV_privilegedAliases(t *testing.T) {
|
||||||
|
src := `#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·alias(SB), NOSPLIT, $0
|
||||||
|
SCALL
|
||||||
|
SBREAK
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
dir := t.TempDir()
|
||||||
|
path := filepath.Join(dir, "alias_riscv64.s")
|
||||||
|
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assertRISCVDifferential(t, path, src, "alias")
|
||||||
|
}
|
||||||
|
|
||||||
func TestRISCV_MOV_sym_FP(t *testing.T) {
|
func TestRISCV_MOV_sym_FP(t *testing.T) {
|
||||||
// MOV $sym(FP), rd lowers to the frame-adjusted ADDI against SP: the
|
// MOV $sym(FP), rd lowers to the frame-adjusted ADDI against SP: the
|
||||||
// toolchain's argframe spelling. A zero frame leaves the offset at the
|
// toolchain's argframe spelling. A zero frame leaves the offset at the
|
||||||
|
|||||||
Reference in new issue
Block a user