rucc_sema/decl.rs
1//! Declared objects and functions, with their linkage and their storage duration resolved.
2//!
3//! Design: `spec/07-types-and-semantics.md` sections 7.4 and 7.14.
4//!
5//! Only the things that exist at run time are here. A `typedef` is a name for a type and lives
6//! in the type table as sugar, an enumerator is a constant and has been folded into the
7//! expressions that used it, and a tag is a type. What is left is objects and functions, which
8//! are what the walk to the IR needs a list of.
9//!
10//! An initializer is flattened. Brace elision, designators and the order the program wrote
11//! things in are all resolved here into a list of values and the byte offsets they go at, so
12//! that nothing downstream walks a nest of braces against a nest of types a second time. The
13//! contract is that the object starts as zero and the entries are applied in order, which is
14//! also what makes partial initialization and an overwriting designator fall out rather than
15//! need rules of their own.
16
17use rucc_base::{Idx, IdxRange, Symbol};
18use rucc_types::TypeId;
19
20use crate::expr::ExprId;
21use crate::stmt::StmtId;
22
23/// One declared object or function in the arena.
24pub type DeclId = Idx<Decl>;
25
26/// The table of references to declarations, which is what a declaration statement is a run of.
27#[derive(Debug)]
28pub struct DeclRef;
29
30/// A run of declarations.
31pub type DeclList = IdxRange<DeclRef>;
32
33/// A run of the values one initializer stores.
34pub type InitList = IdxRange<InitEntry>;
35
36/// An object or a function, as it was declared.
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct Decl {
39 /// The name, absent for a compound literal and for a parameter that was not given one.
40 pub name: Option<Symbol>,
41 /// The type, after the adjustments a declaration performs: an array parameter has already
42 /// become a pointer, and a function parameter a function pointer.
43 pub ty: TypeId,
44 /// Whether it is an object or a function.
45 pub kind: DeclKind,
46 /// Whether the name is shared with other translation units, and how.
47 pub linkage: Linkage,
48 /// How long the object lives.
49 pub duration: StorageDuration,
50 /// How much of a definition this declaration is.
51 pub state: Definition,
52 /// The alignment `alignas` asked for, absent when the type's own alignment stands.
53 pub alignment: Option<u32>,
54 /// Whether `constexpr` was written, which makes the object a named constant.
55 ///
56 /// C23 6.6p8 puts a named constant of an integer type among the things an integer constant
57 /// expression may be built out of, and a member of one of a structure or union type with
58 /// it. That is the whole reason the keyword exists and it is why this is a fact about the
59 /// declaration rather than something a reader could work out: a `const` object with a
60 /// constant initializer is not one of them, so `const int n = 1; int a[n];` is a variable
61 /// length array and the same two lines with `constexpr` are an array of one.
62 pub constant: bool,
63 /// Whether an attribute asks for this to exist where nothing in the file refers to it.
64 ///
65 /// `used`, `retain`, `constructor`, `destructor` and `alias` each say that something reaches
66 /// the definition from where the compiler cannot see it, which is the only reason a program
67 /// ever writes one of them. Nothing else in the tree says that, and a `static` function
68 /// nothing refers to is not emitted, so this is how a program keeps one that has to be.
69 pub retained: bool,
70 /// The initializer, flattened, absent when there was none. An empty list is `= {}`, which
71 /// C23 added and which zero-initializes, and is not the same as no initializer at all.
72 pub init: Option<InitList>,
73 /// The parameters of a function definition, in order, and empty for everything else.
74 ///
75 /// A parameter is an object with automatic storage like any other, and the body refers to
76 /// one the same way it refers to a local. What is different is that nothing in the body
77 /// declares it, so without this there is no way to ask which objects a definition takes and
78 /// in what order, which is the first question the walk to the IR has: the entry block's
79 /// parameters are these, in this order.
80 ///
81 /// A declaration that is not a definition has none of these even when it was written with a
82 /// prototype, because `int f(int a);` declares no object called `a`. The types are in the
83 /// function type, which is where a call reads them.
84 pub params: DeclList,
85 /// The body of a function definition.
86 pub body: Option<StmtId>,
87}
88
89/// Whether a declaration declares an object or a function.
90///
91/// A `typedef` and an enumerator are neither: one is a name for a type and the other is a
92/// constant, and both have been resolved by the time anything reads this.
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum DeclKind {
95 /// An object, which includes parameters, block-scope variables and compound literals.
96 Object,
97 /// A function.
98 Function,
99}
100
101/// Whether a name is shared with other translation units, and how.
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum Linkage {
104 /// The name is not shared. Block-scope objects without `extern`, parameters, and anything
105 /// declared in a function's body except a function or an `extern` object.
106 None,
107 /// The name is shared within the translation unit and not outside it, which is what
108 /// `static` at file scope means.
109 Internal,
110 /// The name is shared with every translation unit that declares it.
111 External,
112}
113
114/// How long an object lives.
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub enum StorageDuration {
117 /// From the start of the program to the end of it.
118 Static,
119 /// From the start of the thread to the end of it, which is `_Thread_local`.
120 Thread,
121 /// From the point the declaration is reached to the end of the block, which is where a
122 /// variable length array's deallocation and a compound literal's lifetime both come from.
123 Automatic,
124}
125
126/// How much of a definition a declaration is.
127///
128/// The three states are what the one-definition rules are written in terms of, and keeping
129/// them apart is what makes a tentative definition become a definition at the end of the
130/// translation unit rather than at the point it was read.
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub enum Definition {
133 /// A declaration and nothing more, which is what `extern int x;` is and what every
134 /// function declaration without a body is.
135 Declared,
136 /// A file-scope object with no initializer and no `extern`, which is a definition only if
137 /// nothing else in the translation unit defines it. C calls this a tentative definition and
138 /// it is the reason `int x; int x;` is one object and not an error.
139 Tentative,
140 /// A definition: an object with an initializer, a block-scope object with automatic
141 /// storage, or a function with a body.
142 Defined,
143}
144
145/// One value an initializer stores, and where it goes.
146///
147/// The offsets are from the start of the object being initialized, so a nested aggregate has
148/// already been walked and there is nothing left to elide or designate.
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150pub struct InitEntry {
151 /// The byte offset from the start of the object.
152 pub offset: u64,
153 /// The value, already converted to the type of what is at that offset.
154 pub value: ExprId,
155 /// The bit offset within the byte at `offset`, for a bit-field.
156 pub bit_offset: u32,
157 /// The width in bits, for a bit-field, and zero for everything else. A bit-field of width
158 /// zero has no name and cannot be initialized, so zero is free to mean this instead.
159 pub bit_width: u32,
160}
161
162impl InitEntry {
163 /// A value at a byte offset, which is what everything that is not a bit-field is.
164 #[must_use]
165 pub const fn at(offset: u64, value: ExprId) -> InitEntry {
166 InitEntry { offset, value, bit_offset: 0, bit_width: 0 }
167 }
168
169 /// Whether this entry writes part of a byte rather than whole bytes.
170 #[must_use]
171 pub const fn is_bit_field(&self) -> bool {
172 self.bit_width != 0
173 }
174}