Hi All, I've been giving the RISC-V spec a close read and have been performing some testing with some instruction decoding code (written based on the spec) and was puzzled by a discrepancy between the registers I was decoding and the registers I was seeing in GNU binutils objdump disassembly (double-checking my implementation). It turns out I may have found a deviation between the specification and the current implementation. Excuse my ignorance if this is a known issue... See page 85 extract here: The RISC-V Instruction Set Manual, Volume I: User-Level ISA, Version 2.0 Register ABI Name Description Saver x0 zero Hard-wired zero - x1 ra Return address Caller x2 s0/fp Saved register/frame pointer Callee x3-13 s1-11 Saved registers Callee x14 sp Stack pointer Callee x15 tp Thread pointer Callee x16-17 v0-1 Return values Caller x18-25 a0-7 Function arguments Caller x26-30 t0-4 Temporaries Caller x31 gp Global pointer - f0-15 fs0-15 FP saved registers Callee f16-17 fv0-1 FP return values Caller f18-25 fa0-7 FP arguments Caller f26-31 ft0-5 FP temporaries Caller Table 18.2: RISC-V calling convention register usage. And here is the code from the riscv gnu binutils: https://github.com/riscv/riscv-gnu-toolchain/blob/master/binutils/opcodes/riscv-opc.c /* Register names used by gas and objdump.�� */ const char * const riscv_gpr_names_numeric[32] = { �� "x0",���� "x1",���� "x2",���� "x3",���� "x4",���� "x5",���� "x6",���� "x7", �� "x8",���� "x9",���� "x10",�� "x11",�� "x12",�� "x13",�� "x14",�� "x15", �� "x16",�� "x17",�� "x18",�� "x19",�� "x20",�� "x21",�� "x22",�� "x23", �� "x24",�� "x25",�� "x26",�� "x27",�� "x28",�� "x29",�� "x30",�� "x31" }; const char * const riscv_gpr_names_abi[32] = { �� "zero", "ra", "sp",�� "gp",�� "tp", "t0",�� "t1",�� "t2", �� "s0",���� "s1", "a0",�� "a1",�� "a2", "a3",�� "a4",�� "a5", �� "a6",���� "a7", "s2",�� "s3",�� "s4", "s5",�� "s6",�� "s7", �� "s8",���� "s9", "s10", "s11", "t3", "t4",�� "t5",�� "t6" }; const char * const riscv_fpr_names_numeric[32] = { �� "f0",���� "f1",���� "f2",���� "f3",���� "f4",���� "f5",���� "f6",���� "f7", �� "f8",���� "f9",���� "f10",�� "f11",�� "f12",�� "f13",�� "f14",�� "f15", �� "f16",�� "f17",�� "f18",�� "f19",�� "f20",�� "f21",�� "f22",�� "f23", �� "f24",�� "f25",�� "f26",�� "f27",�� "f28",�� "f29",�� "f30",�� "f31" }; const char * const riscv_fpr_names_abi[32] = { �� "ft0", "ft1", "ft2",�� "ft3",�� "ft4", "ft5", "ft6",�� "ft7", �� "fs0", "fs1", "fa0",�� "fa1",�� "fa2", "fa3", "fa4",�� "fa5", �� "fa6", "fa7", "fs2",�� "fs3",�� "fs4", "fs5", "fs6",�� "fs7", �� "fs8", "fs9", "fs10", "fs11", "ft8", "ft9", "ft10", "ft11" }; based on the fact the code is probably authoritative (assuming changes are based on register allocations in practice) then the revised table below would represent the user-level ABI implemented in the GNU toolchain (I haven't checked clang/llvm yet). Note: It's almost completely different from the current User-Level ABI in the spec document except for zero and ra. It had me confused for a while ;-) Register ABI Name Description Saver x0 zero Hard-wired zero - x1 ra Return address Caller x2 sp Stack pointer Callee x3 gp Global pointer - x4 tp Thread pointer Callee x5-7 t0-2 Temporaries Caller x8 s0/fp Saved register/frame pointer Callee x9 s1 Saved registers Callee x10-17 a0-7 Function arguments Caller x18-27 s2-11 Saved registers Callee x28-31 t3-t6 Temporaries Caller f0-7 ft0-7 FP temporaries Caller f8-9 fs0-1 FP saved registers Callee f10-17 fa0-7 FP arguments Caller f18-27 fs2-11 FP saved registers Callee f28-31 ft8-11 FP temporaries Caller Table 18.2: RISC-V calling convention register usage /edited/. Please review as I've just gone through and tabulated it manually based on the binutils code. If someone points me at a git repo for the spec then I'd be happy to spend some more time on this, proof-reading and making a pull request. BTW I'm encouraged by the readability of the current ISA spec. I'm especially interested in future Hypervisor functionality and the tagged TLB implementation in the Privileged spec, especially Supervisor Address Space ID Register (sasid). I noticed this is not yet implemented in the ISA simulator. I am curious whether the tagged TLB is supported in any of the current RISC-V implementations? I'm also curious about the semantics of sasid wrt. sptbr updates and TLB flushes given there is no Hypervisor level spec and there is no strong association between an (excuse my Intel terminology) VPID and an EPT (SLAT). I can see a lightweight implementation of tagged TLB (without depending on SLAT) would be beneficial (Intel appears to rely on EPT which makes it pretty heavy). Is the RISC-V semantic such that the TLB ASID is populated based on the sasid value when a TLB misses occurs, and requires that you update sasid when you load a new sptbr? This would mean an implementation needs to be aware of how the ASID is populated in the TLB. i.e. if this was the semantic then changing sasid with the same sptbr loaded would result in TLB entries with different tags for the same page table (for TLB misses occurring after the sasid update). I'm not sure I fully understand the intended semantics of sasid / tagged TLB. The spec may need some wording to clarify this... In any case I hope I can contribute. I think the RISC-V work to-date is awesome! Regards, Michael
It's a known change, see
https://blog.riscv.org/2015/01/announcing-the-risc-v-gcc-4-9-port-and-new-abi/
which has a link to the updated ABI chapter.
It would be good if that PDF could also be found at
http://riscv.org/download.html for people who don't follow the blog (at
least until the updated manual is released).
Besides the rationale in that blog post, there was another reason for
the change: so the same ABI can also be used for RV32E (which is
basically RV32I with only the first half of the register file; a draft
spec for it was posted to this list in June). I believe it also made the
compressed instructions extension slightly simpler.
--
Cesar Eduardo Barros
ces...@cesarb.eti.br
OK I have found the relevant documentation now.
I am wondering whether it is a good idea to separate the User Level ABI
from the ISA Spec.
I can see the ABI evolving and there being multiple variants. RISC-V
represents an opportunity to add hardware support for state of the art
SFI (Software-based Fault Isolation) techniques.
Many of the problems with the security of C are based on repeated ABI
mistakes e.g. place return addresses on stack, grow stack downwards and
allocate variable size buffers on the stack: patch mistake using weak
mechanisms such as stack canaries. The Stack can grow upwards in a
Harvard segmented architecture. Also Control Flow information can be
separated in a stackless architecture such as RISC-V
There are lots of potential ABI level improvements we can make:
* Tagged memory or bitmaps marking valid callgates and vtable pointers:
techniques such as Microsoft's CFG - Control Flow Guard and EPFL's Code
Pointer Integrity.
* LLVM's Safe stack - separating variable length function closure space
from control flow and compile time verified fixed size scalars. RISC-V
has an excellent foundation due to jump and link putting the return
address in a register i.e. doesn't assume a single stack model like x86
and allows for novel ABI variants that address fundamental security
problems with C.
* Other interesting C ABI variants. Packing 64-bit pointer and 1's
complement bounds (size carried from malloc/alloca) into 128 bit
registers (2 element packed 64-bit) and using vector instructions for
pointer arithmetic to preserve bounds over arbitrary transformations. A
RISC-V equivalent to Intel's MPX. The nice thing about 1's complement
for bounds is that you can use an 128-bit adder without carry at bit 64
and reuse a single register file (eliminate requirement for special
bounds registers - that essentially add back a form of segmentation i.e.
offset and limit). The double register allocation for 128-bit scalars in
the base ABI will be useful here.
* Making the ISA and ABI differentiate between scalars and pointers
(pointers into to code segments). Copy Memory tags to Register tags and
tainting/typing memory. Implement fast hardware mechanisms to verify
indirect calls to verified code segments (JVM's invokevirtual).
* Segmented code models / Harvard Architecture. There were many
unintended consequences of removing segmentation from long mode on x64,
especially for paravirtualization, VMMs. Pre VT-x Xen on x86_64 had to
run kernel and userspace in Ring 3 which meant slower kernel-user
context switches
* PC relative addressing for data breaks the Harvard Architecture. i.e.
further mixes code and data. We should have DS relative addressing for
data to avoid leaking PC to malware. Code, Data and thread specific
control flow space should be able to be in different address spaces and
we should be able to fault if a pointer is used to access an address
space it is not tagged for.
I am wondering if this is a good argument for separating the User-Level
ABI (and variants) into separate documents, rather than linking the
User-Level ABI to the base ISA Spec. i.e. allow for decoupling ABI
variants from the ISA.
Regards,
Michael.