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#[doc = include_str!("../doc-examples/hir.rs")]
34pub 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 _pinned: PhantomPinned,
48}
49
50macro_rules! project_ptr {
52 ($x:ident -> $y:ident) => {
53 &raw mut (*$x).$y
54 };
55}
56
57impl Compiler {
58 #[expect(clippy::missing_transmute_annotations)]
60 pub fn new(sess: Session) -> Self {
61 let mut inner = Box::pin(MaybeUninit::<CompilerInner<'_>>::uninit());
62
63 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 Self(ManuallyDrop::new(unsafe { std::mem::transmute(inner) }))
72 }
73
74 #[inline]
76 pub fn sess(&self) -> &Session {
77 &self.0.sess
78 }
79
80 #[inline]
82 pub fn sess_mut(&mut self) -> &mut Session {
83 self.as_mut().sess_mut()
84 }
85
86 #[inline]
88 pub fn dcx(&self) -> &DiagCtxt {
89 &self.sess().dcx
90 }
91
92 #[inline]
94 pub fn dcx_mut(&mut self) -> &mut DiagCtxt {
95 &mut self.sess_mut().dcx
96 }
97
98 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 pub fn enter_mut<T: Send>(&mut self, f: impl FnOnce(&mut CompilerRef<'_>) -> T + Send) -> T {
112 let sess = unsafe { trustme::decouple_lt(&self.0.sess) };
114 sess.enter(|| f(self.as_mut()))
115 }
116
117 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 pub fn enter_sequential_mut<T>(&mut self, f: impl FnOnce(&mut CompilerRef<'_>) -> T) -> T {
136 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 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#[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 unsafe { std::mem::transmute(inner) }
199 }
200
201 #[inline]
202 fn new_mut<'a>(inner: &'a mut CompilerInner<'c>) -> &'a mut Self {
203 unsafe { std::mem::transmute(inner) }
205 }
206
207 #[inline]
209 pub fn sess(&self) -> &'c Session {
210 self.gcx().sess
211 }
212
213 #[inline]
216 fn sess_mut(&mut self) -> &mut Session {
217 &mut self.inner.sess
218 }
219
220 #[inline]
222 pub fn dcx(&self) -> &'c DiagCtxt {
223 &self.sess().dcx
224 }
225
226 #[inline]
228 pub fn dcx_mut(&mut self) -> &mut DiagCtxt {
229 &mut self.sess_mut().dcx
230 }
231
232 #[inline]
234 pub fn sources(&self) -> &'c Sources<'c> {
235 &self.gcx().sources
236 }
237
238 #[inline]
240 pub fn sources_mut(&mut self) -> &mut Sources<'c> {
241 &mut self.gcx_mut().get_mut().sources
242 }
243
244 #[inline]
246 pub fn gcx(&self) -> Gcx<'c> {
247 Gcx::new(unsafe { trustme::decouple_lt(&self.inner.gcx) })
249 }
250
251 #[inline]
252 pub(crate) fn gcx_mut(&mut self) -> GcxMut<'c> {
253 GcxMut::new(&mut self.inner.gcx)
255 }
256
257 pub fn drop_asts(&mut self) {
262 let sources = std::mem::take(&mut self.inner.gcx.sources);
264 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 pub fn parse(&mut self) -> ParsingContext<'c> {
280 ParsingContext::new(self.gcx_mut())
281 }
282
283 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 use solar_ast::{Span, Symbol};
313 use solar_interface::{BytePos, ColorChoice};
314
315 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 #[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 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 stage_test(Err("from `none` to `analysis`"), |c| {
423 assert_eq!(c.analysis(), Ok(ControlFlow::Continue(())));
424 });
425
426 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 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 *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}