solar_interface/
session.rs1use crate::{
2 diagnostics::{DiagCtxt, EmittedDiagnostics},
3 ColorChoice, SessionGlobals, SourceMap,
4};
5use solar_config::{CompilerOutput, CompilerStage, Opts, UnstableOpts};
6use std::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| 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.threads(), session_globals, f)
291 })
292 })
293 }
294}
295
296fn run_in_thread_pool_with_globals<R: Send>(
298 threads: usize,
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 mut builder =
312 rayon::ThreadPoolBuilder::new().thread_name(|i| format!("solar-{i}")).num_threads(threads);
313 if threads == 1 {
316 builder = builder.use_current_thread();
317 }
318 builder
319 .build_scoped(
320 move |thread| session_globals.set(|| thread.run()),
323 move |pool| pool.install(f),
325 )
326 .unwrap()
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 #[test]
334 #[should_panic = "diagnostics context not set"]
335 fn no_dcx() {
336 Session::builder().build();
337 }
338
339 #[test]
340 #[should_panic = "session source map does not match the one in the diagnostics context"]
341 fn sm_mismatch() {
342 let sm1 = Arc::<SourceMap>::default();
343 let sm2 = Arc::<SourceMap>::default();
344 assert!(!Arc::ptr_eq(&sm1, &sm2));
345 Session::builder().source_map(sm1).dcx(DiagCtxt::with_stderr_emitter(Some(sm2))).build();
346 }
347
348 #[test]
349 #[should_panic = "session source map does not match the one in the diagnostics context"]
350 fn sm_mismatch_non_builder() {
351 let sm1 = Arc::<SourceMap>::default();
352 let sm2 = Arc::<SourceMap>::default();
353 assert!(!Arc::ptr_eq(&sm1, &sm2));
354 Session::new(DiagCtxt::with_stderr_emitter(Some(sm2)), sm1);
355 }
356
357 #[test]
358 fn builder() {
359 let _ = Session::builder().with_stderr_emitter().build();
360 }
361
362 #[test]
363 fn empty() {
364 let _ = Session::empty(DiagCtxt::with_stderr_emitter(None));
365 let _ = Session::empty(DiagCtxt::with_stderr_emitter(Some(Default::default())));
366 }
367
368 #[test]
369 fn local() {
370 let sess = Session::builder().with_stderr_emitter().build();
371 assert!(sess.emitted_diagnostics().is_none());
372 assert!(sess.emitted_errors().is_none());
373
374 let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
375 sess.dcx.err("test").emit();
376 let err = sess.dcx.emitted_errors().unwrap().unwrap_err();
377 let err = Box::new(err) as Box<dyn std::error::Error>;
378 assert!(err.to_string().contains("error: test"), "{err:?}");
379 }
380
381 #[test]
382 fn enter() {
383 #[track_caller]
384 fn use_globals_no_sm() {
385 SessionGlobals::with(|_globals| {});
386
387 let s = "hello";
388 let sym = crate::Symbol::intern(s);
389 assert_eq!(sym.as_str(), s);
390 }
391
392 #[track_caller]
393 fn use_globals() {
394 use_globals_no_sm();
395
396 let span = crate::Span::new(crate::BytePos(0), crate::BytePos(1));
397 let s = format!("{span:?}");
398 assert!(!s.contains("Span("), "{s}");
399 let s = format!("{span:#?}");
400 assert!(!s.contains("Span("), "{s}");
401
402 assert!(rayon::current_thread_index().is_some());
403 }
404
405 let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
406 sess.enter_parallel(use_globals);
407 assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
408 assert!(sess.dcx.emitted_errors().unwrap().is_ok());
409 sess.enter_parallel(|| {
410 use_globals();
411 sess.enter_parallel(use_globals);
412 use_globals();
413 });
414 assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
415 assert!(sess.dcx.emitted_errors().unwrap().is_ok());
416
417 SessionGlobals::new().set(|| {
418 use_globals_no_sm();
419 sess.enter_parallel(|| {
420 use_globals();
421 sess.enter_parallel(use_globals);
422 use_globals();
423 });
424 use_globals_no_sm();
425 });
426 assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
427 assert!(sess.dcx.emitted_errors().unwrap().is_ok());
428 }
429
430 #[test]
431 fn enter_diags() {
432 let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
433 assert!(sess.dcx.emitted_errors().unwrap().is_ok());
434 sess.enter_parallel(|| {
435 sess.dcx.err("test1").emit();
436 assert!(sess.dcx.emitted_errors().unwrap().is_err());
437 });
438 assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test1"));
439 sess.enter_parallel(|| {
440 sess.dcx.err("test2").emit();
441 assert!(sess.dcx.emitted_errors().unwrap().is_err());
442 });
443 assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test1"));
444 assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test2"));
445 }
446}