From aa4698a4ed4ce364a8c2469fbe4472f510f04190 Mon Sep 17 00:00:00 2001 From: Stefan O'Rear Date: Sat, 24 Feb 2024 15:18:54 -0500 Subject: [PATCH 1/2] Improve bit-field description Move bit-field description out of the calling convention since bit-fields representation is also relevant in memory. Prefer bit-field over bitfield for consistency with C and C++. Tidy wording for overlength bit-fields (fixes #410) and align with the Itanium C++ ABI. Add descriptions for zero-length and unnamed bit-fields, mirroring x86-64 language and matching gcc and clang behavior. Signed-off-by: Stefan O'Rear --- riscv-cc.adoc | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/riscv-cc.adoc b/riscv-cc.adoc index ba38f4b8..05ac053d 100644 --- a/riscv-cc.adoc +++ b/riscv-cc.adoc @@ -216,21 +216,6 @@ Empty structs or union arguments or return values are ignored by C compilers which support them as a non-standard extension. This is not the case for {Cpp}, which requires them to be sized types. -Bitfields are packed in little-endian fashion. A bitfield that would span the -alignment boundary of its integer type is padded to begin at the next -alignment boundary. For example, `struct { int x : 10; int y : 12; }` is -a 32-bit type with `x` in bits 9-0, `y` in bits 21-10, and bits 31-22 -undefined. By contrast, `struct { short x : 10; short y : 12; }` is a 32-bit -type with `x` in bits 9-0, `y` in bits 27-16, and bits 31-28 and bits 15-10 -undefined. - -Bitfields may larger than its integer type, bits excess than its integer -type will treat as padding bits, then padding to begin at the next alignment -boundary. For example `struct { char x : 9; char y; }` is a 24 byte type with -`x` in bits 7-0, `y` in bit 23-16, and bits 15-8 undefined, -`struct { char x : 9; char y : 2 }` is a 16-bit type with `x` in bits 7-0, `y` -in bit 10-9, and bit 8, bits 15-11 is undefined. - Arguments passed by reference may be modified by the callee. Floating-point reals are passed the same way as aggregates of the same size; @@ -639,6 +624,35 @@ The type `size_t` is defined as `unsigned int` for RV32 and `unsigned long` for The type `ptrdiff_t` is defined as `int` for RV32 and `long` for RV64. +=== Bit-fields + +Bit-fields are packed in little-endian fashion. A bit-field that would span the +alignment boundary of its integer type is padded to begin at the next +alignment boundary. For example, `struct { int x : 10; int y : 12; }` is +a 32-bit type with `x` in bits 9-0, `y` in bits 21-10, and bits 31-22 +undefined. By contrast, `struct { short x : 10; short y : 12; }` is a 32-bit +type with `x` in bits 9-0, `y` in bits 27-16, and bits 31-28 and bits 15-10 +undefined. + +Bit-fields which are larger than their integer types are only present in {Cpp} +and are defined by the _Itanium {Cpp} ABI_ <>. The bit-field +and containing struct are aligned on a boundary corresponding to the largest +integral type smaller than the bit-field, up to 64-bit alignment on RV32 or +128-bit alignment on RV64. Any bits in excess of the size of the declared type +are treated as padding. For example `struct { char x : 9; char y; }` is a +24-bit type with `x` in bits 7-0, `y` in bit 23-16, and bits 15-8 undefined; +`struct { char x : 9; char y : 2 }` is a 16-bit type with `x` in bits 7-0, `y` +in bit 10-9, and bits 8 and 15-11 undefined. + +Unnamed nonzero length bit-fields allocate space in the same fashion as named +bitfields but do not affect the alignment of the containing struct. + +Zero length bit-fields are aligned relative to the start of the containing +struct according to their declared type and, since they must be unnamed, do not +affect the struct alignment. C requies bit-fields on opposite sides of a +zero-length bitfield to be treated as separate memory locations for the +purposes of data races. + === va_list, va_start, and va_arg The `va_list` type is `void*`. A callee with variadic arguments is responsible From 9e93d968003006a3b43199189e6bc649143c095b Mon Sep 17 00:00:00 2001 From: Stefan O'Rear Date: Sat, 24 Feb 2024 17:41:39 -0500 Subject: [PATCH 2/2] Expand va_list description Explain how a varargs save area is constructed, and define the representation of a va_list and the operation of va_arg independently of the definition of va_start. Fixes #412. Signed-off-by: Stefan O'Rear --- riscv-cc.adoc | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/riscv-cc.adoc b/riscv-cc.adoc index 05ac053d..7c9bab50 100644 --- a/riscv-cc.adoc +++ b/riscv-cc.adoc @@ -170,6 +170,7 @@ contents of any fixed registers like `gp` and `tp` never change, NOTE: Calling convention for big-endian is *NOT* included in this specification yet, we intend to define that in future version of this specification. +[#integer-cc] === Integer Calling Convention The base integer calling convention provides eight argument registers, @@ -655,13 +656,35 @@ purposes of data races. === va_list, va_start, and va_arg -The `va_list` type is `void*`. A callee with variadic arguments is responsible -for copying the contents of registers used to pass variadic arguments to the -vararg save area, which must be contiguous with arguments passed on the stack. -The `va_start` macro initializes its `va_list` argument to point to the start -of the vararg save area. The `va_arg` macro will increment its `va_list` -argument according to the size of the given type, taking into account the -rules about 2×XLEN aligned arguments being passed in "aligned" register pairs. +The `va_list` type has the same representation as `void*` and points to a +sequence of zero or more arguments with preceding padding for alignment, +formatted and aligned as variadic arguments passed on the stack according to +the integer calling convention (<>). All standard calling conventions use the +same representation for variadic arguments to allow `va_list` types to be +shared between them. + +The `va_start` macro in a function initializes its `va_list` argument to point +to the first address at which a variadic argument could be passed to the +function. If all integer argument registers are used for named formal +arguments, the first variadic argument will have been passed on the stack by +the caller, and the `va_list` can point to the address immediately after the +last named argument passed on the stack, or the `sp` value on entry if no named +arguments were passed on the stack. If some integer argument registers were not +used for named formal arguments, then the first variadic argument may have been +passed in a register. The function is then expected to construct a _varargs +save area_ immediately below the entry `sp` and fill it with the entry values +of all integer argument registers not used for named arguments, in sequence. +The `va_list` value can then be initialized to the start of the varargs save +area, and it will iterate through any variadic arguments passed via registers +before continuing to variadic arguments passed on the stack, if any. + +The `va_arg` macro will align its `va_list` argument, fetch a value, and +increment the `va_list` according to the alignment and size of a variadic +argument of the given type, which may not be the same as the alignment and size +of the given type in memory. If the type is passed by reference, the size and +alignment used will be those of a pointer, and the fetched pointer will be used +as the address of the actual argument. The `va_copy` macro is a single pointer +copy and the `va_end` macro performs no operation. === Vector type sizes and alignments