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