1use crate::{
2    ByteSymbol, ColorChoice, SessionGlobals, SourceMap, Symbol,
3    diagnostics::{DiagCtxt, EmittedDiagnostics},
4};
5use solar_config::{CompilerOutput, CompilerStage, Opts, SINGLE_THREADED_TARGET, UnstableOpts};
6use std::{
7    fmt,
8    path::Path,
9    sync::{Arc, OnceLock},
10};
11
12pub struct Session {
14    pub opts: Opts,
16
17    pub dcx: DiagCtxt,
19    globals: Arc<SessionGlobals>,
21    thread_pool: OnceLock<rayon::ThreadPool>,
24}
25
26impl Default for Session {
27    fn default() -> Self {
28        Self::new(Opts::default())
29    }
30}
31
32impl fmt::Debug for Session {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        f.debug_struct("Session")
35            .field("opts", &self.opts)
36            .field("dcx", &self.dcx)
37            .finish_non_exhaustive()
38    }
39}
40
41#[derive(Default)]
43#[must_use = "builders don't do anything unless you call `build`"]
44pub struct SessionBuilder {
45    dcx: Option<DiagCtxt>,
46    globals: Option<SessionGlobals>,
47    opts: Option<Opts>,
48}
49
50impl SessionBuilder {
51    pub fn dcx(mut self, dcx: DiagCtxt) -> Self {
57        self.dcx = Some(dcx);
58        self
59    }
60
61    pub fn source_map(mut self, source_map: Arc<SourceMap>) -> Self {
63        self.get_globals().source_map = source_map;
64        self
65    }
66
67    pub fn opts(mut self, opts: Opts) -> Self {
69        self.opts = Some(opts);
70        self
71    }
72
73    #[inline]
75    pub fn with_test_emitter(mut self) -> Self {
76        let sm = self.get_source_map();
77        self.dcx(DiagCtxt::with_test_emitter(Some(sm)))
78    }
79
80    #[inline]
82    pub fn with_stderr_emitter(self) -> Self {
83        self.with_stderr_emitter_and_color(ColorChoice::Auto)
84    }
85
86    #[inline]
88    pub fn with_stderr_emitter_and_color(mut self, color_choice: ColorChoice) -> Self {
89        let sm = self.get_source_map();
90        self.dcx(DiagCtxt::with_stderr_emitter_and_color(Some(sm), color_choice))
91    }
92
93    #[inline]
95    pub fn with_buffer_emitter(mut self, color_choice: ColorChoice) -> Self {
96        let sm = self.get_source_map();
97        self.dcx(DiagCtxt::with_buffer_emitter(Some(sm), color_choice))
98    }
99
100    #[inline]
102    pub fn with_silent_emitter(self, fatal_note: Option<String>) -> Self {
103        self.dcx(DiagCtxt::with_silent_emitter(fatal_note))
104    }
105
106    #[inline]
108    pub fn single_threaded(self) -> Self {
109        self.threads(1)
110    }
111
112    #[inline]
115    pub fn threads(mut self, threads: usize) -> Self {
116        self.opts_mut().threads = threads.into();
117        self
118    }
119
120    fn get_source_map(&mut self) -> Arc<SourceMap> {
122        self.get_globals().source_map.clone()
123    }
124
125    fn get_globals(&mut self) -> &mut SessionGlobals {
126        self.globals.get_or_insert_default()
127    }
128
129    fn opts_mut(&mut self) -> &mut Opts {
131        self.opts.get_or_insert_default()
132    }
133
134    #[track_caller]
146    pub fn build(mut self) -> Session {
147        let opts = self.opts.take();
148        let mut dcx = self.dcx.take().unwrap_or_else(|| {
149            opts.as_ref()
150                .map(DiagCtxt::from_opts)
151                .unwrap_or_else(|| panic!("either diagnostics context or options must be set"))
152        });
153        let sess = Session {
154            globals: Arc::new(match self.globals.take() {
155                Some(globals) => {
156                    if let Some(sm) = dcx.source_map_mut() {
158                        assert!(
159                            Arc::ptr_eq(&globals.source_map, sm),
160                            "session source map does not match the one in the diagnostics context"
161                        );
162                    }
163                    globals
164                }
165                None => {
166                    let sm = dcx.source_map_mut().cloned().unwrap_or_default();
168                    SessionGlobals::new(sm)
169                }
170            }),
171            dcx,
172            opts: opts.unwrap_or_default(),
173            thread_pool: OnceLock::new(),
174        };
175        sess.reconfigure();
176        debug!(version = %solar_config::version::SEMVER_VERSION, "created new session");
177        sess
178    }
179}
180
181impl Session {
182    #[inline]
184    pub fn builder() -> SessionBuilder {
185        SessionBuilder::default()
186    }
187
188    pub fn new(opts: Opts) -> Self {
192        Self::builder().opts(opts).build()
193    }
194
195    pub fn infer_language(&mut self) {
197        if !self.opts.input.is_empty()
198            && self.opts.input.iter().all(|arg| Path::new(arg).extension() == Some("yul".as_ref()))
199        {
200            self.opts.language = solar_config::Language::Yul;
201        }
202    }
203
204    pub fn validate(&self) -> crate::Result<()> {
206        let mut result = Ok(());
207        result = result.and(self.check_unique("emit", &self.opts.emit));
208        result
209    }
210
211    pub fn reconfigure(&self) {
215        'bp: {
216            let new_base_path = if self.opts.unstable.ui_testing {
217                None
219            } else if let Some(base_path) =
220                self.opts.base_path.clone().or_else(|| std::env::current_dir().ok())
221                && let Ok(base_path) = self.source_map().file_loader().canonicalize_path(&base_path)
222            {
223                Some(base_path)
224            } else {
225                break 'bp;
226            };
227            self.source_map().set_base_path(new_base_path);
228        }
229    }
230
231    fn check_unique<T: Eq + std::hash::Hash + std::fmt::Display>(
232        &self,
233        name: &str,
234        list: &[T],
235    ) -> crate::Result<()> {
236        let mut result = Ok(());
237        let mut seen = std::collections::HashSet::new();
238        for item in list {
239            if !seen.insert(item) {
240                let msg = format!("cannot specify `--{name} {item}` twice");
241                result = Err(self.dcx.err(msg).emit());
242            }
243        }
244        result
245    }
246
247    #[inline]
249    pub fn unstable(&self) -> &UnstableOpts {
250        &self.opts.unstable
251    }
252
253    #[inline]
271    pub fn emitted_diagnostics_result(
272        &self,
273    ) -> Option<Result<EmittedDiagnostics, EmittedDiagnostics>> {
274        self.dcx.emitted_diagnostics_result()
275    }
276
277    #[inline]
282    pub fn emitted_diagnostics(&self) -> Option<EmittedDiagnostics> {
283        self.dcx.emitted_diagnostics()
284    }
285
286    #[inline]
291    pub fn emitted_errors(&self) -> Option<Result<(), EmittedDiagnostics>> {
292        self.dcx.emitted_errors()
293    }
294
295    #[inline]
297    pub fn source_map(&self) -> &SourceMap {
298        &self.globals.source_map
299    }
300
301    #[inline]
303    pub fn clone_source_map(&self) -> Arc<SourceMap> {
304        self.globals.source_map.clone()
305    }
306
307    #[inline]
309    pub fn stop_after(&self, stage: CompilerStage) -> bool {
310        self.opts.stop_after >= Some(stage)
311    }
312
313    #[inline]
315    pub fn threads(&self) -> usize {
316        self.opts.threads().get()
317    }
318
319    #[inline]
321    pub fn is_sequential(&self) -> bool {
322        self.threads() == 1
323    }
324
325    #[inline]
327    pub fn is_parallel(&self) -> bool {
328        !self.is_sequential()
329    }
330
331    #[inline]
333    pub fn do_emit(&self, output: CompilerOutput) -> bool {
334        self.opts.emit.contains(&output)
335    }
336
337    #[inline]
343    pub fn spawn(&self, f: impl FnOnce() + Send + 'static) {
344        if self.is_sequential() {
345            f();
346        } else {
347            rayon::spawn(f);
348        }
349    }
350
351    #[inline]
357    pub fn join<A, B, RA, RB>(&self, oper_a: A, oper_b: B) -> (RA, RB)
358    where
359        A: FnOnce() -> RA + Send,
360        B: FnOnce() -> RB + Send,
361        RA: Send,
362        RB: Send,
363    {
364        if self.is_sequential() { (oper_a(), oper_b()) } else { rayon::join(oper_a, oper_b) }
365    }
366
367    #[inline]
371    pub fn scope<'scope, OP, R>(&self, op: OP) -> R
372    where
373        OP: FnOnce(solar_data_structures::sync::Scope<'_, 'scope>) -> R + Send,
374        R: Send,
375    {
376        solar_data_structures::sync::scope(self.is_parallel(), op)
377    }
378
379    #[track_caller]
384    pub fn enter<R: Send>(&self, f: impl FnOnce() -> R + Send) -> R {
385        if in_rayon() {
386            if self.is_sequential() {
388                reentrant_log();
389                return self.enter_sequential(f);
390            }
391            if self.is_entered() {
393                return f();
395            }
396        }
397
398        self.enter_sequential(|| self.thread_pool().install(f))
399    }
400
401    #[inline]
409    #[track_caller]
410    pub fn enter_sequential<R>(&self, f: impl FnOnce() -> R) -> R {
411        self.globals.set(f)
412    }
413
414    #[inline]
418    #[cfg_attr(debug_assertions, track_caller)]
419    pub fn intern(&self, s: &str) -> Symbol {
420        self.globals.symbol_interner.intern(s)
421    }
422
423    #[inline]
427    #[cfg_attr(debug_assertions, track_caller)]
428    pub fn resolve_symbol(&self, s: Symbol) -> &str {
429        self.globals.symbol_interner.get(s)
430    }
431
432    #[inline]
436    #[cfg_attr(debug_assertions, track_caller)]
437    pub fn intern_byte_str(&self, s: &[u8]) -> ByteSymbol {
438        self.globals.symbol_interner.intern_byte_str(s)
439    }
440
441    #[inline]
445    #[cfg_attr(debug_assertions, track_caller)]
446    pub fn resolve_byte_str(&self, s: ByteSymbol) -> &[u8] {
447        self.globals.symbol_interner.get_byte_str(s)
448    }
449
450    pub fn is_entered(&self) -> bool {
452        SessionGlobals::try_with(|g| g.is_some_and(|g| g.maybe_eq(&self.globals)))
453    }
454
455    fn thread_pool(&self) -> &rayon::ThreadPool {
456        self.thread_pool.get_or_init(|| {
457            trace!(threads = self.threads(), "building rayon thread pool");
458            self.thread_pool_builder()
459                .spawn_handler(|thread| {
460                    let mut builder = std::thread::Builder::new();
461                    if let Some(name) = thread.name() {
462                        builder = builder.name(name.to_string());
463                    }
464                    if let Some(size) = thread.stack_size() {
465                        builder = builder.stack_size(size);
466                    }
467                    let globals = self.globals.clone();
468                    builder.spawn(move || globals.set(|| thread.run()))?;
469                    Ok(())
470                })
471                .build()
472                .unwrap_or_else(|e| self.handle_thread_pool_build_error(e))
473        })
474    }
475
476    fn thread_pool_builder(&self) -> rayon::ThreadPoolBuilder {
477        let threads = self.threads();
478        debug_assert!(threads > 0, "number of threads must already be resolved");
479        let mut builder = rayon::ThreadPoolBuilder::new()
480            .thread_name(|i| format!("solar-{i}"))
481            .num_threads(threads);
482        if threads == 1 {
485            builder = builder.use_current_thread();
486        }
487        builder
488    }
489
490    #[cold]
491    fn handle_thread_pool_build_error(&self, e: rayon::ThreadPoolBuildError) -> ! {
492        let mut err = self.dcx.fatal(format!("failed to build the rayon thread pool: {e}"));
493        if self.is_parallel() {
494            if SINGLE_THREADED_TARGET {
495                err = err.note("the current target might not support multi-threaded execution");
496            }
497            err = err.help("try running with `--threads 1` / `-j1` to disable parallelism");
498        }
499        err.emit()
500    }
501}
502
503fn reentrant_log() {
504    debug!(
505        "running in the current thread's rayon thread pool; \
506         this could cause panics later on if it was created without setting the session globals!"
507    );
508}
509
510#[inline]
511fn in_rayon() -> bool {
512    rayon::current_thread_index().is_some()
513}
514
515#[cfg(test)]
516mod tests {
517    use super::*;
518    use std::path::PathBuf;
519
520    fn enter_tests_session() -> Session {
522        let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
523        sess.source_map().new_source_file(PathBuf::from("test"), "abcd").unwrap();
524        sess
525    }
526
527    #[track_caller]
528    fn use_globals_parallel(sess: &Session) {
529        use rayon::prelude::*;
530
531        use_globals();
532        sess.spawn(|| use_globals());
533        sess.join(|| use_globals(), || use_globals());
534        [1, 2, 3].par_iter().for_each(|_| use_globals());
535        use_globals();
536    }
537
538    #[track_caller]
539    fn use_globals() {
540        use_globals_no_sm();
541
542        let span = crate::Span::new(crate::BytePos(0), crate::BytePos(1));
543        assert_eq!(format!("{span:?}"), "test:1:1: 1:2");
544        assert_eq!(format!("{span:#?}"), "test:1:1: 1:2");
545    }
546
547    #[track_caller]
548    fn use_globals_no_sm() {
549        SessionGlobals::with(|_globals| {});
550
551        let s = "hello";
552        let sym = crate::Symbol::intern(s);
553        assert_eq!(sym.as_str(), s);
554    }
555
556    #[track_caller]
557    fn cant_use_globals() {
558        std::panic::catch_unwind(|| use_globals()).unwrap_err();
559    }
560
561    #[test]
562    fn builder() {
563        let _ = Session::builder().with_stderr_emitter().build();
564    }
565
566    #[test]
567    fn not_builder() {
568        let _ = Session::new(Opts::default());
569        let _ = Session::default();
570    }
571
572    #[test]
573    #[should_panic = "session source map does not match the one in the diagnostics context"]
574    fn sm_mismatch() {
575        let sm1 = Arc::<SourceMap>::default();
576        let sm2 = Arc::<SourceMap>::default();
577        assert!(!Arc::ptr_eq(&sm1, &sm2));
578        Session::builder().source_map(sm1).dcx(DiagCtxt::with_stderr_emitter(Some(sm2))).build();
579    }
580
581    #[test]
582    #[should_panic = "either diagnostics context or options must be set"]
583    fn no_dcx() {
584        Session::builder().build();
585    }
586
587    #[test]
588    fn dcx() {
589        let _ = Session::builder().dcx(DiagCtxt::with_stderr_emitter(None)).build();
590        let _ =
591            Session::builder().dcx(DiagCtxt::with_stderr_emitter(Some(Default::default()))).build();
592    }
593
594    #[test]
595    fn local() {
596        let sess = Session::builder().with_stderr_emitter().build();
597        assert!(sess.emitted_diagnostics().is_none());
598        assert!(sess.emitted_errors().is_none());
599
600        let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
601        sess.dcx.err("test").emit();
602        let err = sess.dcx.emitted_errors().unwrap().unwrap_err();
603        let err = Box::new(err) as Box<dyn std::error::Error>;
604        assert!(err.to_string().contains("error: test"), "{err:?}");
605    }
606
607    #[test]
608    fn enter() {
609        crate::enter(|| {
610            use_globals_no_sm();
611            cant_use_globals();
612        });
613
614        let sess = enter_tests_session();
615        sess.enter(|| use_globals());
616        assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
617        assert!(sess.dcx.emitted_errors().unwrap().is_ok());
618        sess.enter(|| {
619            use_globals();
620            sess.enter(|| use_globals());
621            use_globals();
622        });
623        assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
624        assert!(sess.dcx.emitted_errors().unwrap().is_ok());
625
626        sess.enter_sequential(|| {
627            use_globals();
628            sess.enter(|| {
629                use_globals_parallel(&sess);
630                sess.enter(|| use_globals_parallel(&sess));
631                use_globals_parallel(&sess);
632            });
633            use_globals();
634        });
635        assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
636        assert!(sess.dcx.emitted_errors().unwrap().is_ok());
637    }
638
639    #[test]
640    fn enter_diags() {
641        let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
642        assert!(sess.dcx.emitted_errors().unwrap().is_ok());
643        sess.enter(|| {
644            sess.dcx.err("test1").emit();
645            assert!(sess.dcx.emitted_errors().unwrap().is_err());
646        });
647        assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test1"));
648        sess.enter(|| {
649            sess.dcx.err("test2").emit();
650            assert!(sess.dcx.emitted_errors().unwrap().is_err());
651        });
652        assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test1"));
653        assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test2"));
654    }
655
656    #[test]
657    fn enter_thread_pool() {
658        let sess = enter_tests_session();
659
660        assert!(!in_rayon());
661        sess.enter(|| {
662            assert!(in_rayon());
663            sess.enter(|| {
664                assert!(in_rayon());
665            });
666            assert!(in_rayon());
667        });
668        sess.enter_sequential(|| {
669            assert!(!in_rayon());
670            sess.enter(|| {
671                assert!(in_rayon());
672            });
673            assert!(!in_rayon());
674            sess.enter_sequential(|| {
675                assert!(!in_rayon());
676            });
677            assert!(!in_rayon());
678        });
679        assert!(!in_rayon());
680
681        let pool = rayon::ThreadPoolBuilder::new().build().unwrap();
682        pool.install(|| {
683            assert!(in_rayon());
684            cant_use_globals();
685            sess.enter(|| use_globals_parallel(&sess));
686            assert!(in_rayon());
687            cant_use_globals();
688        });
689        assert!(!in_rayon());
690    }
691
692    #[test]
693    fn enter_different_nested_sessions() {
694        let sess1 = enter_tests_session();
695        let sess2 = enter_tests_session();
696        assert!(!sess1.globals.maybe_eq(&sess2.globals));
697        sess1.enter(|| {
698            SessionGlobals::with(|g| assert!(g.maybe_eq(&sess1.globals)));
699            use_globals();
700            sess2.enter(|| {
701                SessionGlobals::with(|g| assert!(g.maybe_eq(&sess2.globals)));
702                use_globals();
703            });
704            use_globals();
705        });
706    }
707
708    #[test]
709    fn set_opts() {
710        let _ = Session::builder()
711            .with_test_emitter()
712            .opts(Opts {
713                evm_version: solar_config::EvmVersion::Berlin,
714                unstable: UnstableOpts { ast_stats: false, ..Default::default() },
715                ..Default::default()
716            })
717            .build();
718    }
719}