aboutsummaryrefslogtreecommitdiff
path: root/src/calling.tex
blob: 55c55e1e6191c318d480a2dd1c49bfe5656e9bd9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
\chapter{Calling Convention}
\label{sec:calling}

This chapter describes the C compiler standards for RV32 and RV64 programs
and two calling conventions: the convention for the base ISA
plus standard general extensions (RV32G/RV64G), and the soft-float
convention for implementations lacking floating-point units (e.g., RV32I/RV64I).

\begin{commentary}
Implementations with ISA extensions might require extended calling
conventions.
\end{commentary}

\section{C Datatypes and Alignment}

Table~\ref{datatypes} summarizes the datatypes natively supported by
RISC-V C programs.  In both RV32 and RV64 C compilers, the C type {\tt
  int} is 32 bits wide.  {\tt long}s and pointers, on the other hand,
are both as wide as a integer register, so in RV32, both are 32 bits
wide, while in RV64, both are 64 bits wide.  Equivalently, RV32
employs an ILP32 integer model, while RV64 is LP64.  In both RV32 and
RV64, the C type {\tt long long} is a 64-bit integer, {\tt float} is a
32-bit IEEE 754-2008 floating-point number, {\tt double} is a 64-bit
IEEE 754-2008 floating-point number, and {\tt long double} is a
128-bit IEEE floating-point number.

The C types {\tt char} and {\tt unsigned char} are 8-bit unsigned integers and
are zero-extended when stored in a RISC-V integer register. {\tt unsigned
short} is a 16-bit unsigned integer and is zero-extended when stored in
a RISC-V integer register.  {\tt signed char} is an 8-bit signed integer and
is sign-extended when stored in a RISC-V integer register, i.e. bits
(XLEN-1)..7 are all equal.  {\tt short} is a 16-bit signed integer and is
sign-extended when stored in a register.

In RV64, 32-bit types, such as {\tt int}, are stored in integer registers
as proper sign extensions of their 32-bit values; that is, bits 63..31 are all
equal.  This restriction holds even for unsigned 32-bit types.

The RV32 and RV64 C compiler and compliant software keep all of the
above datatypes naturally aligned when stored in memory.

\vspace{0.2in}
\begin{table*}[htbp]
\begin{center}
\begin{tabular}{|l|l|r|r|}

  \hline
  C type        & Description            & Bytes in RV32 & Bytes in RV64 \\ \hline 
  \tt char      & Character value/byte   & 1             & 1 \\
  \tt short     & Short integer          & 2             & 2 \\
  \tt int       & Integer                & 4             & 4 \\
  \tt long      & Long integer           & 4             & 8 \\
  \tt long long & Long long integer      & 8             & 8 \\
  \tt void*     & Pointer                & 4             & 8 \\
  \tt float     & Single-precision float & 4             & 4 \\
  \tt double    & Double-precision float & 8             & 8 \\
  \tt long double & Extended-precision float & 16        & 16 \\
  \hline

 \end{tabular}
\end{center}
\caption{C compiler datatypes for base RISC-V ISA.}
\label{datatypes}
\end{table*}


\section{RVG Calling Convention}

The RISC-V calling convention passes arguments in registers when
possible.  Up to eight integer registers, {\tt a0}--{\tt a7},
and up to eight floating-point registers, {\tt fa0}--{\tt fa7},
are used for this purpose.

If the arguments to a function are conceptualized as fields of a C
{\tt struct}, each with pointer alignment, the argument registers are
a shadow of the first eight pointer-words of that {\tt struct}.  If
argument $i<8$ is a floating-point type, it is passed in
floating-point register {\tt fa}$i$; otherwise, it is passed in
integer register {\tt a}$i$.  However, floating-point arguments
that are part of {\tt union}s or array fields of structures are passed
in integer registers.  Additionally, floating-point arguments to
variadic functions (except those that are explicitly named in the
parameter list) are passed in integer registers.

Arguments smaller than a pointer-word are passed in the least-significant bits
of argument registers.  Correspondingly, sub-pointer-word arguments passed on
the stack appear in the lower addresses of a pointer-word, since RISC-V
has a little-endian memory system.

When primitive arguments twice the size of a pointer-word are passed on the
stack, they are naturally aligned.  When they are passed in the integer
registers, they reside in an aligned even-odd register pair, with the even
register holding the least-significant bits.  In RV32, for example, the function
{\tt void foo(int, long long)} is passed its first argument in
{\tt a0} and its second in {\tt a2} and {\tt a3}.  Nothing is passed
in {\tt a1}.

Arguments more than twice the size of a pointer-word are passed by reference.

The portion of the conceptual {\tt struct} that is not passed in argument
registers is passed on the stack.  The stack pointer {\tt sp} points to the
first argument not passed in a register.

Values are returned from functions in integer registers {\tt a0} and {\tt
a1} and floating-point registers {\tt fa0} and {\tt fa1}.  Floating-point
values are returned in floating-point registers only if they are primitives or
members of a {\tt struct} consisting of only one or two floating-point values.
Other return values that fit into two pointer-words are returned in {\tt a0}
and {\tt a1}.  Larger return values are passed entirely in memory; the caller
allocates this memory region and passes a pointer to it as an implicit first
parameter to the callee.

In the standard RISC-V calling convention, the stack grows downward and the
stack pointer is always kept 16-byte aligned.

In addition to the argument and return value registers,
seven integer registers {\tt t0}--{\tt t6} and twelve floating-point registers
{\tt ft0}--{\tt ft11} are temporary registers that are volatile across
calls and must be saved by the caller if later used.
Twelve integer registers {\tt s0}--{\tt s11} and twelve floating-point
registers {\tt fs0}--{\tt fs11} are preserved across calls and must be saved
by the callee if used.  Table~\ref{regmap} indicates the role of each
integer and floating-point register in the calling convention.

\vspace{0.2in}
\begin{table*}[htbp]
\begin{center}
\begin{tabular}{|l|l|l|l|}

  \hline
  Register            & ABI Name            & Description        & Saver \\ \hline 
  \tt x0              & \tt zero            & Hard-wired zero    & --- \\
  \tt x1              & \tt ra              & Return address     & Caller \\
  \tt x2              & \tt sp              & Stack pointer      & Callee \\
  \tt x3              & \tt gp              & Global pointer     & --- \\
  \tt x4              & \tt tp              & Thread pointer     & --- \\
  {\tt x5}--{\tt 7}   & {\tt t0}--{\tt 2}   & Temporaries        & Caller \\
  \tt x8              & {\tt s0}/\tt fp     & Saved register/frame pointer & Callee \\
  \tt x9              & {\tt s1}            & Saved register     & Callee \\
  {\tt x10}--{\tt 11} & {\tt a0}--{\tt 1}   & Function arguments/return values & Caller \\
  {\tt x12}--{\tt 17} & {\tt a2}--{\tt 7}   & Function arguments & Caller \\
  {\tt x18}--{\tt 27} & {\tt s2}--{\tt 11}  & Saved registers    & Callee \\
  {\tt x28}--{\tt 31} & {\tt t3}--{\tt 6}   & Temporaries        & Caller \\
  \hline
  {\tt f0}--{\tt 7}   & {\tt ft0}--{\tt 7}  & FP temporaries     & Caller \\
  {\tt f8}--{\tt 9}   & {\tt fs0}--{\tt 1}  & FP saved registers & Callee \\
  {\tt f10}--{\tt 11} & {\tt fa0}--{\tt 1}  & FP arguments/return values & Caller \\
  {\tt f12}--{\tt 17} & {\tt fa2}--{\tt 7}  & FP arguments       & Caller \\
  {\tt f18}--{\tt 27} & {\tt fs2}--{\tt 11} & FP saved registers & Callee \\
  {\tt f28}--{\tt 31} & {\tt ft8}--{\tt 11} & FP temporaries     & Caller \\
  \hline

 \end{tabular}
\end{center}
\caption{RISC-V calling convention register usage.}
\label{regmap}
\end{table*}

\section{Soft-Float Calling Convention}

The soft-float calling convention is intended for use on RV32 and RV64
implementations that lack floating-point hardware.  It avoids all use
of instructions in the F, D, and Q standard extensions, and hence the
{\tt f} registers.

Integral arguments are passed and returned in the same manner as the
RVG convention, and the stack discipline is the same except that the
stack is only kept aligned to XLEN/8-byte boundaries (e.g., four-byte
alignment for RV32I).

\begin{commentary}
  As floating-point data on the stack will be accessed using integer
  load and store instructions, there is no incentive to maintain stack
  alignment at a coarse granularity in the soft-float calling
  convention.  The reduced stack alignment saves space in the
  memory-constrained systems that might commonly use soft
  floating-point.
\end{commentary}

Floating-point arguments are passed and returned in integer registers,
using the rules for integer arguments of the same size.  In RV32, for
example, the function {\tt double foo(int, double, long double)} is
passed its first argument in {\tt a0}, its second argument in {\tt a2}
and {\tt a3}, and its third argument by reference via {\tt a4}; its
result is returned in {\tt a0} and {\tt a1}.  In RV64, the arguments
are passed in {\tt a0}, {\tt a1}, and the {\tt a2}-{\tt a3} pair, and
the result is returned in {\tt a0}.

The dynamic rounding mode and accrued exception flags are accessed through
the routines provided by the C99 header {\tt fenv.h}.

\section{RV32E Calling Convention}

RV32E uses a subset of the Soft-Float calling convention.  As only 16
integer registers {\tt x0}--{\tt x15} are present, there are only six
argument registers ({\tt x10}--{\tt x15}), two saved registers ({\tt
  x8}--{\tt x9}), and three temporary registers ({\tt x5}--{\tt x7}).
The stack is kept aligned on a four-byte boundary.