Skip to main content

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}