solar_interface/
session.rs1use crate::{
2 diagnostics::{DiagCtxt, EmittedDiagnostics},
3 ColorChoice, SessionGlobals, SourceMap,
4};
5use solar_config::{CompilerOutput, CompilerStage, Opts, UnstableOpts, SINGLE_THREADED_TARGET};
6use std::{path::Path, sync::Arc};
7
8#[derive(derive_builder::Builder)]
10#[builder(pattern = "owned", build_fn(name = "try_build", private), setter(strip_option))]
11pub struct Session {
12 pub dcx: DiagCtxt,
14 #[builder(default)]
16 source_map: Arc<SourceMap>,
17
18 #[builder(default)]
20 pub opts: Opts,
21}
22
23impl SessionBuilder {
24 #[inline]
26 pub fn with_test_emitter(mut self) -> Self {
27 let sm = self.get_source_map();
28 self.dcx(DiagCtxt::with_test_emitter(Some(sm)))
29 }
30
31 #[inline]
33 pub fn with_stderr_emitter(self) -> Self {
34 self.with_stderr_emitter_and_color(ColorChoice::Auto)
35 }
36
37 #[inline]
39 pub fn with_stderr_emitter_and_color(mut self, color_choice: ColorChoice) -> Self {
40 let sm = self.get_source_map();
41 self.dcx(DiagCtxt::with_stderr_emitter_and_color(Some(sm), color_choice))
42 }
43
44 #[inline]
46 pub fn with_buffer_emitter(mut self, color_choice: ColorChoice) -> Self {
47 let sm = self.get_source_map();
48 self.dcx(DiagCtxt::with_buffer_emitter(Some(sm), color_choice))
49 }
50
51 #[inline]
53 pub fn with_silent_emitter(self, fatal_note: Option<String>) -> Self {
54 self.dcx(DiagCtxt::with_silent_emitter(fatal_note))
55 }
56
57 #[inline]
59 pub fn single_threaded(self) -> Self {
60 self.threads(1)
61 }
62
63 #[inline]
66 pub fn threads(mut self, threads: usize) -> Self {
67 self.opts_mut().threads = threads.into();
68 self
69 }
70
71 fn get_source_map(&mut self) -> Arc<SourceMap> {
73 self.source_map.get_or_insert_default().clone()
74 }
75
76 fn opts_mut(&mut self) -> &mut Opts {
78 self.opts.get_or_insert_default()
79 }
80
81 #[track_caller]
93 pub fn build(mut self) -> Session {
94 let dcx = self.dcx.as_mut().unwrap_or_else(|| panic!("diagnostics context not set"));
96 if self.source_map.is_none() {
97 self.source_map = dcx.source_map_mut().cloned();
98 }
99
100 let mut sess = self.try_build().unwrap();
101 if let Some(sm) = sess.dcx.source_map_mut() {
102 assert!(
103 Arc::ptr_eq(&sess.source_map, sm),
104 "session source map does not match the one in the diagnostics context"
105 );
106 }
107 sess
108 }
109}
110
111impl Session {
112 pub fn new(dcx: DiagCtxt, source_map: Arc<SourceMap>) -> Self {
114 Self::builder().dcx(dcx).source_map(source_map).build()
115 }
116
117 pub fn empty(dcx: DiagCtxt) -> Self {
119 Self::builder().dcx(dcx).build()
120 }
121
122 #[inline]
124 pub fn builder() -> SessionBuilder {
125 SessionBuilder::default()
126 }
127
128 pub fn infer_language(&mut self) {
130 if !self.opts.input.is_empty()
131 && self.opts.input.iter().all(|arg| Path::new(arg).extension() == Some("yul".as_ref()))
132 {
133 self.opts.language = solar_config::Language::Yul;
134 }
135 }
136
137 pub fn validate(&self) -> crate::Result<()> {
139 let mut result = Ok(());
140 result = result.and(self.check_unique("emit", &self.opts.emit));
141 result
142 }
143
144 fn check_unique<T: Eq + std::hash::Hash + std::fmt::Display>(
145 &self,
146 name: &str,
147 list: &[T],
148 ) -> crate::Result<()> {
149 let mut result = Ok(());
150 let mut seen = std::collections::HashSet::new();
151 for item in list {
152 if !seen.insert(item) {
153 let msg = format!("cannot specify `--{name} {item}` twice");
154 result = Err(self.dcx.err(msg).emit());
155 }
156 }
157 result
158 }
159
160 #[inline]
162 pub fn unstable(&self) -> &UnstableOpts {
163 &self.opts.unstable
164 }
165
166 #[inline]
171 pub fn emitted_diagnostics(&self) -> Option<EmittedDiagnostics> {
172 self.dcx.emitted_diagnostics()
173 }
174
175 #[inline]
180 pub fn emitted_errors(&self) -> Option<Result<(), EmittedDiagnostics>> {
181 self.dcx.emitted_errors()
182 }
183
184 #[inline]
186 pub fn source_map(&self) -> &SourceMap {
187 &self.source_map
188 }
189
190 #[inline]
192 pub fn clone_source_map(&self) -> Arc<SourceMap> {
193 self.source_map.clone()
194 }
195
196 #[inline]
198 pub fn stop_after(&self, stage: CompilerStage) -> bool {
199 self.opts.stop_after >= Some(stage)
200 }
201
202 #[inline]
204 pub fn threads(&self) -> usize {
205 self.opts.threads().get()
206 }
207
208 #[inline]
210 pub fn is_sequential(&self) -> bool {
211 self.threads() == 1
212 }
213
214 #[inline]
216 pub fn is_parallel(&self) -> bool {
217 !self.is_sequential()
218 }
219
220 #[inline]
222 pub fn do_emit(&self, output: CompilerOutput) -> bool {
223 self.opts.emit.contains(&output)
224 }
225
226 #[inline]
231 pub fn spawn(&self, f: impl FnOnce() + Send + 'static) {
232 if self.is_sequential() {
233 f();
234 } else {
235 rayon::spawn(f);
236 }
237 }
238
239 #[inline]
242 pub fn join<A, B, RA, RB>(&self, oper_a: A, oper_b: B) -> (RA, RB)
243 where
244 A: FnOnce() -> RA + Send,
245 B: FnOnce() -> RB + Send,
246 RA: Send,
247 RB: Send,
248 {
249 if self.is_sequential() {
250 (oper_a(), oper_b())
251 } else {
252 rayon::join(oper_a, oper_b)
253 }
254 }
255
256 #[inline]
260 pub fn scope<'scope, OP, R>(&self, op: OP) -> R
261 where
262 OP: FnOnce(solar_data_structures::sync::Scope<'_, 'scope>) -> R + Send,
263 R: Send,
264 {
265 solar_data_structures::sync::scope(self.is_parallel(), op)
266 }
267
268 #[inline]
276 pub fn enter<R>(&self, f: impl FnOnce() -> R) -> R {
277 SessionGlobals::with_or_default(|_| {
278 SessionGlobals::with_source_map(self.clone_source_map(), f)
279 })
280 }
281
282 #[inline]
287 pub fn enter_parallel<R: Send>(&self, f: impl FnOnce() -> R + Send) -> R {
288 SessionGlobals::with_or_default(|session_globals| {
289 SessionGlobals::with_source_map(self.clone_source_map(), || {
290 run_in_thread_pool_with_globals(self, session_globals, f)
291 })
292 })
293 }
294}
295
296fn run_in_thread_pool_with_globals<R: Send>(
298 sess: &Session,
299 session_globals: &SessionGlobals,
300 f: impl FnOnce() -> R + Send,
301) -> R {
302 if rayon::current_thread_index().is_some() {
304 debug!(
305 "running in the current thread's rayon thread pool; \
306 this could cause panics later on if it was created without setting the session globals!"
307 );
308 return f();
309 }
310
311 let threads = sess.threads();
312 debug_assert!(threads > 0, "number of threads must already be resolved");
313 let mut builder =
314 rayon::ThreadPoolBuilder::new().thread_name(|i| format!("solar-{i}")).num_threads(threads);
315 if threads == 1 {
318 builder = builder.use_current_thread();
319 }
320 match builder.build_scoped(
321 move |thread| session_globals.set(|| thread.run()),
324 move |pool| pool.install(f),
326 ) {
327 Ok(r) => r,
328 Err(e) => {
329 let mut err = sess.dcx.fatal(format!("failed to build the rayon thread pool: {e}"));
330 if threads > 1 {
331 if SINGLE_THREADED_TARGET {
332 err = err.note("the current target might not support multi-threaded execution");
333 }
334 err = err.help("try running with `--threads 1` / `-j1` to disable parallelism");
335 }
336 err.emit();
337 }
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344
345 #[test]
346 #[should_panic = "diagnostics context not set"]
347 fn no_dcx() {
348 Session::builder().build();
349 }
350
351 #[test]
352 #[should_panic = "session source map does not match the one in the diagnostics context"]
353 fn sm_mismatch() {
354 let sm1 = Arc::<SourceMap>::default();
355 let sm2 = Arc::<SourceMap>::default();
356 assert!(!Arc::ptr_eq(&sm1, &sm2));
357 Session::builder().source_map(sm1).dcx(DiagCtxt::with_stderr_emitter(Some(sm2))).build();
358 }
359
360 #[test]
361 #[should_panic = "session source map does not match the one in the diagnostics context"]
362 fn sm_mismatch_non_builder() {
363 let sm1 = Arc::<SourceMap>::default();
364 let sm2 = Arc::<SourceMap>::default();
365 assert!(!Arc::ptr_eq(&sm1, &sm2));
366 Session::new(DiagCtxt::with_stderr_emitter(Some(sm2)), sm1);
367 }
368
369 #[test]
370 fn builder() {
371 let _ = Session::builder().with_stderr_emitter().build();
372 }
373
374 #[test]
375 fn empty() {
376 let _ = Session::empty(DiagCtxt::with_stderr_emitter(None));
377 let _ = Session::empty(DiagCtxt::with_stderr_emitter(Some(Default::default())));
378 }
379
380 #[test]
381 fn local() {
382 let sess = Session::builder().with_stderr_emitter().build();
383 assert!(sess.emitted_diagnostics().is_none());
384 assert!(sess.emitted_errors().is_none());
385
386 let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
387 sess.dcx.err("test").emit();
388 let err = sess.dcx.emitted_errors().unwrap().unwrap_err();
389 let err = Box::new(err) as Box<dyn std::error::Error>;
390 assert!(err.to_string().contains("error: test"), "{err:?}");
391 }
392
393 #[test]
394 fn enter() {
395 #[track_caller]
396 fn use_globals_no_sm() {
397 SessionGlobals::with(|_globals| {});
398
399 let s = "hello";
400 let sym = crate::Symbol::intern(s);
401 assert_eq!(sym.as_str(), s);
402 }
403
404 #[track_caller]
405 fn use_globals() {
406 use_globals_no_sm();
407
408 let span = crate::Span::new(crate::BytePos(0), crate::BytePos(1));
409 let s = format!("{span:?}");
410 assert!(!s.contains("Span("), "{s}");
411 let s = format!("{span:#?}");
412 assert!(!s.contains("Span("), "{s}");
413
414 assert!(rayon::current_thread_index().is_some());
415 }
416
417 let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
418 sess.enter_parallel(use_globals);
419 assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
420 assert!(sess.dcx.emitted_errors().unwrap().is_ok());
421 sess.enter_parallel(|| {
422 use_globals();
423 sess.enter_parallel(use_globals);
424 use_globals();
425 });
426 assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
427 assert!(sess.dcx.emitted_errors().unwrap().is_ok());
428
429 SessionGlobals::new().set(|| {
430 use_globals_no_sm();
431 sess.enter_parallel(|| {
432 use_globals();
433 sess.enter_parallel(use_globals);
434 use_globals();
435 });
436 use_globals_no_sm();
437 });
438 assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
439 assert!(sess.dcx.emitted_errors().unwrap().is_ok());
440 }
441
442 #[test]
443 fn enter_diags() {
444 let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
445 assert!(sess.dcx.emitted_errors().unwrap().is_ok());
446 sess.enter_parallel(|| {
447 sess.dcx.err("test1").emit();
448 assert!(sess.dcx.emitted_errors().unwrap().is_err());
449 });
450 assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test1"));
451 sess.enter_parallel(|| {
452 sess.dcx.err("test2").emit();
453 assert!(sess.dcx.emitted_errors().unwrap().is_err());
454 });
455 assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test1"));
456 assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test2"));
457 }
458
459 #[test]
460 fn set_opts() {
461 let _ = Session::builder()
462 .with_test_emitter()
463 .opts(Opts {
464 evm_version: solar_config::EvmVersion::Berlin,
465 unstable: UnstableOpts { ast_stats: false, ..Default::default() },
466 ..Default::default()
467 })
468 .build();
469 }
470}