solar_sema/
compiler.rs

1use crate::{
2    ParsingContext, Sources, fmt_bytes,
3    ty::{Gcx, GcxMut, GlobalCtxt},
4};
5use solar_data_structures::trustme;
6use solar_interface::{Result, Session, diagnostics::DiagCtxt};
7use std::{
8    fmt,
9    marker::PhantomPinned,
10    mem::{ManuallyDrop, MaybeUninit},
11    ops::ControlFlow,
12    pin::Pin,
13};
14use thread_local::ThreadLocal;
15
16/// The compiler.
17///
18/// This is the main entry point and driver for the compiler.
19///
20/// It must be [`enter`ed](Self::enter) to perform most operations, as it makes use of thread-local
21/// storage, which is only available inside of a closure.
22/// [`enter_mut`](Self::enter_mut) is only necessary when parsing sources and lowering the ASTs. All
23/// accesses after can make use of `gcx`, passed by immutable reference.
24///
25/// Once a stage-advancing operation is performed, such as `parse`, `lower`, etc., the compiler may
26/// not perform the same or a previous operation again, with the exception of `parse`.
27///
28/// # Examples
29///
30/// ```
31/// # mod solar { pub use {solar_interface as interface, solar_sema as sema}; }
32/// # fn main() {}
33#[doc = include_str!("../doc-examples/hir.rs")]
34/// ```
35pub struct Compiler(ManuallyDrop<Pin<Box<CompilerInner<'static>>>>);
36
37impl fmt::Debug for Compiler {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        self.enter_sequential(|compiler| compiler.debug_fmt("Compiler", f))
40    }
41}
42
43struct CompilerInner<'a> {
44    sess: Session,
45    gcx: GlobalCtxt<'a>,
46    /// Lifetimes in this struct are self-referential.
47    _pinned: PhantomPinned,
48}
49
50/// `$x->$y` like in C.
51macro_rules! project_ptr {
52    ($x:ident -> $y:ident) => {
53        &raw mut (*$x).$y
54    };
55}
56
57impl Compiler {
58    /// Creates a new compiler.
59    #[expect(clippy::missing_transmute_annotations)]
60    pub fn new(sess: Session) -> Self {
61        let mut inner = Box::pin(MaybeUninit::<CompilerInner<'_>>::uninit());
62
63        // SAFETY: Valid pointer, `init` initializes all fields.
64        unsafe {
65            let inner = Pin::get_unchecked_mut(Pin::as_mut(&mut inner));
66            let inner = inner.as_mut_ptr();
67            CompilerInner::init(inner, sess);
68        }
69
70        // SAFETY: `inner` has been initialized, `MaybeUninit<T>` is transmuted to `T`.
71        Self(ManuallyDrop::new(unsafe { std::mem::transmute(inner) }))
72    }
73
74    /// Returns a reference to the compiler session.
75    #[inline]
76    pub fn sess(&self) -> &Session {
77        &self.0.sess
78    }
79
80    /// Returns a mutable reference to the compiler session.
81    #[inline]
82    pub fn sess_mut(&mut self) -> &mut Session {
83        self.as_mut().sess_mut()
84    }
85
86    /// Returns a reference to the diagnostics context.
87    #[inline]
88    pub fn dcx(&self) -> &DiagCtxt {
89        &self.sess().dcx
90    }
91
92    /// Returns a mutable reference to the diagnostics context.
93    #[inline]
94    pub fn dcx_mut(&mut self) -> &mut DiagCtxt {
95        &mut self.sess_mut().dcx
96    }
97
98    /// Enters the compiler context.
99    ///
100    /// See [`Session::enter`](Session::enter) for more details.
101    pub fn enter<T: Send>(&self, f: impl FnOnce(&CompilerRef<'_>) -> T + Send) -> T {
102        self.0.sess.enter(|| f(CompilerRef::new(&self.0)))
103    }
104
105    /// Enters the compiler context with mutable access.
106    ///
107    /// This is currently only necessary when parsing sources and lowering the ASTs.
108    /// All accesses after can make use of `gcx`, passed by immutable reference.
109    ///
110    /// See [`Session::enter`](Session::enter) for more details.
111    pub fn enter_mut<T: Send>(&mut self, f: impl FnOnce(&mut CompilerRef<'_>) -> T + Send) -> T {
112        // SAFETY: `CompilerRef` does not allow mutable access to the session.
113        let sess = unsafe { trustme::decouple_lt(&self.0.sess) };
114        sess.enter(|| f(self.as_mut()))
115    }
116
117    /// Enters the compiler context.
118    ///
119    /// Note that this does not set up the rayon thread pool. This is only useful when parsing
120    /// sequentially, like manually using `Parser`. Otherwise, it might cause panics later on if a
121    /// thread pool is expected to be set up correctly.
122    ///
123    /// See [`enter`](Self::enter) for more details.
124    pub fn enter_sequential<T>(&self, f: impl FnOnce(&CompilerRef<'_>) -> T) -> T {
125        self.0.sess.enter_sequential(|| f(CompilerRef::new(&self.0)))
126    }
127
128    /// Enters the compiler context with mutable access.
129    ///
130    /// Note that this does not set up the rayon thread pool. This is only useful when parsing
131    /// sequentially, like manually using `Parser`. Otherwise, it might cause panics later on if a
132    /// thread pool is expected to be set up correctly.
133    ///
134    /// See [`enter_mut`](Self::enter_mut) for more details.
135    pub fn enter_sequential_mut<T>(&mut self, f: impl FnOnce(&mut CompilerRef<'_>) -> T) -> T {
136        // SAFETY: `CompilerRef` does not allow mutable access to the session.
137        let sess = unsafe { trustme::decouple_lt(&self.0.sess) };
138        sess.enter_sequential(|| f(self.as_mut()))
139    }
140
141    fn as_mut(&mut self) -> &mut CompilerRef<'_> {
142        // SAFETY: `CompilerRef` does not allow invalidating the `Pin`.
143        let inner = unsafe { Pin::get_unchecked_mut(Pin::as_mut(&mut self.0)) };
144        let inner = unsafe {
145            std::mem::transmute::<&mut CompilerInner<'static>, &mut CompilerInner<'_>>(inner)
146        };
147        CompilerRef::new_mut(inner)
148    }
149}
150
151impl CompilerInner<'_> {
152    #[inline]
153    #[allow(elided_lifetimes_in_paths)]
154    unsafe fn init(this: *mut Self, sess: Session) {
155        unsafe {
156            let sess_p = project_ptr!(this->sess);
157            sess_p.write(sess);
158
159            let sess = &*sess_p;
160            project_ptr!(this->gcx).write(GlobalCtxt::new(sess));
161        }
162    }
163}
164
165impl Drop for CompilerInner<'_> {
166    fn drop(&mut self) {
167        log_ast_arenas_stats(&mut self.gcx.ast_arenas);
168        debug!(hir_allocated = %fmt_bytes(self.gcx.hir_arenas.iter_mut().map(|a| a.allocated_bytes()).sum::<usize>()));
169    }
170}
171
172impl Drop for Compiler {
173    fn drop(&mut self) {
174        let _guard = debug_span!("Compiler::drop").entered();
175        unsafe { ManuallyDrop::drop(&mut self.0) };
176    }
177}
178
179/// A reference to the compiler.
180///
181/// This is only available inside the [`Compiler::enter`] closure, and has access to the global
182/// context.
183#[repr(transparent)]
184pub struct CompilerRef<'c> {
185    inner: CompilerInner<'c>,
186}
187
188impl fmt::Debug for CompilerRef<'_> {
189    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190        self.debug_fmt("CompilerRef", f)
191    }
192}
193
194impl<'c> CompilerRef<'c> {
195    #[inline]
196    fn new<'a>(inner: &'a CompilerInner<'c>) -> &'a Self {
197        // SAFETY: `repr(transparent)`
198        unsafe { std::mem::transmute(inner) }
199    }
200
201    #[inline]
202    fn new_mut<'a>(inner: &'a mut CompilerInner<'c>) -> &'a mut Self {
203        // SAFETY: `repr(transparent)`
204        unsafe { std::mem::transmute(inner) }
205    }
206
207    /// Returns a reference to the compiler session.
208    #[inline]
209    pub fn sess(&self) -> &'c Session {
210        self.gcx().sess
211    }
212
213    // NOTE: Do not expose mutable access to the session directly! See `replace_entered_session`.
214    /// Returns a mutable reference to the compiler session.
215    #[inline]
216    fn sess_mut(&mut self) -> &mut Session {
217        &mut self.inner.sess
218    }
219
220    /// Returns a reference to the diagnostics context.
221    #[inline]
222    pub fn dcx(&self) -> &'c DiagCtxt {
223        &self.sess().dcx
224    }
225
226    /// Returns a mutable reference to the diagnostics context.
227    #[inline]
228    pub fn dcx_mut(&mut self) -> &mut DiagCtxt {
229        &mut self.sess_mut().dcx
230    }
231
232    /// Returns a reference to the sources.
233    #[inline]
234    pub fn sources(&self) -> &'c Sources<'c> {
235        &self.gcx().sources
236    }
237
238    /// Returns a mutable reference to the sources.
239    #[inline]
240    pub fn sources_mut(&mut self) -> &mut Sources<'c> {
241        &mut self.gcx_mut().get_mut().sources
242    }
243
244    /// Returns a reference to the global context.
245    #[inline]
246    pub fn gcx(&self) -> Gcx<'c> {
247        // SAFETY: `CompilerRef` is only accessible in the `Compiler::enter` closure.
248        Gcx::new(unsafe { trustme::decouple_lt(&self.inner.gcx) })
249    }
250
251    #[inline]
252    pub(crate) fn gcx_mut(&mut self) -> GcxMut<'c> {
253        // SAFETY: `CompilerRef` is only accessible in the `Compiler::enter` closure.
254        GcxMut::new(&mut self.inner.gcx)
255    }
256
257    /// Drops the sources, ASTs, and AST arenas in a separate thread.
258    ///
259    /// This is not done by default in the pipeline, but it can be called after `lower_asts` to
260    /// free up memory.
261    pub fn drop_asts(&mut self) {
262        // TODO: Do we want to drop all the sources instead of just the ASTs?
263        let sources = std::mem::take(&mut self.inner.gcx.sources);
264        // SAFETY: `sources` points into `ast_arenas`, which we move together into the closure.
265        let sources = unsafe { std::mem::transmute::<Sources<'_>, Sources<'static>>(sources) };
266        let mut ast_arenas = std::mem::take(&mut self.inner.gcx.ast_arenas);
267        self.inner.gcx.sess.spawn(move || {
268            let _guard = debug_span!("drop_asts").entered();
269            log_ast_arenas_stats(&mut ast_arenas);
270            drop(sources);
271            drop(ast_arenas);
272        });
273    }
274
275    /// Returns a builder for parsing sources.
276    ///
277    /// [`ParsingContext::parse`](ParsingContext::parse) must be called at the end to actually parse
278    /// the sources.
279    pub fn parse(&mut self) -> ParsingContext<'c> {
280        ParsingContext::new(self.gcx_mut())
281    }
282
283    /// Performs AST lowering.
284    ///
285    /// Lowers the entire program to HIR, populating `gcx.hir`.
286    pub fn lower_asts(&mut self) -> Result<ControlFlow<()>> {
287        crate::lower(self)
288    }
289
290    pub fn analysis(&self) -> Result<ControlFlow<()>> {
291        crate::analysis(self.gcx())
292    }
293
294    fn debug_fmt(&self, name: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295        f.debug_struct(name).field("gcx", &self.gcx()).finish_non_exhaustive()
296    }
297}
298
299fn log_ast_arenas_stats(arenas: &mut ThreadLocal<solar_ast::Arena>) {
300    if arenas.iter_mut().len() == 0 {
301        return;
302    }
303    debug!(asts_allocated = %fmt_bytes(arenas.iter_mut().map(|a| a.allocated_bytes()).sum::<usize>()));
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309    use std::path::PathBuf;
310
311    // --- copy from `crates/interface/src/session.rs`
312    use solar_ast::{Span, Symbol};
313    use solar_interface::{BytePos, ColorChoice};
314
315    /// Session to test `enter`.
316    fn enter_tests_session() -> Session {
317        let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
318        sess.source_map().new_source_file(PathBuf::from("test"), "abcd").unwrap();
319        sess
320    }
321
322    #[track_caller]
323    fn use_globals_parallel(sess: &Session) {
324        use rayon::prelude::*;
325
326        use_globals();
327        sess.spawn(|| use_globals());
328        sess.join(|| use_globals(), || use_globals());
329        [1, 2, 3].par_iter().for_each(|_| use_globals());
330        use_globals();
331    }
332
333    #[track_caller]
334    fn use_globals() {
335        use_globals_no_sm();
336
337        let span = Span::new(BytePos(0), BytePos(1));
338        assert_eq!(format!("{span:?}"), "test:1:1: 1:2");
339        assert_eq!(format!("{span:#?}"), "test:1:1: 1:2");
340    }
341
342    #[track_caller]
343    fn use_globals_no_sm() {
344        let s = "hello";
345        let sym = Symbol::intern(s);
346        assert_eq!(sym.as_str(), s);
347    }
348    // --- end copy
349
350    #[test]
351    fn parse_multiple_times() {
352        let sess = Session::builder().with_test_emitter().build();
353        let mut compiler = Compiler::new(sess);
354
355        assert!(compiler.enter(|c| c.gcx().sources.is_empty()));
356        compiler.enter_mut(|c| {
357            let pcx = c.parse();
358            pcx.parse();
359        });
360        assert!(compiler.enter(|c| c.gcx().sources.is_empty()));
361
362        compiler.enter_mut(|c| {
363            let mut pcx = c.parse();
364            pcx.add_file(
365                c.sess().source_map().new_source_file(PathBuf::from("test.sol"), "").unwrap(),
366            );
367            pcx.parse();
368        });
369        assert_eq!(compiler.enter(|c| c.gcx().sources.len()), 1);
370        assert_eq!(compiler.enter(|c| c.gcx().sources.asts().count()), 1);
371
372        compiler.enter_mut(|c| {
373            let mut pcx = c.parse();
374            pcx.add_file(
375                c.sess().source_map().new_source_file(PathBuf::from("test2.sol"), "").unwrap(),
376            );
377            pcx.parse();
378        });
379        assert_eq!(compiler.enter(|c| c.gcx().sources.len()), 2);
380        assert_eq!(compiler.enter(|c| c.gcx().sources.asts().count()), 2);
381
382        compiler.enter_mut(|c| c.drop_asts());
383        assert_eq!(compiler.enter(|c| c.gcx().sources.len()), 0);
384        assert_eq!(compiler.enter(|c| c.gcx().sources.asts().count()), 0);
385    }
386
387    fn stage_test(expected: Result<(), &str>, f: fn(&mut CompilerRef<'_>)) {
388        let sess =
389            Session::builder().with_buffer_emitter(solar_interface::ColorChoice::Never).build();
390        let mut compiler = Compiler::new(sess);
391        let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| compiler.enter_mut(f)));
392        let errs = compiler.sess().dcx.emitted_errors().unwrap();
393        match expected {
394            Ok(()) => assert!(r.is_ok(), "panicked: {errs:#?}"),
395            Err(e) => {
396                assert!(r.is_err(), "didn't panic: {errs:#?}");
397                let errs = errs.unwrap_err();
398                let d = errs.to_string();
399                assert!(d.contains("invalid compiler stage transition:"), "{d}");
400                assert!(d.contains(e), "{d}");
401                assert!(d.contains("stages must be advanced sequentially"), "{d}");
402            }
403        }
404    }
405
406    fn parse_dummy_file(c: &mut CompilerRef<'_>) {
407        let mut pcx = c.parse();
408        pcx.add_file(c.sess().source_map().new_source_file(PathBuf::from("test.sol"), "").unwrap());
409        pcx.parse();
410    }
411
412    #[test]
413    fn stage_tests() {
414        // Backwards.
415        stage_test(Err("from `lowering` to `parsing`"), |c| {
416            parse_dummy_file(c);
417            assert_eq!(c.lower_asts(), Ok(ControlFlow::Continue(())));
418            parse_dummy_file(c);
419        });
420
421        // Too far ahead.
422        stage_test(Err("from `none` to `analysis`"), |c| {
423            assert_eq!(c.analysis(), Ok(ControlFlow::Continue(())));
424        });
425
426        // Same stage.
427        stage_test(Err("from `lowering` to `lowering`"), |c| {
428            parse_dummy_file(c);
429            assert_eq!(c.lower_asts(), Ok(ControlFlow::Continue(())));
430            assert_eq!(c.lower_asts(), Ok(ControlFlow::Continue(())));
431            assert_eq!(c.analysis(), Ok(ControlFlow::Continue(())));
432        });
433        stage_test(Err("from `analysis` to `analysis`"), |c| {
434            parse_dummy_file(c);
435            assert_eq!(c.lower_asts(), Ok(ControlFlow::Continue(())));
436            assert_eq!(c.analysis(), Ok(ControlFlow::Continue(())));
437            assert_eq!(c.analysis(), Ok(ControlFlow::Continue(())));
438        });
439        // Parsing is special cased.
440        stage_test(Ok(()), |c| {
441            parse_dummy_file(c);
442            parse_dummy_file(c);
443        });
444    }
445
446    #[test]
447    fn replace_session() {
448        let mut compiler = Compiler::new(Session::builder().with_test_emitter().build());
449        compiler.dcx().err("test").emit();
450        assert!(compiler.sess().dcx.has_errors().is_err());
451        *compiler.sess_mut() = Session::builder().with_test_emitter().build();
452        assert!(compiler.sess().dcx.has_errors().is_ok());
453    }
454
455    #[test]
456    fn replace_entered_session() {
457        let mut compiler = Compiler::new(enter_tests_session());
458        compiler.enter_mut(|compiler| {
459            use_globals_parallel(compiler.sess());
460
461            compiler.dcx().err("test").emit();
462            assert!(compiler.sess().dcx.has_errors().is_err());
463
464            // Replacing `Session` here drops the internal thread pool, which is currently in use,
465            // so we must not expose mutable access to the session.
466            *compiler.dcx_mut() = enter_tests_session().dcx;
467            assert!(compiler.sess().dcx.has_errors().is_ok());
468
469            use_globals_parallel(compiler.sess());
470        });
471        assert!(compiler.sess().dcx.has_errors().is_ok());
472    }
473}