Expand description
The C type system, interned, and layout computation.
Design: spec/07-types-and-semantics.md. Layer rank 2, see spec/18-package-layout.md.
There is one Types per translation unit and it owns every type in it. A TypeId is
four bytes and two of them are equal exactly when they are the same type, which turns the
question the compiler asks more often than any other into an integer comparison.
Two ideas shape the rest of it.
Sugar is kept and never decided on. typedef int32_t; gives a node that remembers the
name and points at canonical int. Every semantic rule reads Types::canonical and sees
int; every diagnostic reads the type as it was written and says int32_t. Compilers that
throw the name away produce messages nobody can act on, and compilers that decide on the
name produce wrong answers, and both are common. Sugar is not only at the outermost node,
so int32_t * and int32_t[4] are sugar too and canonicalising rebuilds them.
_Atomic is a type, not a qualifier. const and volatile and restrict are a
bitmask in the interning key, because nothing about them changes what an object is. C lets
_Atomic be written in the same position, but _Atomic(T) can have a different alignment
from T, so it is a type constructor here and the parser is what maps the spelling onto
it. Document 01 recorded a compiler that treated it as a qualifier and lost track of it,
which is exactly the shortcut that makes atomics silently wrong.
Layout comes out of TargetInfo and never out of the host.
long is four bytes on Windows and eight on Linux, and long double is eight bytes on
Apple and sixteen on SysV x86-64, so a cross compiler that asks its own platform is wrong
twice before it has read a line of C.
use rucc_target::{TargetInfo, Triple};
use rucc_types::{IntKind, Types, layout};
let mut types = Types::new();
let linux = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
let windows = TargetInfo::new("x86_64-pc-windows-msvc".parse::<Triple>().unwrap());
let long = types.int(IntKind::Long);
assert_eq!(layout(&types, long, &linux).unwrap().size, 8);
assert_eq!(layout(&types, long, &windows).unwrap().size, 4);Records are laid out by layout_record, which takes the members and gives back their
offsets, and the result is handed to Types::complete_record so that the record then has
a size like any other type. Bit-fields, packed, #pragma pack, aligned, zero width
bit-fields and flexible array members are all in there, and every one of their rules was
measured against gcc and clang rather than read off a document.
promote and usual_arithmetic are 6.3.1.1 and 6.3.1.8, the rules that decide what
type an arithmetic expression has. Their answers were read out of gcc and clang with
_Generic naming the type of every interesting pair, which is also how the C23 changes were
pinned down: _BitInt does not promote, and an enumeration promotes through whatever it is
represented in.
__int128 is one of the integer kinds rather than a _BitInt(128) in disguise. The two are
different types: __int128 is sixteen bytes aligned to sixteen everywhere, _BitInt(128) is
aligned to its granule, and __int128 outranks long long where a _BitInt is ranked by
width alone. It is available on every target here, because all three architectures are
64-bit and GCC has it on every 64-bit target it supports.
compatible and composite are 6.2.7, the relation that decides whether two
declarations of one name are talking about the same thing and the type that is left when they
are. Identity is not that relation: int f(int a[3]) and int f(int *a) are different types
and the same function. The composite is what a caller merging two declarations should keep,
because it is the only one of the three types in play that knows both the array size and the
parameter list.
§Status
The type universe, the interner, the canonical and sugar split, the qualifier rules, layout with records included, the arithmetic conversions, and compatibility with the composite type are implemented.
Not here yet, and named so that the gaps are not mistaken for decisions: the decimal floating types, and printing a type back as C declaration syntax.
Every crate in the workspace is published, and publishing implies a promise. This one is
tier 3: its Rust API is explicitly unstable and will change without a major version bump.
Depend on the rucc binary’s behaviour, not on this.
Structs§
- EnumId
- The identity of an
enumdeclaration inTypes. - Enum
Info - What is known about one
enumdeclaration. - Field
- One member of a record, placed.
- Field
Decl - One member of a record as the program wrote it.
- Function
Id - The identity of a function type in
Types. - Function
Type - A function type.
- Layout
- The size and alignment of a complete object type.
- Qualifiers
- The qualifiers a type can carry.
- Record
Id - The identity of a
structoruniondeclaration inTypes. - Record
Info - What is known about one
structoruniondeclaration. - Record
Layout - A record, laid out.
- Record
Options - What the program asked for on the record itself.
- Type
- A type with its qualifiers, which together are one entry in the type table.
- TypeId
- The identity of a type.
- Types
- Every type in one translation unit.
- VlaId
- The identity of one variable length array’s size expression.
Enums§
- Array
Len - How many elements an array has, which is four different answers in C.
- Float
Kind - The real floating types.
- IntKind
- The standard integer types, the character types kept apart from them, and
__int128. - Layout
Error - Why a type has no layout.
- Record
Error - Why a record has no layout.
- Record
Kind - Whether a record is a
structor aunion. - Type
Kind - What a type is, with its qualifiers stripped off into
Type::quals.
Constants§
- MILESTONE
- The milestone in
spec/17-milestones.mdthat fills this crate in.
Functions§
- adjust_
parameter - The type a parameter declared as
idreally has, 6.7.6.3p7 and p8. - compatible
- Whether a declaration of
leftand a declaration ofrightdeclare the same thing, 6.2.7. - composite
- The composite type of two compatible types, 6.2.7p3, and
Nonewhen they are not compatible. - float_
width - The width of a real floating type in bits, including the padding
long doublecarries. - int_
width - The width of a standard integer type in bits.
- layout
- The size and alignment of
idontarget. - layout_
record - Lays out a
structor aunion. - promote
- The integer promotions, 6.3.1.1.
- promote_
bit_ field - The integer promotions applied to a bit-field of the given width.
- usual_
arithmetic - The usual arithmetic conversions, 6.3.1.8: the one type both operands are converted to.