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;
22use crate::tast::StrId;
23
24/// One declared object or function in the arena.
25pub type DeclId = Idx<Decl>;
26
27/// The table of references to declarations, which is what a declaration statement is a run of.
28#[derive(Debug)]
29pub struct DeclRef;
30
31/// A run of declarations.
32pub type DeclList = IdxRange<DeclRef>;
33
34/// A run of the values one initializer stores.
35pub type InitList = IdxRange<InitEntry>;
36
37/// An object or a function, as it was declared.
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct Decl {
40 /// The name, absent for a compound literal and for a parameter that was not given one.
41 pub name: Option<Symbol>,
42 /// The type, after the adjustments a declaration performs: an array parameter has already
43 /// become a pointer, and a function parameter a function pointer.
44 pub ty: TypeId,
45 /// Whether it is an object or a function.
46 pub kind: DeclKind,
47 /// Whether the name is shared with other translation units, and how.
48 pub linkage: Linkage,
49 /// How long the object lives.
50 pub duration: StorageDuration,
51 /// How much of a definition this declaration is.
52 pub state: Definition,
53 /// The alignment `alignas` asked for, absent when the type's own alignment stands.
54 pub alignment: Option<u32>,
55 /// Whether `constexpr` was written, which makes the object a named constant.
56 ///
57 /// C23 6.6p8 puts a named constant of an integer type among the things an integer constant
58 /// expression may be built out of, and a member of one of a structure or union type with
59 /// it. That is the whole reason the keyword exists and it is why this is a fact about the
60 /// declaration rather than something a reader could work out: a `const` object with a
61 /// constant initializer is not one of them, so `const int n = 1; int a[n];` is a variable
62 /// length array and the same two lines with `constexpr` are an array of one.
63 pub constant: bool,
64 /// Whether an attribute asks for this to exist where nothing in the file refers to it.
65 ///
66 /// `used`, `retain`, `constructor`, `destructor` and `alias` each say that something reaches
67 /// the definition from where the compiler cannot see it, which is the only reason a program
68 /// ever writes one of them. Nothing else in the tree says that, and a `static` function
69 /// nothing refers to is not emitted, so this is how a program keeps one that has to be.
70 pub retained: bool,
71 /// The symbol this name stands for in the object file, when a declaration of it wrote an
72 /// assembler name of its own.
73 ///
74 /// `extern int f (int) __asm__ ("g");` says that `f` here is the symbol `g`, which is how
75 /// the C library redirects a name: `open` under `_FILE_OFFSET_BITS=64` is declared this way
76 /// and reaches `open64`, and every `_FORTIFY_SOURCE` wrapper is the same trick. It is a fact
77 /// about the name rather than about one declaration of it, so it is kept where the
78 /// declarations of a name are merged, and the first one written is the one that stands.
79 pub asm_label: Option<StrId>,
80 /// The symbol this name is a second spelling of, when `__attribute__((alias("target")))` was
81 /// written on a declaration of it.
82 ///
83 /// A declaration with one of these defines the name rather than declaring it: nothing is
84 /// emitted for the declaration itself and the object file gets a second symbol pointing at
85 /// whatever the string names. `extern int b __attribute__((alias("a")));` is how a program
86 /// gives `a` the name `b`, and `weak, alias` beside it is the form glibc writes so that a
87 /// program may define the name itself instead.
88 ///
89 /// The string is the symbol the linker sees rather than an identifier this resolves, which
90 /// is why it is a [`StrId`] and not a [`Symbol`](rucc_base::Symbol). Whether anything
91 /// defines it is settled where the whole translation unit is known.
92 pub alias: Option<StrId>,
93 /// Whether a definition of this name here is emitted, which `inline` is the only thing that
94 /// changes.
95 ///
96 /// C 6.7.4p7: where every file-scope declaration of a function writes `inline` and none of
97 /// them writes `extern`, the definition in this unit is an inline definition, no external
98 /// definition is emitted for it, and a call goes to the definition some other unit holds.
99 /// One declaration without `inline`, or one with `extern`, makes the whole thing an external
100 /// definition again, which is why this is a fact about the name and is settled where the
101 /// declarations of a name are merged.
102 ///
103 /// The two readings of `inline` swap over under [`Self::gnu_inline`], where it is the
104 /// definition alone that decides and `extern inline` is the one that is not emitted.
105 pub inline: Emission,
106 /// Whether this name is under GNU's reading of `inline` rather than C's.
107 ///
108 /// `__attribute__((__gnu_inline__))` asks for it by name, and the C89 dialects are under it
109 /// throughout, which is what `__GNUC_GNU_INLINE__` tells a header. It is kept because the two
110 /// readings fold differently over the declarations of a name, and because gcc refuses a name
111 /// whose declarations disagree about which one they are under.
112 pub gnu_inline: bool,
113 /// The initializer, flattened, absent when there was none. An empty list is `= {}`, which
114 /// C23 added and which zero-initializes, and is not the same as no initializer at all.
115 pub init: Option<InitList>,
116 /// Whether control does not come back from a call to this function.
117 ///
118 /// `_Noreturn`, `__attribute__((noreturn))` and `[[noreturn]]` all say it and all land here.
119 /// What a caller does with it is put an `unreachable` after the call, so a program that tests
120 /// its allocation with `if (!p) abort();` stops having a path where the block after the test is
121 /// reached carrying a null pointer. Nothing else in the compiler can work that out, because
122 /// what `abort` does belongs to `abort`.
123 ///
124 /// A fact about the name rather than about one declaration of it, so one declaration saying it
125 /// is enough and the merge keeps it. That is the same rule [`Self::retained`] is under and it
126 /// is there for the same reason: the usual place to write it is a header, and the definition in
127 /// the file below writes nothing.
128 pub noreturn: bool,
129 /// How far outside a shared library the name reaches, when a declaration of it said, and
130 /// nothing when none did.
131 ///
132 /// `__attribute__((visibility("hidden")))` and the other three strings it takes. What is kept
133 /// here is only what was written, because the other way a name gets a visibility is
134 /// `-fvisibility=` and that is a fact about the compilation rather than about the declaration.
135 /// The two meet where the IR is built, which is also the only place that has both.
136 ///
137 /// A fact about the name rather than about one declaration of it, like [`Self::asm_label`],
138 /// and merged the way that one is: the first declaration to say something stands. gcc warns
139 /// and keeps the first when a later one disagrees, since the calls above it have already been
140 /// compiled against the answer it gave.
141 pub visibility: Option<Visibility>,
142 /// The parameters of a function definition, in order, and empty for everything else.
143 ///
144 /// A parameter is an object with automatic storage like any other, and the body refers to
145 /// one the same way it refers to a local. What is different is that nothing in the body
146 /// declares it, so without this there is no way to ask which objects a definition takes and
147 /// in what order, which is the first question the walk to the IR has: the entry block's
148 /// parameters are these, in this order.
149 ///
150 /// A declaration that is not a definition has none of these even when it was written with a
151 /// prototype, because `int f(int a);` declares no object called `a`. The types are in the
152 /// function type, which is where a call reads them.
153 pub params: DeclList,
154 /// The body of a function definition.
155 pub body: Option<StmtId>,
156}
157
158/// Whether a declaration declares an object or a function.
159///
160/// A `typedef` and an enumerator are neither: one is a name for a type and the other is a
161/// constant, and both have been resolved by the time anything reads this.
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub enum DeclKind {
164 /// An object, which includes parameters, block-scope variables and compound literals.
165 Object,
166 /// A function.
167 Function,
168}
169
170/// Whether the definition of a name is emitted, which is what `inline` decides.
171///
172/// Two of the three mean that it is emitted, and they are apart because they behave differently
173/// when one more declaration of the name arrives: a name nothing has said anything about takes
174/// whatever the next declaration says, and one that is already an external definition stays one
175/// however the rest of the file is written.
176#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub enum Emission {
178 /// Nothing has been said about it. Every object is this, and so is every function that is not
179 /// declared at file scope with external linkage, since the rule is written about those alone.
180 Silent,
181 /// The definition here is an inline definition and nothing is emitted for it.
182 Inline,
183 /// The definition here is an external definition and is emitted.
184 External,
185}
186
187impl Emission {
188 /// Whether a definition of the name is emitted.
189 #[must_use]
190 pub const fn emits(self) -> bool {
191 !matches!(self, Emission::Inline)
192 }
193}
194
195/// Whether a name is shared with other translation units, and how.
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub enum Linkage {
198 /// The name is not shared. Block-scope objects without `extern`, parameters, and anything
199 /// declared in a function's body except a function or an `extern` object.
200 None,
201 /// The name is shared within the translation unit and not outside it, which is what
202 /// `static` at file scope means.
203 Internal,
204 /// The name is shared with every translation unit that declares it.
205 External,
206}
207
208/// How far outside a shared library a name reaches.
209///
210/// A different question from [`Linkage`] and asked of a different linker. The linkage is what the
211/// static linker does with a name while it is building the output, and this is what the dynamic
212/// linker may do with it once that output is a shared library and is being loaded. A hidden name
213/// is still external as far as the static link is concerned, so two files in the same library
214/// reach each other by it; it is simply not in the dynamic symbol table afterwards.
215///
216/// Four strings are written and there are three answers, because `internal` is `hidden` plus a
217/// promise the program makes about never taking the address across a component boundary. Reading
218/// it as hidden gives less than was asked for, which is safe in the way `-fstrict-aliasing` is
219/// safe: every program correct under the stronger assumption is correct under the weaker one and
220/// nothing here derives anything from the difference. It is written down in
221/// `spec/13-gnu-compat.md` section 13.4 rather than left for someone to find in the output.
222#[derive(Debug, Clone, Copy, PartialEq, Eq)]
223pub enum Visibility {
224 /// In the dynamic symbol table and interposable, which is what a name gets when nothing said
225 /// otherwise and what `visibility("default")` puts back after `-fvisibility=hidden`.
226 Default,
227 /// Not in the dynamic symbol table, so nothing outside the library can name it.
228 Hidden,
229 /// In the dynamic symbol table, and a reference from inside the library binds to the
230 /// definition inside it.
231 Protected,
232}
233
234/// How long an object lives.
235#[derive(Debug, Clone, Copy, PartialEq, Eq)]
236pub enum StorageDuration {
237 /// From the start of the program to the end of it.
238 Static,
239 /// From the start of the thread to the end of it, which is `_Thread_local`.
240 Thread,
241 /// From the point the declaration is reached to the end of the block, which is where a
242 /// variable length array's deallocation and a compound literal's lifetime both come from.
243 Automatic,
244}
245
246/// How much of a definition a declaration is.
247///
248/// The three states are what the one-definition rules are written in terms of, and keeping
249/// them apart is what makes a tentative definition become a definition at the end of the
250/// translation unit rather than at the point it was read.
251#[derive(Debug, Clone, Copy, PartialEq, Eq)]
252pub enum Definition {
253 /// A declaration and nothing more, which is what `extern int x;` is and what every
254 /// function declaration without a body is.
255 Declared,
256 /// A file-scope object with no initializer and no `extern`, which is a definition only if
257 /// nothing else in the translation unit defines it. C calls this a tentative definition and
258 /// it is the reason `int x; int x;` is one object and not an error.
259 Tentative,
260 /// A definition: an object with an initializer, a block-scope object with automatic
261 /// storage, or a function with a body.
262 Defined,
263}
264
265/// One value an initializer stores, and where it goes.
266///
267/// The offsets are from the start of the object being initialized, so a nested aggregate has
268/// already been walked and there is nothing left to elide or designate.
269#[derive(Debug, Clone, Copy, PartialEq, Eq)]
270pub struct InitEntry {
271 /// The byte offset from the start of the object.
272 pub offset: u64,
273 /// The value, already converted to the type of what is at that offset.
274 pub value: ExprId,
275 /// The bit offset within the byte at `offset`, for a bit-field.
276 pub bit_offset: u32,
277 /// The width in bits, for a bit-field, and zero for everything else. A bit-field of width
278 /// zero has no name and cannot be initialized, so zero is free to mean this instead.
279 pub bit_width: u32,
280}
281
282impl InitEntry {
283 /// A value at a byte offset, which is what everything that is not a bit-field is.
284 #[must_use]
285 pub const fn at(offset: u64, value: ExprId) -> InitEntry {
286 InitEntry { offset, value, bit_offset: 0, bit_width: 0 }
287 }
288
289 /// Whether this entry writes part of a byte rather than whole bytes.
290 #[must_use]
291 pub const fn is_bit_field(&self) -> bool {
292 self.bit_width != 0
293 }
294}