use crate::{
ParsingContext, Sources, fmt_bytes,
ty::{Gcx, GcxMut, GlobalCtxt},
};
use solar_data_structures::trustme;
use solar_interface::{Result, Session, diagnostics::DiagCtxt};
use std::{
fmt,
marker::PhantomPinned,
mem::{ManuallyDrop, MaybeUninit},
ops::ControlFlow,
pin::Pin,
};
use thread_local::ThreadLocal;
#[doc = include_str!("../doc-examples/hir.rs")]
pub struct Compiler(ManuallyDrop<Pin<Box<CompilerInner<'static>>>>);
impl fmt::Debug for Compiler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.enter_sequential(|compiler| compiler.debug_fmt("Compiler", f))
}
}
struct CompilerInner<'a> {
sess: Session,
gcx: GlobalCtxt<'a>,
_pinned: PhantomPinned,
}
macro_rules! project_ptr {
($x:ident -> $y:ident) => {
&raw mut (*$x).$y
};
}
impl Compiler {
#[expect(clippy::missing_transmute_annotations)]
pub fn new(sess: Session) -> Self {
let mut inner = Box::pin(MaybeUninit::<CompilerInner<'_>>::uninit());
unsafe {
let inner = Pin::get_unchecked_mut(Pin::as_mut(&mut inner));
let inner = inner.as_mut_ptr();
CompilerInner::init(inner, sess);
}
Self(ManuallyDrop::new(unsafe { std::mem::transmute(inner) }))
}
#[inline]
pub fn sess(&self) -> &Session {
&self.0.sess
}
#[inline]
pub fn sess_mut(&mut self) -> &mut Session {
self.as_mut().sess_mut()
}
#[inline]
pub fn dcx(&self) -> &DiagCtxt {
&self.sess().dcx
}
#[inline]
pub fn dcx_mut(&mut self) -> &mut DiagCtxt {
&mut self.sess_mut().dcx
}
pub fn enter<T: Send>(&self, f: impl FnOnce(&CompilerRef<'_>) -> T + Send) -> T {
self.0.sess.enter(|| f(CompilerRef::new(&self.0)))
}
pub fn enter_mut<T: Send>(&mut self, f: impl FnOnce(&mut CompilerRef<'_>) -> T + Send) -> T {
let sess = unsafe { trustme::decouple_lt(&self.0.sess) };
sess.enter(|| f(self.as_mut()))
}
pub fn enter_sequential<T>(&self, f: impl FnOnce(&CompilerRef<'_>) -> T) -> T {
self.0.sess.enter_sequential(|| f(CompilerRef::new(&self.0)))
}
pub fn enter_sequential_mut<T>(&mut self, f: impl FnOnce(&mut CompilerRef<'_>) -> T) -> T {
let sess = unsafe { trustme::decouple_lt(&self.0.sess) };
sess.enter_sequential(|| f(self.as_mut()))
}
fn as_mut(&mut self) -> &mut CompilerRef<'_> {
let inner = unsafe { Pin::get_unchecked_mut(Pin::as_mut(&mut self.0)) };
let inner = unsafe {
std::mem::transmute::<&mut CompilerInner<'static>, &mut CompilerInner<'_>>(inner)
};
CompilerRef::new_mut(inner)
}
}
impl CompilerInner<'_> {
#[inline]
#[allow(elided_lifetimes_in_paths)]
unsafe fn init(this: *mut Self, sess: Session) {
unsafe {
let sess_p = project_ptr!(this->sess);
sess_p.write(sess);
let sess = &*sess_p;
project_ptr!(this->gcx).write(GlobalCtxt::new(sess));
}
}
}
impl Drop for CompilerInner<'_> {
fn drop(&mut self) {
log_ast_arenas_stats(&mut self.gcx.ast_arenas);
debug!(hir_allocated = %fmt_bytes(self.gcx.hir_arenas.iter_mut().map(|a| a.allocated_bytes()).sum::<usize>()));
}
}
impl Drop for Compiler {
fn drop(&mut self) {
let _guard = debug_span!("Compiler::drop").entered();
unsafe { ManuallyDrop::drop(&mut self.0) };
}
}
#[repr(transparent)]
pub struct CompilerRef<'c> {
inner: CompilerInner<'c>,
}
impl fmt::Debug for CompilerRef<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.debug_fmt("CompilerRef", f)
}
}
impl<'c> CompilerRef<'c> {
#[inline]
fn new<'a>(inner: &'a CompilerInner<'c>) -> &'a Self {
unsafe { std::mem::transmute(inner) }
}
#[inline]
fn new_mut<'a>(inner: &'a mut CompilerInner<'c>) -> &'a mut Self {
unsafe { std::mem::transmute(inner) }
}
#[inline]
pub fn sess(&self) -> &'c Session {
self.gcx().sess
}
#[inline]
fn sess_mut(&mut self) -> &mut Session {
&mut self.inner.sess
}
#[inline]
pub fn dcx(&self) -> &'c DiagCtxt {
&self.sess().dcx
}
#[inline]
pub fn dcx_mut(&mut self) -> &mut DiagCtxt {
&mut self.sess_mut().dcx
}
#[inline]
pub fn sources(&self) -> &'c Sources<'c> {
&self.gcx().sources
}
#[inline]
pub fn sources_mut(&mut self) -> &mut Sources<'c> {
&mut self.gcx_mut().get_mut().sources
}
#[inline]
pub fn gcx(&self) -> Gcx<'c> {
Gcx::new(unsafe { trustme::decouple_lt(&self.inner.gcx) })
}
#[inline]
pub(crate) fn gcx_mut(&mut self) -> GcxMut<'c> {
GcxMut::new(&mut self.inner.gcx)
}
pub fn drop_asts(&mut self) {
let sources = std::mem::take(&mut self.inner.gcx.sources);
let sources = unsafe { std::mem::transmute::<Sources<'_>, Sources<'static>>(sources) };
let mut ast_arenas = std::mem::take(&mut self.inner.gcx.ast_arenas);
self.inner.gcx.sess.spawn(move || {
let _guard = debug_span!("drop_asts").entered();
log_ast_arenas_stats(&mut ast_arenas);
drop(sources);
drop(ast_arenas);
});
}
pub fn parse(&mut self) -> ParsingContext<'c> {
ParsingContext::new(self.gcx_mut())
}
pub fn lower_asts(&mut self) -> Result<ControlFlow<()>> {
crate::lower(self)
}
pub fn analysis(&self) -> Result<ControlFlow<()>> {
crate::analysis(self.gcx())
}
fn debug_fmt(&self, name: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct(name).field("gcx", &self.gcx()).finish_non_exhaustive()
}
}
fn log_ast_arenas_stats(arenas: &mut ThreadLocal<solar_ast::Arena>) {
if arenas.iter_mut().len() == 0 {
return;
}
debug!(asts_allocated = %fmt_bytes(arenas.iter_mut().map(|a| a.allocated_bytes()).sum::<usize>()));
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
use solar_ast::{Span, Symbol};
use solar_interface::{BytePos, ColorChoice};
fn enter_tests_session() -> Session {
let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
sess.source_map().new_source_file(PathBuf::from("test"), "abcd").unwrap();
sess
}
#[track_caller]
fn use_globals_parallel(sess: &Session) {
use rayon::prelude::*;
use_globals();
sess.spawn(|| use_globals());
sess.join(|| use_globals(), || use_globals());
[1, 2, 3].par_iter().for_each(|_| use_globals());
use_globals();
}
#[track_caller]
fn use_globals() {
use_globals_no_sm();
let span = Span::new(BytePos(0), BytePos(1));
assert_eq!(format!("{span:?}"), "test:1:1: 1:2");
assert_eq!(format!("{span:#?}"), "test:1:1: 1:2");
}
#[track_caller]
fn use_globals_no_sm() {
let s = "hello";
let sym = Symbol::intern(s);
assert_eq!(sym.as_str(), s);
}
#[test]
fn parse_multiple_times() {
let sess = Session::builder().with_test_emitter().build();
let mut compiler = Compiler::new(sess);
assert!(compiler.enter(|c| c.gcx().sources.is_empty()));
compiler.enter_mut(|c| {
let pcx = c.parse();
pcx.parse();
});
assert!(compiler.enter(|c| c.gcx().sources.is_empty()));
compiler.enter_mut(|c| {
let mut pcx = c.parse();
pcx.add_file(
c.sess().source_map().new_source_file(PathBuf::from("test.sol"), "").unwrap(),
);
pcx.parse();
});
assert_eq!(compiler.enter(|c| c.gcx().sources.len()), 1);
assert_eq!(compiler.enter(|c| c.gcx().sources.asts().count()), 1);
compiler.enter_mut(|c| {
let mut pcx = c.parse();
pcx.add_file(
c.sess().source_map().new_source_file(PathBuf::from("test2.sol"), "").unwrap(),
);
pcx.parse();
});
assert_eq!(compiler.enter(|c| c.gcx().sources.len()), 2);
assert_eq!(compiler.enter(|c| c.gcx().sources.asts().count()), 2);
compiler.enter_mut(|c| c.drop_asts());
assert_eq!(compiler.enter(|c| c.gcx().sources.len()), 0);
assert_eq!(compiler.enter(|c| c.gcx().sources.asts().count()), 0);
}
fn stage_test(expected: Result<(), &str>, f: fn(&mut CompilerRef<'_>)) {
let sess =
Session::builder().with_buffer_emitter(solar_interface::ColorChoice::Never).build();
let mut compiler = Compiler::new(sess);
let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| compiler.enter_mut(f)));
let errs = compiler.sess().dcx.emitted_errors().unwrap();
match expected {
Ok(()) => assert!(r.is_ok(), "panicked: {errs:#?}"),
Err(e) => {
assert!(r.is_err(), "didn't panic: {errs:#?}");
let errs = errs.unwrap_err();
let d = errs.to_string();
assert!(d.contains("invalid compiler stage transition:"), "{d}");
assert!(d.contains(e), "{d}");
assert!(d.contains("stages must be advanced sequentially"), "{d}");
}
}
}
fn parse_dummy_file(c: &mut CompilerRef<'_>) {
let mut pcx = c.parse();
pcx.add_file(c.sess().source_map().new_source_file(PathBuf::from("test.sol"), "").unwrap());
pcx.parse();
}
#[test]
fn stage_tests() {
stage_test(Err("from `lowering` to `parsing`"), |c| {
parse_dummy_file(c);
assert_eq!(c.lower_asts(), Ok(ControlFlow::Continue(())));
parse_dummy_file(c);
});
stage_test(Err("from `none` to `analysis`"), |c| {
assert_eq!(c.analysis(), Ok(ControlFlow::Continue(())));
});
stage_test(Err("from `lowering` to `lowering`"), |c| {
parse_dummy_file(c);
assert_eq!(c.lower_asts(), Ok(ControlFlow::Continue(())));
assert_eq!(c.lower_asts(), Ok(ControlFlow::Continue(())));
assert_eq!(c.analysis(), Ok(ControlFlow::Continue(())));
});
stage_test(Err("from `analysis` to `analysis`"), |c| {
parse_dummy_file(c);
assert_eq!(c.lower_asts(), Ok(ControlFlow::Continue(())));
assert_eq!(c.analysis(), Ok(ControlFlow::Continue(())));
assert_eq!(c.analysis(), Ok(ControlFlow::Continue(())));
});
stage_test(Ok(()), |c| {
parse_dummy_file(c);
parse_dummy_file(c);
});
}
#[test]
fn replace_session() {
let mut compiler = Compiler::new(Session::builder().with_test_emitter().build());
compiler.dcx().err("test").emit();
assert!(compiler.sess().dcx.has_errors().is_err());
*compiler.sess_mut() = Session::builder().with_test_emitter().build();
assert!(compiler.sess().dcx.has_errors().is_ok());
}
#[test]
fn replace_entered_session() {
let mut compiler = Compiler::new(enter_tests_session());
compiler.enter_mut(|compiler| {
use_globals_parallel(compiler.sess());
compiler.dcx().err("test").emit();
assert!(compiler.sess().dcx.has_errors().is_err());
*compiler.dcx_mut() = enter_tests_session().dcx;
assert!(compiler.sess().dcx.has_errors().is_ok());
use_globals_parallel(compiler.sess());
});
assert!(compiler.sess().dcx.has_errors().is_ok());
}
}