use crate::{
ByteSymbol, ColorChoice, SessionGlobals, SourceMap, Symbol,
diagnostics::{DiagCtxt, EmittedDiagnostics},
};
use solar_config::{CompilerOutput, CompilerStage, Opts, SINGLE_THREADED_TARGET, UnstableOpts};
use std::{
fmt,
path::Path,
sync::{Arc, OnceLock},
};
pub struct Session {
pub opts: Opts,
pub dcx: DiagCtxt,
globals: Arc<SessionGlobals>,
thread_pool: OnceLock<rayon::ThreadPool>,
}
impl Default for Session {
fn default() -> Self {
Self::new(Opts::default())
}
}
impl fmt::Debug for Session {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Session")
.field("opts", &self.opts)
.field("dcx", &self.dcx)
.finish_non_exhaustive()
}
}
#[derive(Default)]
#[must_use = "builders don't do anything unless you call `build`"]
pub struct SessionBuilder {
dcx: Option<DiagCtxt>,
globals: Option<SessionGlobals>,
opts: Option<Opts>,
}
impl SessionBuilder {
pub fn dcx(mut self, dcx: DiagCtxt) -> Self {
self.dcx = Some(dcx);
self
}
pub fn source_map(mut self, source_map: Arc<SourceMap>) -> Self {
self.get_globals().source_map = source_map;
self
}
pub fn opts(mut self, opts: Opts) -> Self {
self.opts = Some(opts);
self
}
#[inline]
pub fn with_test_emitter(mut self) -> Self {
let sm = self.get_source_map();
self.dcx(DiagCtxt::with_test_emitter(Some(sm)))
}
#[inline]
pub fn with_stderr_emitter(self) -> Self {
self.with_stderr_emitter_and_color(ColorChoice::Auto)
}
#[inline]
pub fn with_stderr_emitter_and_color(mut self, color_choice: ColorChoice) -> Self {
let sm = self.get_source_map();
self.dcx(DiagCtxt::with_stderr_emitter_and_color(Some(sm), color_choice))
}
#[inline]
pub fn with_buffer_emitter(mut self, color_choice: ColorChoice) -> Self {
let sm = self.get_source_map();
self.dcx(DiagCtxt::with_buffer_emitter(Some(sm), color_choice))
}
#[inline]
pub fn with_silent_emitter(self, fatal_note: Option<String>) -> Self {
self.dcx(DiagCtxt::with_silent_emitter(fatal_note))
}
#[inline]
pub fn single_threaded(self) -> Self {
self.threads(1)
}
#[inline]
pub fn threads(mut self, threads: usize) -> Self {
self.opts_mut().threads = threads.into();
self
}
fn get_source_map(&mut self) -> Arc<SourceMap> {
self.get_globals().source_map.clone()
}
fn get_globals(&mut self) -> &mut SessionGlobals {
self.globals.get_or_insert_default()
}
fn opts_mut(&mut self) -> &mut Opts {
self.opts.get_or_insert_default()
}
#[track_caller]
pub fn build(mut self) -> Session {
let opts = self.opts.take();
let mut dcx = self.dcx.take().unwrap_or_else(|| {
opts.as_ref()
.map(DiagCtxt::from_opts)
.unwrap_or_else(|| panic!("either diagnostics context or options must be set"))
});
let sess = Session {
globals: Arc::new(match self.globals.take() {
Some(globals) => {
if let Some(sm) = dcx.source_map_mut() {
assert!(
Arc::ptr_eq(&globals.source_map, sm),
"session source map does not match the one in the diagnostics context"
);
}
globals
}
None => {
let sm = dcx.source_map_mut().cloned().unwrap_or_default();
SessionGlobals::new(sm)
}
}),
dcx,
opts: opts.unwrap_or_default(),
thread_pool: OnceLock::new(),
};
sess.reconfigure();
debug!(version = %solar_config::version::SEMVER_VERSION, "created new session");
sess
}
}
impl Session {
#[inline]
pub fn builder() -> SessionBuilder {
SessionBuilder::default()
}
pub fn new(opts: Opts) -> Self {
Self::builder().opts(opts).build()
}
pub fn infer_language(&mut self) {
if !self.opts.input.is_empty()
&& self.opts.input.iter().all(|arg| Path::new(arg).extension() == Some("yul".as_ref()))
{
self.opts.language = solar_config::Language::Yul;
}
}
pub fn validate(&self) -> crate::Result<()> {
let mut result = Ok(());
result = result.and(self.check_unique("emit", &self.opts.emit));
result
}
pub fn reconfigure(&self) {
'bp: {
let new_base_path = if self.opts.unstable.ui_testing {
None
} else if let Some(base_path) =
self.opts.base_path.clone().or_else(|| std::env::current_dir().ok())
&& let Ok(base_path) = self.source_map().file_loader().canonicalize_path(&base_path)
{
Some(base_path)
} else {
break 'bp;
};
self.source_map().set_base_path(new_base_path);
}
}
fn check_unique<T: Eq + std::hash::Hash + std::fmt::Display>(
&self,
name: &str,
list: &[T],
) -> crate::Result<()> {
let mut result = Ok(());
let mut seen = std::collections::HashSet::new();
for item in list {
if !seen.insert(item) {
let msg = format!("cannot specify `--{name} {item}` twice");
result = Err(self.dcx.err(msg).emit());
}
}
result
}
#[inline]
pub fn unstable(&self) -> &UnstableOpts {
&self.opts.unstable
}
#[inline]
pub fn emitted_diagnostics_result(
&self,
) -> Option<Result<EmittedDiagnostics, EmittedDiagnostics>> {
self.dcx.emitted_diagnostics_result()
}
#[inline]
pub fn emitted_diagnostics(&self) -> Option<EmittedDiagnostics> {
self.dcx.emitted_diagnostics()
}
#[inline]
pub fn emitted_errors(&self) -> Option<Result<(), EmittedDiagnostics>> {
self.dcx.emitted_errors()
}
#[inline]
pub fn source_map(&self) -> &SourceMap {
&self.globals.source_map
}
#[inline]
pub fn clone_source_map(&self) -> Arc<SourceMap> {
self.globals.source_map.clone()
}
#[inline]
pub fn stop_after(&self, stage: CompilerStage) -> bool {
self.opts.stop_after >= Some(stage)
}
#[inline]
pub fn threads(&self) -> usize {
self.opts.threads().get()
}
#[inline]
pub fn is_sequential(&self) -> bool {
self.threads() == 1
}
#[inline]
pub fn is_parallel(&self) -> bool {
!self.is_sequential()
}
#[inline]
pub fn do_emit(&self, output: CompilerOutput) -> bool {
self.opts.emit.contains(&output)
}
#[inline]
pub fn spawn(&self, f: impl FnOnce() + Send + 'static) {
if self.is_sequential() {
f();
} else {
rayon::spawn(f);
}
}
#[inline]
pub fn join<A, B, RA, RB>(&self, oper_a: A, oper_b: B) -> (RA, RB)
where
A: FnOnce() -> RA + Send,
B: FnOnce() -> RB + Send,
RA: Send,
RB: Send,
{
if self.is_sequential() { (oper_a(), oper_b()) } else { rayon::join(oper_a, oper_b) }
}
#[inline]
pub fn scope<'scope, OP, R>(&self, op: OP) -> R
where
OP: FnOnce(solar_data_structures::sync::Scope<'_, 'scope>) -> R + Send,
R: Send,
{
solar_data_structures::sync::scope(self.is_parallel(), op)
}
#[track_caller]
pub fn enter<R: Send>(&self, f: impl FnOnce() -> R + Send) -> R {
if in_rayon() {
if self.is_sequential() {
reentrant_log();
return self.enter_sequential(f);
}
if self.is_entered() {
return f();
}
}
self.enter_sequential(|| self.thread_pool().install(f))
}
#[inline]
#[track_caller]
pub fn enter_sequential<R>(&self, f: impl FnOnce() -> R) -> R {
self.globals.set(f)
}
#[inline]
#[cfg_attr(debug_assertions, track_caller)]
pub fn intern(&self, s: &str) -> Symbol {
self.globals.symbol_interner.intern(s)
}
#[inline]
#[cfg_attr(debug_assertions, track_caller)]
pub fn resolve_symbol(&self, s: Symbol) -> &str {
self.globals.symbol_interner.get(s)
}
#[inline]
#[cfg_attr(debug_assertions, track_caller)]
pub fn intern_byte_str(&self, s: &[u8]) -> ByteSymbol {
self.globals.symbol_interner.intern_byte_str(s)
}
#[inline]
#[cfg_attr(debug_assertions, track_caller)]
pub fn resolve_byte_str(&self, s: ByteSymbol) -> &[u8] {
self.globals.symbol_interner.get_byte_str(s)
}
pub fn is_entered(&self) -> bool {
SessionGlobals::try_with(|g| g.is_some_and(|g| g.maybe_eq(&self.globals)))
}
fn thread_pool(&self) -> &rayon::ThreadPool {
self.thread_pool.get_or_init(|| {
trace!(threads = self.threads(), "building rayon thread pool");
self.thread_pool_builder()
.spawn_handler(|thread| {
let mut builder = std::thread::Builder::new();
if let Some(name) = thread.name() {
builder = builder.name(name.to_string());
}
if let Some(size) = thread.stack_size() {
builder = builder.stack_size(size);
}
let globals = self.globals.clone();
builder.spawn(move || globals.set(|| thread.run()))?;
Ok(())
})
.build()
.unwrap_or_else(|e| self.handle_thread_pool_build_error(e))
})
}
fn thread_pool_builder(&self) -> rayon::ThreadPoolBuilder {
let threads = self.threads();
debug_assert!(threads > 0, "number of threads must already be resolved");
let mut builder = rayon::ThreadPoolBuilder::new()
.thread_name(|i| format!("solar-{i}"))
.num_threads(threads);
if threads == 1 {
builder = builder.use_current_thread();
}
builder
}
#[cold]
fn handle_thread_pool_build_error(&self, e: rayon::ThreadPoolBuildError) -> ! {
let mut err = self.dcx.fatal(format!("failed to build the rayon thread pool: {e}"));
if self.is_parallel() {
if SINGLE_THREADED_TARGET {
err = err.note("the current target might not support multi-threaded execution");
}
err = err.help("try running with `--threads 1` / `-j1` to disable parallelism");
}
err.emit()
}
}
fn reentrant_log() {
debug!(
"running in the current thread's rayon thread pool; \
this could cause panics later on if it was created without setting the session globals!"
);
}
#[inline]
fn in_rayon() -> bool {
rayon::current_thread_index().is_some()
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
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 = crate::Span::new(crate::BytePos(0), crate::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() {
SessionGlobals::with(|_globals| {});
let s = "hello";
let sym = crate::Symbol::intern(s);
assert_eq!(sym.as_str(), s);
}
#[track_caller]
fn cant_use_globals() {
std::panic::catch_unwind(|| use_globals()).unwrap_err();
}
#[test]
fn builder() {
let _ = Session::builder().with_stderr_emitter().build();
}
#[test]
fn not_builder() {
let _ = Session::new(Opts::default());
let _ = Session::default();
}
#[test]
#[should_panic = "session source map does not match the one in the diagnostics context"]
fn sm_mismatch() {
let sm1 = Arc::<SourceMap>::default();
let sm2 = Arc::<SourceMap>::default();
assert!(!Arc::ptr_eq(&sm1, &sm2));
Session::builder().source_map(sm1).dcx(DiagCtxt::with_stderr_emitter(Some(sm2))).build();
}
#[test]
#[should_panic = "either diagnostics context or options must be set"]
fn no_dcx() {
Session::builder().build();
}
#[test]
fn dcx() {
let _ = Session::builder().dcx(DiagCtxt::with_stderr_emitter(None)).build();
let _ =
Session::builder().dcx(DiagCtxt::with_stderr_emitter(Some(Default::default()))).build();
}
#[test]
fn local() {
let sess = Session::builder().with_stderr_emitter().build();
assert!(sess.emitted_diagnostics().is_none());
assert!(sess.emitted_errors().is_none());
let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
sess.dcx.err("test").emit();
let err = sess.dcx.emitted_errors().unwrap().unwrap_err();
let err = Box::new(err) as Box<dyn std::error::Error>;
assert!(err.to_string().contains("error: test"), "{err:?}");
}
#[test]
fn enter() {
crate::enter(|| {
use_globals_no_sm();
cant_use_globals();
});
let sess = enter_tests_session();
sess.enter(|| use_globals());
assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
assert!(sess.dcx.emitted_errors().unwrap().is_ok());
sess.enter(|| {
use_globals();
sess.enter(|| use_globals());
use_globals();
});
assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
assert!(sess.dcx.emitted_errors().unwrap().is_ok());
sess.enter_sequential(|| {
use_globals();
sess.enter(|| {
use_globals_parallel(&sess);
sess.enter(|| use_globals_parallel(&sess));
use_globals_parallel(&sess);
});
use_globals();
});
assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
assert!(sess.dcx.emitted_errors().unwrap().is_ok());
}
#[test]
fn enter_diags() {
let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
assert!(sess.dcx.emitted_errors().unwrap().is_ok());
sess.enter(|| {
sess.dcx.err("test1").emit();
assert!(sess.dcx.emitted_errors().unwrap().is_err());
});
assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test1"));
sess.enter(|| {
sess.dcx.err("test2").emit();
assert!(sess.dcx.emitted_errors().unwrap().is_err());
});
assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test1"));
assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test2"));
}
#[test]
fn enter_thread_pool() {
let sess = enter_tests_session();
assert!(!in_rayon());
sess.enter(|| {
assert!(in_rayon());
sess.enter(|| {
assert!(in_rayon());
});
assert!(in_rayon());
});
sess.enter_sequential(|| {
assert!(!in_rayon());
sess.enter(|| {
assert!(in_rayon());
});
assert!(!in_rayon());
sess.enter_sequential(|| {
assert!(!in_rayon());
});
assert!(!in_rayon());
});
assert!(!in_rayon());
let pool = rayon::ThreadPoolBuilder::new().build().unwrap();
pool.install(|| {
assert!(in_rayon());
cant_use_globals();
sess.enter(|| use_globals_parallel(&sess));
assert!(in_rayon());
cant_use_globals();
});
assert!(!in_rayon());
}
#[test]
fn enter_different_nested_sessions() {
let sess1 = enter_tests_session();
let sess2 = enter_tests_session();
assert!(!sess1.globals.maybe_eq(&sess2.globals));
sess1.enter(|| {
SessionGlobals::with(|g| assert!(g.maybe_eq(&sess1.globals)));
use_globals();
sess2.enter(|| {
SessionGlobals::with(|g| assert!(g.maybe_eq(&sess2.globals)));
use_globals();
});
use_globals();
});
}
#[test]
fn set_opts() {
let _ = Session::builder()
.with_test_emitter()
.opts(Opts {
evm_version: solar_config::EvmVersion::Berlin,
unstable: UnstableOpts { ast_stats: false, ..Default::default() },
..Default::default()
})
.build();
}
}