Skip to main content

Crate rucc_types

Crate rucc_types 

Source
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 enum declaration in Types.
EnumInfo
What is known about one enum declaration.
Field
One member of a record, placed.
FieldDecl
One member of a record as the program wrote it.
FunctionId
The identity of a function type in Types.
FunctionType
A function type.
Layout
The size and alignment of a complete object type.
Qualifiers
The qualifiers a type can carry.
RecordId
The identity of a struct or union declaration in Types.
RecordInfo
What is known about one struct or union declaration.
RecordLayout
A record, laid out.
RecordOptions
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§

ArrayLen
How many elements an array has, which is four different answers in C.
FloatKind
The real floating types.
IntKind
The standard integer types, the character types kept apart from them, and __int128.
LayoutError
Why a type has no layout.
RecordError
Why a record has no layout.
RecordKind
Whether a record is a struct or a union.
TypeKind
What a type is, with its qualifiers stripped off into Type::quals.

Constants§

MILESTONE
The milestone in spec/17-milestones.md that fills this crate in.

Functions§

adjust_parameter
The type a parameter declared as id really has, 6.7.6.3p7 and p8.
compatible
Whether a declaration of left and a declaration of right declare the same thing, 6.2.7.
composite
The composite type of two compatible types, 6.2.7p3, and None when they are not compatible.
float_width
The width of a real floating type in bits, including the padding long double carries.
int_width
The width of a standard integer type in bits.
layout
The size and alignment of id on target.
layout_record
Lays out a struct or a union.
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.