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, compatibility with the composite type, and spell, which writes a type back as the C declaration it is, are implemented.

Not here yet, and named so that the gap is not mistaken for a decision: the decimal floating types.

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.
IntegerInfo
What an integer type is once it no longer matters how it was spelled.
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.
declare
The type as a declaration of name, which is how it would be written in the program.
element
The element type of an array or a vector, or None where it is neither.
float_format
The binary format a real floating type has on target.
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.
integer_info
The signedness and width of an integer type, and None when id is not one.
is_aggregate
An aggregate type, 6.2.5p21: an array or a struct.
is_arithmetic
An arithmetic type, 6.2.5p18: the integer types and the floating types.
is_array
An array type.
is_atomic
_Atomic(T), whatever T is.
is_complete
A complete type: one whose size is known, so an object of it can exist.
is_complex
A complex type, _Complex T.
is_floating
A floating type, which is the real ones and the complex ones together.
is_function
A function type.
is_integer
An integer type, 6.2.5p17.
is_modifiable
Whether a value of this type may be modified, 6.3.2.1p1.
is_object
An object type, 6.2.5p1: anything that is not a function type.
is_pointer
A pointer type.
is_real
A real type, 6.2.5p17: the integer types and the real floating types.
is_real_floating
A real floating type: float, double, long double and the extended ones.
is_record
A struct or a union.
is_scalar
A scalar type, 6.2.5p21: the arithmetic types and the pointer types.
is_vector
A GNU vector type.
is_void
void.
layout
The size and alignment of id on target.
layout_record
Lays out a struct or a union.
pointee
What a pointer points to, or None where it is not a pointer.
promote
The integer promotions, 6.3.1.1.
promote_bit_field
The integer promotions applied to a bit-field of the given width.
spell
The type as a type name, which is how it would be written in a cast or in a sizeof.
usual_arithmetic
The usual arithmetic conversions, 6.3.1.8: the one type both operands are converted to.