solar_interface/
session.rs1use crate::{
2 ColorChoice, SessionGlobals, SourceMap,
3 diagnostics::{DiagCtxt, EmittedDiagnostics},
4};
5use solar_config::{CompilerOutput, CompilerStage, Opts, SINGLE_THREADED_TARGET, UnstableOpts};
6use std::{
7 path::Path,
8 sync::{Arc, OnceLock},
9};
10
11pub struct Session {
13 pub opts: Opts,
15
16 pub dcx: DiagCtxt,
18 globals: Arc<SessionGlobals>,
20 thread_pool: OnceLock<rayon::ThreadPool>,
23}
24
25impl Default for Session {
26 fn default() -> Self {
27 Self::new(Opts::default())
28 }
29}
30
31#[derive(Default)]
33#[must_use = "builders don't do anything unless you call `build`"]
34pub struct SessionBuilder {
35 dcx: Option<DiagCtxt>,
36 globals: Option<SessionGlobals>,
37 opts: Option<Opts>,
38}
39
40impl SessionBuilder {
41 pub fn dcx(mut self, dcx: DiagCtxt) -> Self {
47 self.dcx = Some(dcx);
48 self
49 }
50
51 pub fn source_map(mut self, source_map: Arc<SourceMap>) -> Self {
53 self.get_globals().source_map = source_map;
54 self
55 }
56
57 pub fn opts(mut self, opts: Opts) -> Self {
59 self.opts = Some(opts);
60 self
61 }
62
63 #[inline]
65 pub fn with_test_emitter(mut self) -> Self {
66 let sm = self.get_source_map();
67 self.dcx(DiagCtxt::with_test_emitter(Some(sm)))
68 }
69
70 #[inline]
72 pub fn with_stderr_emitter(self) -> Self {
73 self.with_stderr_emitter_and_color(ColorChoice::Auto)
74 }
75
76 #[inline]
78 pub fn with_stderr_emitter_and_color(mut self, color_choice: ColorChoice) -> Self {
79 let sm = self.get_source_map();
80 self.dcx(DiagCtxt::with_stderr_emitter_and_color(Some(sm), color_choice))
81 }
82
83 #[inline]
85 pub fn with_buffer_emitter(mut self, color_choice: ColorChoice) -> Self {
86 let sm = self.get_source_map();
87 self.dcx(DiagCtxt::with_buffer_emitter(Some(sm), color_choice))
88 }
89
90 #[inline]
92 pub fn with_silent_emitter(self, fatal_note: Option<String>) -> Self {
93 self.dcx(DiagCtxt::with_silent_emitter(fatal_note))
94 }
95
96 #[inline]
98 pub fn single_threaded(self) -> Self {
99 self.threads(1)
100 }
101
102 #[inline]
105 pub fn threads(mut self, threads: usize) -> Self {
106 self.opts_mut().threads = threads.into();
107 self
108 }
109
110 fn get_source_map(&mut self) -> Arc<SourceMap> {
112 self.get_globals().source_map.clone()
113 }
114
115 fn get_globals(&mut self) -> &mut SessionGlobals {
116 self.globals.get_or_insert_default()
117 }
118
119 fn opts_mut(&mut self) -> &mut Opts {
121 self.opts.get_or_insert_default()
122 }
123
124 #[track_caller]
136 pub fn build(mut self) -> Session {
137 let opts = self.opts.take();
138 let mut dcx = self.dcx.take().unwrap_or_else(|| {
139 opts.as_ref()
140 .map(DiagCtxt::from_opts)
141 .unwrap_or_else(|| panic!("either diagnostics context or options must be set"))
142 });
143 let sess = Session {
144 globals: Arc::new(match self.globals.take() {
145 Some(globals) => {
146 if let Some(sm) = dcx.source_map_mut() {
148 assert!(
149 Arc::ptr_eq(&globals.source_map, sm),
150 "session source map does not match the one in the diagnostics context"
151 );
152 }
153 globals
154 }
155 None => {
156 let sm = dcx.source_map_mut().cloned().unwrap_or_default();
158 SessionGlobals::new(sm)
159 }
160 }),
161 dcx,
162 opts: opts.unwrap_or_default(),
163 thread_pool: OnceLock::new(),
164 };
165 sess.reconfigure();
166 debug!(version = %solar_config::version::SEMVER_VERSION, "created new session");
167 sess
168 }
169}
170
171impl Session {
172 #[inline]
174 pub fn builder() -> SessionBuilder {
175 SessionBuilder::default()
176 }
177
178 pub fn new(opts: Opts) -> Self {
182 Self::builder().opts(opts).build()
183 }
184
185 pub fn infer_language(&mut self) {
187 if !self.opts.input.is_empty()
188 && self.opts.input.iter().all(|arg| Path::new(arg).extension() == Some("yul".as_ref()))
189 {
190 self.opts.language = solar_config::Language::Yul;
191 }
192 }
193
194 pub fn validate(&self) -> crate::Result<()> {
196 let mut result = Ok(());
197 result = result.and(self.check_unique("emit", &self.opts.emit));
198 result
199 }
200
201 pub fn reconfigure(&self) {
205 'bp: {
206 let new_base_path = if self.opts.unstable.ui_testing {
207 None
209 } else if let Some(base_path) =
210 self.opts.base_path.clone().or_else(|| std::env::current_dir().ok())
211 && let Ok(base_path) = self.source_map().file_loader().canonicalize_path(&base_path)
212 {
213 Some(base_path)
214 } else {
215 break 'bp;
216 };
217 self.source_map().set_base_path(new_base_path);
218 }
219 }
220
221 fn check_unique<T: Eq + std::hash::Hash + std::fmt::Display>(
222 &self,
223 name: &str,
224 list: &[T],
225 ) -> crate::Result<()> {
226 let mut result = Ok(());
227 let mut seen = std::collections::HashSet::new();
228 for item in list {
229 if !seen.insert(item) {
230 let msg = format!("cannot specify `--{name} {item}` twice");
231 result = Err(self.dcx.err(msg).emit());
232 }
233 }
234 result
235 }
236
237 #[inline]
239 pub fn unstable(&self) -> &UnstableOpts {
240 &self.opts.unstable
241 }
242
243 #[inline]
261 pub fn emitted_diagnostics_result(
262 &self,
263 ) -> Option<Result<EmittedDiagnostics, EmittedDiagnostics>> {
264 self.dcx.emitted_diagnostics_result()
265 }
266
267 #[inline]
272 pub fn emitted_diagnostics(&self) -> Option<EmittedDiagnostics> {
273 self.dcx.emitted_diagnostics()
274 }
275
276 #[inline]
281 pub fn emitted_errors(&self) -> Option<Result<(), EmittedDiagnostics>> {
282 self.dcx.emitted_errors()
283 }
284
285 #[inline]
287 pub fn source_map(&self) -> &SourceMap {
288 &self.globals.source_map
289 }
290
291 #[inline]
293 pub fn clone_source_map(&self) -> Arc<SourceMap> {
294 self.globals.source_map.clone()
295 }
296
297 #[inline]
299 pub fn stop_after(&self, stage: CompilerStage) -> bool {
300 self.opts.stop_after >= Some(stage)
301 }
302
303 #[inline]
305 pub fn threads(&self) -> usize {
306 self.opts.threads().get()
307 }
308
309 #[inline]
311 pub fn is_sequential(&self) -> bool {
312 self.threads() == 1
313 }
314
315 #[inline]
317 pub fn is_parallel(&self) -> bool {
318 !self.is_sequential()
319 }
320
321 #[inline]
323 pub fn do_emit(&self, output: CompilerOutput) -> bool {
324 self.opts.emit.contains(&output)
325 }
326
327 #[inline]
333 pub fn spawn(&self, f: impl FnOnce() + Send + 'static) {
334 if self.is_sequential() {
335 f();
336 } else {
337 rayon::spawn(f);
338 }
339 }
340
341 #[inline]
347 pub fn join<A, B, RA, RB>(&self, oper_a: A, oper_b: B) -> (RA, RB)
348 where
349 A: FnOnce() -> RA + Send,
350 B: FnOnce() -> RB + Send,
351 RA: Send,
352 RB: Send,
353 {
354 if self.is_sequential() { (oper_a(), oper_b()) } else { rayon::join(oper_a, oper_b) }
355 }
356
357 #[inline]
361 pub fn scope<'scope, OP, R>(&self, op: OP) -> R
362 where
363 OP: FnOnce(solar_data_structures::sync::Scope<'_, 'scope>) -> R + Send,
364 R: Send,
365 {
366 solar_data_structures::sync::scope(self.is_parallel(), op)
367 }
368
369 #[track_caller]
374 pub fn enter<R: Send>(&self, f: impl FnOnce() -> R + Send) -> R {
375 if in_rayon() {
376 if self.is_sequential() {
378 reentrant_log();
379 return self.enter_sequential(f);
380 }
381 if self.is_set() {
383 return f();
385 }
386 }
387
388 self.enter_sequential(|| self.thread_pool().install(f))
389 }
390
391 #[inline]
399 #[track_caller]
400 pub fn enter_sequential<R>(&self, f: impl FnOnce() -> R) -> R {
401 self.globals.set(f)
402 }
403
404 fn is_set(&self) -> bool {
406 SessionGlobals::try_with(|g| g.is_some_and(|g| g.maybe_eq(&self.globals)))
407 }
408
409 fn thread_pool(&self) -> &rayon::ThreadPool {
410 self.thread_pool.get_or_init(|| {
411 trace!(threads = self.threads(), "building rayon thread pool");
412 self.thread_pool_builder()
413 .spawn_handler(|thread| {
414 let mut builder = std::thread::Builder::new();
415 if let Some(name) = thread.name() {
416 builder = builder.name(name.to_string());
417 }
418 if let Some(size) = thread.stack_size() {
419 builder = builder.stack_size(size);
420 }
421 let globals = self.globals.clone();
422 builder.spawn(move || globals.set(|| thread.run()))?;
423 Ok(())
424 })
425 .build()
426 .unwrap_or_else(|e| self.handle_thread_pool_build_error(e))
427 })
428 }
429
430 fn thread_pool_builder(&self) -> rayon::ThreadPoolBuilder {
431 let threads = self.threads();
432 debug_assert!(threads > 0, "number of threads must already be resolved");
433 let mut builder = rayon::ThreadPoolBuilder::new()
434 .thread_name(|i| format!("solar-{i}"))
435 .num_threads(threads);
436 if threads == 1 {
439 builder = builder.use_current_thread();
440 }
441 builder
442 }
443
444 #[cold]
445 fn handle_thread_pool_build_error(&self, e: rayon::ThreadPoolBuildError) -> ! {
446 let mut err = self.dcx.fatal(format!("failed to build the rayon thread pool: {e}"));
447 if self.is_parallel() {
448 if SINGLE_THREADED_TARGET {
449 err = err.note("the current target might not support multi-threaded execution");
450 }
451 err = err.help("try running with `--threads 1` / `-j1` to disable parallelism");
452 }
453 err.emit()
454 }
455}
456
457fn reentrant_log() {
458 debug!(
459 "running in the current thread's rayon thread pool; \
460 this could cause panics later on if it was created without setting the session globals!"
461 );
462}
463
464#[inline]
465fn in_rayon() -> bool {
466 rayon::current_thread_index().is_some()
467}
468
469#[cfg(test)]
470mod tests {
471 use super::*;
472 use std::path::PathBuf;
473
474 fn enter_tests_session() -> Session {
476 let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
477 sess.source_map().new_source_file(PathBuf::from("test"), "abcd").unwrap();
478 sess
479 }
480
481 #[track_caller]
482 fn use_globals_parallel(sess: &Session) {
483 use rayon::prelude::*;
484
485 use_globals();
486 sess.spawn(|| use_globals());
487 sess.join(|| use_globals(), || use_globals());
488 [1, 2, 3].par_iter().for_each(|_| use_globals());
489 use_globals();
490 }
491
492 #[track_caller]
493 fn use_globals() {
494 use_globals_no_sm();
495
496 let span = crate::Span::new(crate::BytePos(0), crate::BytePos(1));
497 assert_eq!(format!("{span:?}"), "test:1:1: 1:2");
498 assert_eq!(format!("{span:#?}"), "test:1:1: 1:2");
499 }
500
501 #[track_caller]
502 fn use_globals_no_sm() {
503 SessionGlobals::with(|_globals| {});
504
505 let s = "hello";
506 let sym = crate::Symbol::intern(s);
507 assert_eq!(sym.as_str(), s);
508 }
509
510 #[track_caller]
511 fn cant_use_globals() {
512 std::panic::catch_unwind(|| use_globals()).unwrap_err();
513 }
514
515 #[test]
516 fn builder() {
517 let _ = Session::builder().with_stderr_emitter().build();
518 }
519
520 #[test]
521 fn not_builder() {
522 let _ = Session::new(Opts::default());
523 let _ = Session::default();
524 }
525
526 #[test]
527 #[should_panic = "session source map does not match the one in the diagnostics context"]
528 fn sm_mismatch() {
529 let sm1 = Arc::<SourceMap>::default();
530 let sm2 = Arc::<SourceMap>::default();
531 assert!(!Arc::ptr_eq(&sm1, &sm2));
532 Session::builder().source_map(sm1).dcx(DiagCtxt::with_stderr_emitter(Some(sm2))).build();
533 }
534
535 #[test]
536 #[should_panic = "either diagnostics context or options must be set"]
537 fn no_dcx() {
538 Session::builder().build();
539 }
540
541 #[test]
542 fn dcx() {
543 let _ = Session::builder().dcx(DiagCtxt::with_stderr_emitter(None)).build();
544 let _ =
545 Session::builder().dcx(DiagCtxt::with_stderr_emitter(Some(Default::default()))).build();
546 }
547
548 #[test]
549 fn local() {
550 let sess = Session::builder().with_stderr_emitter().build();
551 assert!(sess.emitted_diagnostics().is_none());
552 assert!(sess.emitted_errors().is_none());
553
554 let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
555 sess.dcx.err("test").emit();
556 let err = sess.dcx.emitted_errors().unwrap().unwrap_err();
557 let err = Box::new(err) as Box<dyn std::error::Error>;
558 assert!(err.to_string().contains("error: test"), "{err:?}");
559 }
560
561 #[test]
562 fn enter() {
563 crate::enter(|| {
564 use_globals_no_sm();
565 cant_use_globals();
566 });
567
568 let sess = enter_tests_session();
569 sess.enter(|| use_globals());
570 assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
571 assert!(sess.dcx.emitted_errors().unwrap().is_ok());
572 sess.enter(|| {
573 use_globals();
574 sess.enter(|| use_globals());
575 use_globals();
576 });
577 assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
578 assert!(sess.dcx.emitted_errors().unwrap().is_ok());
579
580 sess.enter_sequential(|| {
581 use_globals();
582 sess.enter(|| {
583 use_globals_parallel(&sess);
584 sess.enter(|| use_globals_parallel(&sess));
585 use_globals_parallel(&sess);
586 });
587 use_globals();
588 });
589 assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
590 assert!(sess.dcx.emitted_errors().unwrap().is_ok());
591 }
592
593 #[test]
594 fn enter_diags() {
595 let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
596 assert!(sess.dcx.emitted_errors().unwrap().is_ok());
597 sess.enter(|| {
598 sess.dcx.err("test1").emit();
599 assert!(sess.dcx.emitted_errors().unwrap().is_err());
600 });
601 assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test1"));
602 sess.enter(|| {
603 sess.dcx.err("test2").emit();
604 assert!(sess.dcx.emitted_errors().unwrap().is_err());
605 });
606 assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test1"));
607 assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test2"));
608 }
609
610 #[test]
611 fn enter_thread_pool() {
612 let sess = enter_tests_session();
613
614 assert!(!in_rayon());
615 sess.enter(|| {
616 assert!(in_rayon());
617 sess.enter(|| {
618 assert!(in_rayon());
619 });
620 assert!(in_rayon());
621 });
622 sess.enter_sequential(|| {
623 assert!(!in_rayon());
624 sess.enter(|| {
625 assert!(in_rayon());
626 });
627 assert!(!in_rayon());
628 sess.enter_sequential(|| {
629 assert!(!in_rayon());
630 });
631 assert!(!in_rayon());
632 });
633 assert!(!in_rayon());
634
635 let pool = rayon::ThreadPoolBuilder::new().build().unwrap();
636 pool.install(|| {
637 assert!(in_rayon());
638 cant_use_globals();
639 sess.enter(|| use_globals_parallel(&sess));
640 assert!(in_rayon());
641 cant_use_globals();
642 });
643 assert!(!in_rayon());
644 }
645
646 #[test]
647 fn enter_different_nested_sessions() {
648 let sess1 = enter_tests_session();
649 let sess2 = enter_tests_session();
650 assert!(!sess1.globals.maybe_eq(&sess2.globals));
651 sess1.enter(|| {
652 SessionGlobals::with(|g| assert!(g.maybe_eq(&sess1.globals)));
653 use_globals();
654 sess2.enter(|| {
655 SessionGlobals::with(|g| assert!(g.maybe_eq(&sess2.globals)));
656 use_globals();
657 });
658 use_globals();
659 });
660 }
661
662 #[test]
663 fn set_opts() {
664 let _ = Session::builder()
665 .with_test_emitter()
666 .opts(Opts {
667 evm_version: solar_config::EvmVersion::Berlin,
668 unstable: UnstableOpts { ast_stats: false, ..Default::default() },
669 ..Default::default()
670 })
671 .build();
672 }
673}