diff --git a/asm/riscv_assemble.go b/asm/riscv_assemble.go index 0760e01..53687c6 100644 --- a/asm/riscv_assemble.go +++ b/asm/riscv_assemble.go @@ -1273,6 +1273,34 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv } word = riscvIType(enc, 0, 0, imm) 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 @@ -1990,11 +2018,19 @@ func splitRISCV32Imm(imm int32) (low, high int32) { // riscvNormalisePseudo rewrites the toolchain's UNDEF spelling onto EBREAK: // 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 -// 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 { if strings.EqualFold(mnem, "UNDEF") { return "EBREAK" } + switch mnem { + case "SCALL": + return "ECALL" + case "SBREAK": + return "EBREAK" + } return mnem } diff --git a/asm/riscv_encode_test.go b/asm/riscv_encode_test.go index 71b1bcd..28fca0b 100644 --- a/asm/riscv_encode_test.go +++ b/asm/riscv_encode_test.go @@ -7,6 +7,8 @@ import ( "bytes" "encoding/binary" "encoding/hex" + "os" + "path/filepath" "strings" "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) { // 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