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}