Deviation between RISC-V ISA Spec User-Level ABI and GNU binutils

264 views
Skip to first unread message

Michael Clark

unread,
Jan 7, 2016, 4:29:07 AM1/7/16
to RISC-V ISA Specification Discussion
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

Cesar Eduardo Barros

unread,
Jan 7, 2016, 12:51:21 PM1/7/16
to Michael Clark, RISC-V ISA Specification Discussion
Em 07-01-2016 07:29, Michael Clark escreveu:
> 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...

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

Michael Clark

unread,
Jan 7, 2016, 6:30:37 PM1/7/16
to Cesar Eduardo Barros, RISC-V ISA Specification Discussion

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.

Reply all
Reply to author
Forward
0 new messages