Module: Rubycc::Type
- Defined in:
- lib/rubycc/type.rb
Overview
The type system for this C subset: the standard integer types (char and its
signed/unsigned variants, short, int, long and _Bool), the incomplete
void type, pointers to another type, one-dimensional arrays of another
type, and structures (Type::StructType). The integer, pointer and array
types compare by value, so any two int * are equal, and each renders
itself the way a C declarator would ("int", "unsigned long", "char *",
"int [10]") for use in diagnostics. Structures instead compare by identity
(see Type::StructType): a struct type is the same type only when it is the
very same tag definition, which is what lets a self-referential struct
("struct node { struct node *next; }") describe itself without a
value-equality walk looping forever.
Every type but void and an incomplete struct reports its storage width in
bytes via #size (char/_Bool 1, short 2, int 4, long 8, any pointer 8, an
array its element width times its length, a struct its laid-out size) and
its required boundary via #alignment (an integer type is aligned to its own
width, any pointer 8, an array its element's alignment, a struct its widest
member's). void has no size or alignment (see Type::VoidType) since it is
only ever valid as a function's return type or as the target of a pointer;
an incomplete struct likewise has neither until it is completed.
#integer? groups the standard integer types that mix freely in expressions
and convert to one another implicitly; #float? (FloatType) names the two
floating types, and #arithmetic? is the union of the two — every integer or
floating type — the notion "arithmetic" the usual arithmetic conversions act
on. #signed? / #unsigned? report an integer type's signedness — the axis
that decides signed vs unsigned division, right shift and comparison — and
#bool? names _Bool specifically (whose only values are 0 and 1). #int? and
#char? still name those two specific types (never their unsigned cousins),
#void? names void and #struct? names a structure or a union (both are
aggregates that share the same lvalue/copy machinery; #union? tells the two
apart).
Defined Under Namespace
Classes: Array, EnumType, FloatType, FunctionType, IntegerType, Member, Pointer, StructType, VoidType
Constant Summary collapse
- Char =
The shared integer-type instances, one per distinct C type. LP64 governs the widths: int is 4 bytes, long and any pointer 8.
long longnormalizes tolong(same width under LP64) at the point it is parsed, so no separate instance is needed here.The character types are three distinct types (6.2.5p15):
signed charandunsigned charhave a fixed signedness, while plaincharbehaves as one or the other, and which one is implementation-defined — pinned per ABI, so it is a property of the target rather than of this subset: the x86-64 System V psABI makes plaincharsigned, AAPCS64 makes it unsigned. Both plain-charinstances therefore exist side by side and spell themselves "char" (so #char?, #to_s and every diagnostic read alike whichever is in play); Type.plain_char picks the one a target uses, and the front end carries that choice from Compiler#compile down to every place achartype is built. Type::Char stays the signed one, which is what a caller with no target in hand (the default x86-64) means by "char". IntegerType.new("char", 1, true)
- UnsignedChar =
IntegerType.new("char", 1, false)
- SChar =
IntegerType.new("signed char", 1, true)
- UChar =
IntegerType.new("unsigned char", 1, false)
- Short =
IntegerType.new("short", 2, true)
- UShort =
IntegerType.new("unsigned short", 2, false)
- Int =
IntegerType.new("int", 4, true)
- UInt =
IntegerType.new("unsigned int", 4, false)
- Long =
IntegerType.new("long", 8, true)
- ULong =
IntegerType.new("unsigned long", 8, false)
- Bool =
_Boolis treated as an unsigned 1-byte type whose stored value is only ever 0 or 1; a conversion to it lowers to "value != 0" (see the generator). IntegerType.new("_Bool", 1, false, bool: true)
- Int128 =
The GNU 128-bit integer types (
__int128andunsigned __int128), 16 bytes wide and 16-byte aligned. They are ordinary IntegerTypes for classification (#integer?, #signed?, sizeof/_Alignof, the usual arithmetic conversions), but their value does not fit a single 64-bit slot: the generator represents a 128-bit value the way it represents a small struct — as a 16-byte stack object whose low eightbyte lives at +0 and high at +8, its address carried in an ordinary vreg — and lowers each supported operation to 64-bit ops on the two halves. The width alone (size 16) marks them apart from every other integer type; the generator's #wide128? tests it. IntegerType.new("__int128", 16, true)
- UInt128 =
IntegerType.new("unsigned __int128", 16, false)
- Float =
The shared floating-type instances (Type::Float, Type::Double).
FloatType.new("float", 4)
- Double =
FloatType.new("double", 8)
- Void =
The lone
void. Referred to everywhere as Type::Void. VoidType.new
- VaListTag =
The System V AMD64 psABI representation of a
va_listelement: the__va_list_tagstructure a call's variable arguments are read through. Its layout — a 32-bitgp_offset(the byte offset of the next integer argument still in the register-save area), a 32-bitfp_offset(the same for a vector argument), anoverflow_arg_areapointer (the next argument that spilled onto the stack) and areg_save_areapointer (the base of the saved argument registers) — is fixed by the ABI, so building it here from the ordinary #define layout path (size 24, 8-byte aligned) matches what a System V compiler and its C library agree on. A single shared instance stands for the tag, and, being a StructType, it compares by identity, so the generator recognizes "pointer to __va_list_tag" by object identity when type-checking a va_start/va_arg/va_end operand. StructType.new("__va_list_tag").tap do |tag| tag.define([ ["gp_offset", UInt], ["fp_offset", UInt], ["overflow_arg_area", Pointer.new(Void)], ["reg_save_area", Pointer.new(Void)] ]) end
- BuiltinVaList =
The type the built-in
__builtin_va_listtypedef names: a one-element array of __va_list_tag. The array shape is what makes ava_listobject decay to a__va_list_tag *in every expression context (so passing one to a helper hands over a pointer to the same object, and va_start/va_arg write through it), while a local declaration still reserves the whole 24-byte tag as a stack object — exactly the System V convention. Array.new(VaListTag, 1)
- AArch64VaListTag =
The AAPCS64 representation of a
va_listelement. AArch64 splits the two register files System V folds into one save area, so the tag has five fields rather than four (AAPCS64 §B.4 / the Arm-64 va_list):__stackthe next stack argument,__gr_topand__vr_topthe ends of the integer and vector save areas, and__gr_offs/__vr_offssigned byte offsets from those tops. The offsets run the other way from System V's: they start negative (the whole file still to be read) and climb toward zero, at which point the file is spent and the argument comes off__stack. The layout (size 32, 8-byte aligned) is what the AArch64 C library agrees on, and the tag compares by identity, so the generator recognizes it exactly as it does the System V one. StructType.new("__va_list").tap do |tag| tag.define([ ["__stack", Pointer.new(Void)], ["__gr_top", Pointer.new(Void)], ["__vr_top", Pointer.new(Void)], ["__gr_offs", Int], ["__vr_offs", Int] ]) end
- AArch64BuiltinVaList =
The AArch64 counterpart of BuiltinVaList: a one-element array of the five-field tag. Making it an array (rather than the bare struct gcc's __builtin_va_list happens to be) has the same decay-to-pointer effect the System V form relies on, and it is ABI-identical at a call boundary: a 32-byte va_list is passed by reference under AAPCS64 6.4.2 (a pointer to the object in a single integer register), which is exactly what the decayed array pointer already is. Forwarding a va_list to vprintf therefore lands the same pointer in the same register a gcc caller would.
Array.new(AArch64VaListTag, 1)
Class Method Summary collapse
-
.character?(type) ⇒ Boolean
True for the character types (6.2.5p15): either plain
charand the two explicitly signed ones. -
.composite(first, second) ⇒ Object
The composite type of two declarations of the same object (6.2.7p3), or nil when the two types are not compatible — the signal a declaration merge turns into its "conflicting types" diagnostic.
-
.plain_char(signed) ⇒ Object
The plain-
charinstance a target uses: the signed one when its ABI makes plaincharsigned (x86-64 System V), the unsigned one otherwise (AAPCS64).
Class Method Details
.character?(type) ⇒ Boolean
True for the character types (6.2.5p15): either plain char and the two
explicitly signed ones. This is what "an array of character type", the
form a string literal may initialize, means; _Bool is one byte wide too
but is not a character type.
1231 1232 1233 1234 |
# File 'lib/rubycc/type.rb', line 1231 def self.character?(type) type.equal?(Char) || type.equal?(UnsignedChar) || type.equal?(SChar) || type.equal?(UChar) end |
.composite(first, second) ⇒ Object
The composite type of two declarations of the same object (6.2.7p3), or nil when the two types are not compatible — the signal a declaration merge turns into its "conflicting types" diagnostic.
One case of that paragraph matters in this subset: when one declaration is an array type of known size and the other an array type of unspecified size, the composite type is the array type of known size. So
extern int tbl[]; int tbl[3] = {1, 2, 3};
declares one object of type "int [3]" in either order, and a later sizeof/subscript measures the completed type rather than the unbounded reference. Two known but different sizes are incompatible, and so are incompatible element types. The recursion into the element type carries the same rule through a multidimensional array ("extern int m[4];" against "int m[4]"), whose inner dimensions are always known and so must agree.
Every other pair of types composes exactly when the two are identical, which is what the equality comparison at each declaration-merge site meant before this rule existed. The paragraph's remaining cases — a function type declared with a parameter type list against one declared without, and a pointer to either of the above — do not arise here: this subset merges function signatures by equality (it models no unprototyped declaration), and a parameter of array type is adjusted to a pointer by the parser.
1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 |
# File 'lib/rubycc/type.rb', line 1216 def self.composite(first, second) return first if first == second return nil unless first.array? && second.array? element = composite(first.element, second.element) return nil if element.nil? return nil if first.length && second.length && first.length != second.length Array.new(element, first.length || second.length) end |
.plain_char(signed) ⇒ Object
The plain-char instance a target uses: the signed one when its ABI makes
plain char signed (x86-64 System V), the unsigned one otherwise
(AAPCS64). Neither is signed char/unsigned char, which keep their own
fixed-signedness instances whatever the target.
1188 1189 1190 |
# File 'lib/rubycc/type.rb', line 1188 def self.plain_char(signed) signed ? Char : UnsignedChar end |