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::{path::Path, sync::Arc};
7
8pub struct Session {
10 pub opts: Opts,
12
13 pub dcx: DiagCtxt,
15 globals: SessionGlobals,
17}
18
19#[derive(Default)]
21#[must_use = "builders don't do anything unless you call `build`"]
22pub struct SessionBuilder {
23 dcx: Option<DiagCtxt>,
24 globals: Option<SessionGlobals>,
25 opts: Option<Opts>,
26}
27
28impl SessionBuilder {
29 pub fn dcx(mut self, dcx: DiagCtxt) -> Self {
33 self.dcx = Some(dcx);
34 self
35 }
36
37 pub fn source_map(mut self, source_map: Arc<SourceMap>) -> Self {
39 self.get_globals().source_map = source_map;
40 self
41 }
42
43 pub fn opts(mut self, opts: Opts) -> Self {
45 self.opts = Some(opts);
46 self
47 }
48
49 #[inline]
51 pub fn with_test_emitter(mut self) -> Self {
52 let sm = self.get_source_map();
53 self.dcx(DiagCtxt::with_test_emitter(Some(sm)))
54 }
55
56 #[inline]
58 pub fn with_stderr_emitter(self) -> Self {
59 self.with_stderr_emitter_and_color(ColorChoice::Auto)
60 }
61
62 #[inline]
64 pub fn with_stderr_emitter_and_color(mut self, color_choice: ColorChoice) -> Self {
65 let sm = self.get_source_map();
66 self.dcx(DiagCtxt::with_stderr_emitter_and_color(Some(sm), color_choice))
67 }
68
69 #[inline]
71 pub fn with_buffer_emitter(mut self, color_choice: ColorChoice) -> Self {
72 let sm = self.get_source_map();
73 self.dcx(DiagCtxt::with_buffer_emitter(Some(sm), color_choice))
74 }
75
76 #[inline]
78 pub fn with_silent_emitter(self, fatal_note: Option<String>) -> Self {
79 self.dcx(DiagCtxt::with_silent_emitter(fatal_note))
80 }
81
82 #[inline]
84 pub fn single_threaded(self) -> Self {
85 self.threads(1)
86 }
87
88 #[inline]
91 pub fn threads(mut self, threads: usize) -> Self {
92 self.opts_mut().threads = threads.into();
93 self
94 }
95
96 fn get_source_map(&mut self) -> Arc<SourceMap> {
98 self.get_globals().source_map.clone()
99 }
100
101 fn get_globals(&mut self) -> &mut SessionGlobals {
102 self.globals.get_or_insert_default()
103 }
104
105 fn opts_mut(&mut self) -> &mut Opts {
107 self.opts.get_or_insert_default()
108 }
109
110 #[track_caller]
122 pub fn build(mut self) -> Session {
123 let mut dcx = self.dcx.take().unwrap_or_else(|| panic!("diagnostics context not set"));
124 let sess = Session {
125 globals: match self.globals.take() {
126 Some(globals) => {
127 if let Some(sm) = dcx.source_map_mut() {
129 assert!(
130 Arc::ptr_eq(&globals.source_map, sm),
131 "session source map does not match the one in the diagnostics context"
132 );
133 }
134 globals
135 }
136 None => {
137 let sm = dcx.source_map_mut().cloned().unwrap_or_default();
139 SessionGlobals::new(sm)
140 }
141 },
142 dcx,
143 opts: self.opts.take().unwrap_or_default(),
144 };
145
146 if let Some(base_path) =
147 sess.opts.base_path.clone().or_else(|| std::env::current_dir().ok())
148 && let Ok(base_path) = sess.source_map().file_loader().canonicalize_path(&base_path)
149 {
150 sess.source_map().set_base_path(base_path);
151 }
152
153 sess
154 }
155}
156
157impl Session {
158 pub fn new(dcx: DiagCtxt, source_map: Arc<SourceMap>) -> Self {
160 Self::builder().dcx(dcx).source_map(source_map).build()
161 }
162
163 pub fn empty(dcx: DiagCtxt) -> Self {
165 Self::builder().dcx(dcx).build()
166 }
167
168 #[inline]
170 pub fn builder() -> SessionBuilder {
171 SessionBuilder::default()
172 }
173
174 pub fn infer_language(&mut self) {
176 if !self.opts.input.is_empty()
177 && self.opts.input.iter().all(|arg| Path::new(arg).extension() == Some("yul".as_ref()))
178 {
179 self.opts.language = solar_config::Language::Yul;
180 }
181 }
182
183 pub fn validate(&self) -> crate::Result<()> {
185 let mut result = Ok(());
186 result = result.and(self.check_unique("emit", &self.opts.emit));
187 result
188 }
189
190 fn check_unique<T: Eq + std::hash::Hash + std::fmt::Display>(
191 &self,
192 name: &str,
193 list: &[T],
194 ) -> crate::Result<()> {
195 let mut result = Ok(());
196 let mut seen = std::collections::HashSet::new();
197 for item in list {
198 if !seen.insert(item) {
199 let msg = format!("cannot specify `--{name} {item}` twice");
200 result = Err(self.dcx.err(msg).emit());
201 }
202 }
203 result
204 }
205
206 #[inline]
208 pub fn unstable(&self) -> &UnstableOpts {
209 &self.opts.unstable
210 }
211
212 #[inline]
217 pub fn emitted_diagnostics(&self) -> Option<EmittedDiagnostics> {
218 self.dcx.emitted_diagnostics()
219 }
220
221 #[inline]
226 pub fn emitted_errors(&self) -> Option<Result<(), EmittedDiagnostics>> {
227 self.dcx.emitted_errors()
228 }
229
230 #[inline]
232 pub fn source_map(&self) -> &SourceMap {
233 &self.globals.source_map
234 }
235
236 #[inline]
238 pub fn clone_source_map(&self) -> Arc<SourceMap> {
239 self.globals.source_map.clone()
240 }
241
242 #[inline]
244 pub fn stop_after(&self, stage: CompilerStage) -> bool {
245 self.opts.stop_after >= Some(stage)
246 }
247
248 #[inline]
250 pub fn threads(&self) -> usize {
251 self.opts.threads().get()
252 }
253
254 #[inline]
256 pub fn is_sequential(&self) -> bool {
257 self.threads() == 1
258 }
259
260 #[inline]
262 pub fn is_parallel(&self) -> bool {
263 !self.is_sequential()
264 }
265
266 #[inline]
268 pub fn do_emit(&self, output: CompilerOutput) -> bool {
269 self.opts.emit.contains(&output)
270 }
271
272 #[inline]
278 pub fn spawn(&self, f: impl FnOnce() + Send + 'static) {
279 if self.is_sequential() {
280 f();
281 } else {
282 rayon::spawn(f);
283 }
284 }
285
286 #[inline]
292 pub fn join<A, B, RA, RB>(&self, oper_a: A, oper_b: B) -> (RA, RB)
293 where
294 A: FnOnce() -> RA + Send,
295 B: FnOnce() -> RB + Send,
296 RA: Send,
297 RB: Send,
298 {
299 if self.is_sequential() { (oper_a(), oper_b()) } else { rayon::join(oper_a, oper_b) }
300 }
301
302 #[inline]
306 pub fn scope<'scope, OP, R>(&self, op: OP) -> R
307 where
308 OP: FnOnce(solar_data_structures::sync::Scope<'_, 'scope>) -> R + Send,
309 R: Send,
310 {
311 solar_data_structures::sync::scope(self.is_parallel(), op)
312 }
313
314 #[track_caller]
320 pub fn enter<R: Send>(&self, f: impl FnOnce() -> R + Send) -> R {
321 if in_rayon() {
322 if self.is_sequential() {
324 reentrant_log();
325 return self.enter_sequential(f);
326 }
327 if self.is_set() {
329 return f();
331 }
332 }
333
334 self.enter_sequential(|| {
335 match thread_pool_builder(self).build_scoped(
336 thread_wrapper(self),
340 move |pool| pool.install(f),
342 ) {
343 Ok(r) => r,
344 Err(e) => handle_thread_pool_build_error(self, e),
345 }
346 })
347 }
348
349 #[inline]
357 #[track_caller]
358 pub fn enter_sequential<R>(&self, f: impl FnOnce() -> R) -> R {
359 self.globals.set(f)
360 }
361
362 fn is_set(&self) -> bool {
364 SessionGlobals::try_with(|g| g.is_some_and(|g| g.maybe_eq(&self.globals)))
365 }
366}
367
368fn reentrant_log() {
369 debug!(
370 "running in the current thread's rayon thread pool; \
371 this could cause panics later on if it was created without setting the session globals!"
372 );
373}
374
375fn thread_pool_builder(sess: &Session) -> rayon::ThreadPoolBuilder {
376 let threads = sess.threads();
377 debug_assert!(threads > 0, "number of threads must already be resolved");
378 let mut builder =
379 rayon::ThreadPoolBuilder::new().thread_name(|i| format!("solar-{i}")).num_threads(threads);
380 if threads == 1 {
383 builder = builder.use_current_thread();
384 }
385 builder
386}
387
388fn thread_wrapper(sess: &Session) -> impl Fn(rayon::ThreadBuilder) {
389 move |thread| sess.enter_sequential(|| thread.run())
390}
391
392#[cold]
393fn handle_thread_pool_build_error(sess: &Session, e: rayon::ThreadPoolBuildError) -> ! {
394 let mut err = sess.dcx.fatal(format!("failed to build the rayon thread pool: {e}"));
395 if sess.is_parallel() {
396 if SINGLE_THREADED_TARGET {
397 err = err.note("the current target might not support multi-threaded execution");
398 }
399 err = err.help("try running with `--threads 1` / `-j1` to disable parallelism");
400 }
401 err.emit()
402}
403
404#[inline]
405fn in_rayon() -> bool {
406 rayon::current_thread_index().is_some()
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412 use std::path::PathBuf;
413
414 fn enter_tests_session() -> Session {
416 let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
417 sess.source_map().new_source_file(PathBuf::from("test"), "abcd").unwrap();
418 sess
419 }
420
421 #[track_caller]
422 fn use_globals_parallel(sess: &Session) {
423 use rayon::prelude::*;
424
425 use_globals();
426 sess.spawn(|| use_globals());
427 sess.join(|| use_globals(), || use_globals());
428 [1, 2, 3].par_iter().for_each(|_| use_globals());
429 use_globals();
430 }
431
432 #[track_caller]
433 fn use_globals() {
434 use_globals_no_sm();
435
436 let span = crate::Span::new(crate::BytePos(0), crate::BytePos(1));
437 assert_eq!(format!("{span:?}"), "test:1:1: 1:2");
438 assert_eq!(format!("{span:#?}"), "test:1:1: 1:2");
439 }
440
441 #[track_caller]
442 fn use_globals_no_sm() {
443 SessionGlobals::with(|_globals| {});
444
445 let s = "hello";
446 let sym = crate::Symbol::intern(s);
447 assert_eq!(sym.as_str(), s);
448 }
449
450 #[track_caller]
451 fn cant_use_globals() {
452 std::panic::catch_unwind(|| use_globals()).unwrap_err();
453 }
454
455 #[test]
456 #[should_panic = "diagnostics context not set"]
457 fn no_dcx() {
458 Session::builder().build();
459 }
460
461 #[test]
462 #[should_panic = "session source map does not match the one in the diagnostics context"]
463 fn sm_mismatch() {
464 let sm1 = Arc::<SourceMap>::default();
465 let sm2 = Arc::<SourceMap>::default();
466 assert!(!Arc::ptr_eq(&sm1, &sm2));
467 Session::builder().source_map(sm1).dcx(DiagCtxt::with_stderr_emitter(Some(sm2))).build();
468 }
469
470 #[test]
471 #[should_panic = "session source map does not match the one in the diagnostics context"]
472 fn sm_mismatch_non_builder() {
473 let sm1 = Arc::<SourceMap>::default();
474 let sm2 = Arc::<SourceMap>::default();
475 assert!(!Arc::ptr_eq(&sm1, &sm2));
476 Session::new(DiagCtxt::with_stderr_emitter(Some(sm2)), sm1);
477 }
478
479 #[test]
480 fn builder() {
481 let _ = Session::builder().with_stderr_emitter().build();
482 }
483
484 #[test]
485 fn empty() {
486 let _ = Session::empty(DiagCtxt::with_stderr_emitter(None));
487 let _ = Session::empty(DiagCtxt::with_stderr_emitter(Some(Default::default())));
488 }
489
490 #[test]
491 fn local() {
492 let sess = Session::builder().with_stderr_emitter().build();
493 assert!(sess.emitted_diagnostics().is_none());
494 assert!(sess.emitted_errors().is_none());
495
496 let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
497 sess.dcx.err("test").emit();
498 let err = sess.dcx.emitted_errors().unwrap().unwrap_err();
499 let err = Box::new(err) as Box<dyn std::error::Error>;
500 assert!(err.to_string().contains("error: test"), "{err:?}");
501 }
502
503 #[test]
504 fn enter() {
505 crate::enter(|| {
506 use_globals_no_sm();
507 cant_use_globals();
508 });
509
510 let sess = enter_tests_session();
511 sess.enter(|| use_globals());
512 assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
513 assert!(sess.dcx.emitted_errors().unwrap().is_ok());
514 sess.enter(|| {
515 use_globals();
516 sess.enter(|| use_globals());
517 use_globals();
518 });
519 assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
520 assert!(sess.dcx.emitted_errors().unwrap().is_ok());
521
522 sess.enter_sequential(|| {
523 use_globals();
524 sess.enter(|| {
525 use_globals_parallel(&sess);
526 sess.enter(|| use_globals_parallel(&sess));
527 use_globals_parallel(&sess);
528 });
529 use_globals();
530 });
531 assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
532 assert!(sess.dcx.emitted_errors().unwrap().is_ok());
533 }
534
535 #[test]
536 fn enter_diags() {
537 let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
538 assert!(sess.dcx.emitted_errors().unwrap().is_ok());
539 sess.enter(|| {
540 sess.dcx.err("test1").emit();
541 assert!(sess.dcx.emitted_errors().unwrap().is_err());
542 });
543 assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test1"));
544 sess.enter(|| {
545 sess.dcx.err("test2").emit();
546 assert!(sess.dcx.emitted_errors().unwrap().is_err());
547 });
548 assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test1"));
549 assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test2"));
550 }
551
552 #[test]
553 fn enter_thread_pool() {
554 let sess = enter_tests_session();
555
556 assert!(!in_rayon());
557 sess.enter(|| {
558 assert!(in_rayon());
559 sess.enter(|| {
560 assert!(in_rayon());
561 });
562 assert!(in_rayon());
563 });
564 sess.enter_sequential(|| {
565 assert!(!in_rayon());
566 sess.enter(|| {
567 assert!(in_rayon());
568 });
569 assert!(!in_rayon());
570 sess.enter_sequential(|| {
571 assert!(!in_rayon());
572 });
573 assert!(!in_rayon());
574 });
575 assert!(!in_rayon());
576
577 let pool = rayon::ThreadPoolBuilder::new().build().unwrap();
578 pool.install(|| {
579 assert!(in_rayon());
580 cant_use_globals();
581 sess.enter(|| use_globals_parallel(&sess));
582 assert!(in_rayon());
583 cant_use_globals();
584 });
585 assert!(!in_rayon());
586 }
587
588 #[test]
589 fn enter_different_nested_sessions() {
590 let sess1 = enter_tests_session();
591 let sess2 = enter_tests_session();
592 assert!(!sess1.globals.maybe_eq(&sess2.globals));
593 sess1.enter(|| {
594 SessionGlobals::with(|g| assert!(g.maybe_eq(&sess1.globals)));
595 use_globals();
596 sess2.enter(|| {
597 SessionGlobals::with(|g| assert!(g.maybe_eq(&sess2.globals)));
598 use_globals();
599 });
600 use_globals();
601 });
602 }
603
604 #[test]
605 fn set_opts() {
606 let _ = Session::builder()
607 .with_test_emitter()
608 .opts(Opts {
609 evm_version: solar_config::EvmVersion::Berlin,
610 unstable: UnstableOpts { ast_stats: false, ..Default::default() },
611 ..Default::default()
612 })
613 .build();
614 }
615}