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 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}
147
148impl Session {
149 pub fn new(dcx: DiagCtxt, source_map: Arc<SourceMap>) -> Self {
151 Self::builder().dcx(dcx).source_map(source_map).build()
152 }
153
154 pub fn empty(dcx: DiagCtxt) -> Self {
156 Self::builder().dcx(dcx).build()
157 }
158
159 #[inline]
161 pub fn builder() -> SessionBuilder {
162 SessionBuilder::default()
163 }
164
165 pub fn infer_language(&mut self) {
167 if !self.opts.input.is_empty()
168 && self.opts.input.iter().all(|arg| Path::new(arg).extension() == Some("yul".as_ref()))
169 {
170 self.opts.language = solar_config::Language::Yul;
171 }
172 }
173
174 pub fn validate(&self) -> crate::Result<()> {
176 let mut result = Ok(());
177 result = result.and(self.check_unique("emit", &self.opts.emit));
178 result
179 }
180
181 fn check_unique<T: Eq + std::hash::Hash + std::fmt::Display>(
182 &self,
183 name: &str,
184 list: &[T],
185 ) -> crate::Result<()> {
186 let mut result = Ok(());
187 let mut seen = std::collections::HashSet::new();
188 for item in list {
189 if !seen.insert(item) {
190 let msg = format!("cannot specify `--{name} {item}` twice");
191 result = Err(self.dcx.err(msg).emit());
192 }
193 }
194 result
195 }
196
197 #[inline]
199 pub fn unstable(&self) -> &UnstableOpts {
200 &self.opts.unstable
201 }
202
203 #[inline]
208 pub fn emitted_diagnostics(&self) -> Option<EmittedDiagnostics> {
209 self.dcx.emitted_diagnostics()
210 }
211
212 #[inline]
217 pub fn emitted_errors(&self) -> Option<Result<(), EmittedDiagnostics>> {
218 self.dcx.emitted_errors()
219 }
220
221 #[inline]
223 pub fn source_map(&self) -> &SourceMap {
224 &self.globals.source_map
225 }
226
227 #[inline]
229 pub fn clone_source_map(&self) -> Arc<SourceMap> {
230 self.globals.source_map.clone()
231 }
232
233 #[inline]
235 pub fn stop_after(&self, stage: CompilerStage) -> bool {
236 self.opts.stop_after >= Some(stage)
237 }
238
239 #[inline]
241 pub fn threads(&self) -> usize {
242 self.opts.threads().get()
243 }
244
245 #[inline]
247 pub fn is_sequential(&self) -> bool {
248 self.threads() == 1
249 }
250
251 #[inline]
253 pub fn is_parallel(&self) -> bool {
254 !self.is_sequential()
255 }
256
257 #[inline]
259 pub fn do_emit(&self, output: CompilerOutput) -> bool {
260 self.opts.emit.contains(&output)
261 }
262
263 #[inline]
268 pub fn spawn(&self, f: impl FnOnce() + Send + 'static) {
269 if self.is_sequential() {
270 f();
271 } else {
272 rayon::spawn(f);
273 }
274 }
275
276 #[inline]
279 pub fn join<A, B, RA, RB>(&self, oper_a: A, oper_b: B) -> (RA, RB)
280 where
281 A: FnOnce() -> RA + Send,
282 B: FnOnce() -> RB + Send,
283 RA: Send,
284 RB: Send,
285 {
286 if self.is_sequential() { (oper_a(), oper_b()) } else { rayon::join(oper_a, oper_b) }
287 }
288
289 #[inline]
293 pub fn scope<'scope, OP, R>(&self, op: OP) -> R
294 where
295 OP: FnOnce(solar_data_structures::sync::Scope<'_, 'scope>) -> R + Send,
296 R: Send,
297 {
298 solar_data_structures::sync::scope(self.is_parallel(), op)
299 }
300
301 #[inline]
309 #[track_caller]
310 pub fn enter<R>(&self, f: impl FnOnce() -> R) -> R {
311 self.globals.set(f)
312 }
313
314 #[inline]
319 #[track_caller]
320 pub fn enter_parallel<R: Send>(&self, f: impl FnOnce() -> R + Send) -> R {
321 self.enter(|| enter_thread_pool(self, f))
322 }
323}
324
325#[track_caller]
327fn enter_thread_pool<R: Send>(sess: &Session, f: impl FnOnce() -> R + Send) -> R {
328 if rayon::current_thread_index().is_some() {
330 debug!(
331 "running in the current thread's rayon thread pool; \
332 this could cause panics later on if it was created without setting the session globals!"
333 );
334 return f();
335 }
336
337 let threads = sess.threads();
338 debug_assert!(threads > 0, "number of threads must already be resolved");
339 let mut builder =
340 rayon::ThreadPoolBuilder::new().thread_name(|i| format!("solar-{i}")).num_threads(threads);
341 if threads == 1 {
344 builder = builder.use_current_thread();
345 }
346 match builder.build_scoped(
347 move |thread| sess.enter(|| thread.run()),
351 move |pool| pool.install(f),
353 ) {
354 Ok(r) => r,
355 Err(e) => {
356 let mut err = sess.dcx.fatal(format!("failed to build the rayon thread pool: {e}"));
357 if threads > 1 {
358 if SINGLE_THREADED_TARGET {
359 err = err.note("the current target might not support multi-threaded execution");
360 }
361 err = err.help("try running with `--threads 1` / `-j1` to disable parallelism");
362 }
363 err.emit();
364 }
365 }
366}
367
368#[cfg(test)]
369mod tests {
370 use super::*;
371 use std::path::PathBuf;
372
373 #[test]
374 #[should_panic = "diagnostics context not set"]
375 fn no_dcx() {
376 Session::builder().build();
377 }
378
379 #[test]
380 #[should_panic = "session source map does not match the one in the diagnostics context"]
381 fn sm_mismatch() {
382 let sm1 = Arc::<SourceMap>::default();
383 let sm2 = Arc::<SourceMap>::default();
384 assert!(!Arc::ptr_eq(&sm1, &sm2));
385 Session::builder().source_map(sm1).dcx(DiagCtxt::with_stderr_emitter(Some(sm2))).build();
386 }
387
388 #[test]
389 #[should_panic = "session source map does not match the one in the diagnostics context"]
390 fn sm_mismatch_non_builder() {
391 let sm1 = Arc::<SourceMap>::default();
392 let sm2 = Arc::<SourceMap>::default();
393 assert!(!Arc::ptr_eq(&sm1, &sm2));
394 Session::new(DiagCtxt::with_stderr_emitter(Some(sm2)), sm1);
395 }
396
397 #[test]
398 fn builder() {
399 let _ = Session::builder().with_stderr_emitter().build();
400 }
401
402 #[test]
403 fn empty() {
404 let _ = Session::empty(DiagCtxt::with_stderr_emitter(None));
405 let _ = Session::empty(DiagCtxt::with_stderr_emitter(Some(Default::default())));
406 }
407
408 #[test]
409 fn local() {
410 let sess = Session::builder().with_stderr_emitter().build();
411 assert!(sess.emitted_diagnostics().is_none());
412 assert!(sess.emitted_errors().is_none());
413
414 let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
415 sess.dcx.err("test").emit();
416 let err = sess.dcx.emitted_errors().unwrap().unwrap_err();
417 let err = Box::new(err) as Box<dyn std::error::Error>;
418 assert!(err.to_string().contains("error: test"), "{err:?}");
419 }
420
421 #[test]
422 fn enter() {
423 #[track_caller]
424 fn use_globals_no_sm() {
425 SessionGlobals::with(|_globals| {});
426
427 let s = "hello";
428 let sym = crate::Symbol::intern(s);
429 assert_eq!(sym.as_str(), s);
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 assert!(rayon::current_thread_index().is_some());
441 }
442
443 let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
444 sess.source_map().new_source_file(PathBuf::from("test"), "abcd").unwrap();
445 sess.enter_parallel(|| use_globals());
446 assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
447 assert!(sess.dcx.emitted_errors().unwrap().is_ok());
448 sess.enter_parallel(|| {
449 use_globals();
450 sess.enter_parallel(use_globals);
451 use_globals();
452 });
453 assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
454 assert!(sess.dcx.emitted_errors().unwrap().is_ok());
455
456 sess.enter(|| {
457 use_globals_no_sm();
458 sess.enter_parallel(|| {
459 use_globals();
460 sess.enter_parallel(|| use_globals());
461 use_globals();
462 });
463 use_globals_no_sm();
464 });
465 assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
466 assert!(sess.dcx.emitted_errors().unwrap().is_ok());
467 }
468
469 #[test]
470 fn enter_diags() {
471 let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
472 assert!(sess.dcx.emitted_errors().unwrap().is_ok());
473 sess.enter_parallel(|| {
474 sess.dcx.err("test1").emit();
475 assert!(sess.dcx.emitted_errors().unwrap().is_err());
476 });
477 assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test1"));
478 sess.enter_parallel(|| {
479 sess.dcx.err("test2").emit();
480 assert!(sess.dcx.emitted_errors().unwrap().is_err());
481 });
482 assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test1"));
483 assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test2"));
484 }
485
486 #[test]
487 fn set_opts() {
488 let _ = Session::builder()
489 .with_test_emitter()
490 .opts(Opts {
491 evm_version: solar_config::EvmVersion::Berlin,
492 unstable: UnstableOpts { ast_stats: false, ..Default::default() },
493 ..Default::default()
494 })
495 .build();
496 }
497}