solar_interface/
session.rs

1use crate::{
2    ByteSymbol, ColorChoice, SessionGlobals, SourceMap, Symbol,
3    diagnostics::{DiagCtxt, EmittedDiagnostics},
4};
5use solar_config::{CompilerOutput, CompilerStage, Opts, SINGLE_THREADED_TARGET, UnstableOpts};
6use std::{
7    fmt,
8    path::Path,
9    sync::{Arc, OnceLock},
10};
11
12/// Information about the current compiler session.
13pub struct Session {
14    /// The compiler options.
15    pub opts: Opts,
16
17    /// The diagnostics context.
18    pub dcx: DiagCtxt,
19    /// The globals.
20    globals: Arc<SessionGlobals>,
21    /// The rayon thread pool. This is spawned lazily on first use, rather than always constructing
22    /// one with `SessionBuilder`.
23    thread_pool: OnceLock<rayon::ThreadPool>,
24}
25
26impl Default for Session {
27    fn default() -> Self {
28        Self::new(Opts::default())
29    }
30}
31
32impl fmt::Debug for Session {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        f.debug_struct("Session")
35            .field("opts", &self.opts)
36            .field("dcx", &self.dcx)
37            .finish_non_exhaustive()
38    }
39}
40
41/// [`Session`] builder.
42#[derive(Default)]
43#[must_use = "builders don't do anything unless you call `build`"]
44pub struct SessionBuilder {
45    dcx: Option<DiagCtxt>,
46    globals: Option<SessionGlobals>,
47    opts: Option<Opts>,
48}
49
50impl SessionBuilder {
51    /// Sets the diagnostic context.
52    ///
53    /// If `opts` is set this will default to [`DiagCtxt::from_opts`], otherwise this is required.
54    ///
55    /// See also the `with_*_emitter*` methods.
56    pub fn dcx(mut self, dcx: DiagCtxt) -> Self {
57        self.dcx = Some(dcx);
58        self
59    }
60
61    /// Sets the source map.
62    pub fn source_map(mut self, source_map: Arc<SourceMap>) -> Self {
63        self.get_globals().source_map = source_map;
64        self
65    }
66
67    /// Sets the compiler options.
68    pub fn opts(mut self, opts: Opts) -> Self {
69        self.opts = Some(opts);
70        self
71    }
72
73    /// Sets the diagnostic context to a test emitter.
74    #[inline]
75    pub fn with_test_emitter(mut self) -> Self {
76        let sm = self.get_source_map();
77        self.dcx(DiagCtxt::with_test_emitter(Some(sm)))
78    }
79
80    /// Sets the diagnostic context to a stderr emitter.
81    #[inline]
82    pub fn with_stderr_emitter(self) -> Self {
83        self.with_stderr_emitter_and_color(ColorChoice::Auto)
84    }
85
86    /// Sets the diagnostic context to a stderr emitter and a color choice.
87    #[inline]
88    pub fn with_stderr_emitter_and_color(mut self, color_choice: ColorChoice) -> Self {
89        let sm = self.get_source_map();
90        self.dcx(DiagCtxt::with_stderr_emitter_and_color(Some(sm), color_choice))
91    }
92
93    /// Sets the diagnostic context to a human emitter that emits diagnostics to a local buffer.
94    #[inline]
95    pub fn with_buffer_emitter(mut self, color_choice: ColorChoice) -> Self {
96        let sm = self.get_source_map();
97        self.dcx(DiagCtxt::with_buffer_emitter(Some(sm), color_choice))
98    }
99
100    /// Sets the diagnostic context to a silent emitter.
101    #[inline]
102    pub fn with_silent_emitter(self, fatal_note: Option<String>) -> Self {
103        self.dcx(DiagCtxt::with_silent_emitter(fatal_note))
104    }
105
106    /// Sets the number of threads to use for parallelism to 1.
107    #[inline]
108    pub fn single_threaded(self) -> Self {
109        self.threads(1)
110    }
111
112    /// Sets the number of threads to use for parallelism. Zero specifies the number of logical
113    /// cores.
114    #[inline]
115    pub fn threads(mut self, threads: usize) -> Self {
116        self.opts_mut().threads = threads.into();
117        self
118    }
119
120    /// Gets the source map from the diagnostics context.
121    fn get_source_map(&mut self) -> Arc<SourceMap> {
122        self.get_globals().source_map.clone()
123    }
124
125    fn get_globals(&mut self) -> &mut SessionGlobals {
126        self.globals.get_or_insert_default()
127    }
128
129    /// Returns a mutable reference to the options.
130    fn opts_mut(&mut self) -> &mut Opts {
131        self.opts.get_or_insert_default()
132    }
133
134    /// Consumes the builder to create a new session.
135    ///
136    /// The diagnostics context must be set before calling this method, either by calling
137    /// [`dcx`](Self::dcx) or by using one of the provided helper methods, like
138    /// [`with_stderr_emitter`](Self::with_stderr_emitter).
139    ///
140    /// # Panics
141    ///
142    /// Panics if:
143    /// - the diagnostics context is not set
144    /// - the source map in the diagnostics context does not match the one set in the builder
145    #[track_caller]
146    pub fn build(mut self) -> Session {
147        let opts = self.opts.take();
148        let mut dcx = self.dcx.take().unwrap_or_else(|| {
149            opts.as_ref()
150                .map(DiagCtxt::from_opts)
151                .unwrap_or_else(|| panic!("either diagnostics context or options must be set"))
152        });
153        let sess = Session {
154            globals: Arc::new(match self.globals.take() {
155                Some(globals) => {
156                    // Check that the source map matches the one in the diagnostics context.
157                    if let Some(sm) = dcx.source_map_mut() {
158                        assert!(
159                            Arc::ptr_eq(&globals.source_map, sm),
160                            "session source map does not match the one in the diagnostics context"
161                        );
162                    }
163                    globals
164                }
165                None => {
166                    // Set the source map from the diagnostics context.
167                    let sm = dcx.source_map_mut().cloned().unwrap_or_default();
168                    SessionGlobals::new(sm)
169                }
170            }),
171            dcx,
172            opts: opts.unwrap_or_default(),
173            thread_pool: OnceLock::new(),
174        };
175        sess.reconfigure();
176        debug!(version = %solar_config::version::SEMVER_VERSION, "created new session");
177        sess
178    }
179}
180
181impl Session {
182    /// Creates a new session builder.
183    #[inline]
184    pub fn builder() -> SessionBuilder {
185        SessionBuilder::default()
186    }
187
188    /// Creates a new session from the given options.
189    ///
190    /// See [`builder`](Self::builder) for a more flexible way to create a session.
191    pub fn new(opts: Opts) -> Self {
192        Self::builder().opts(opts).build()
193    }
194
195    /// Infers the language from the input files.
196    pub fn infer_language(&mut self) {
197        if !self.opts.input.is_empty()
198            && self.opts.input.iter().all(|arg| Path::new(arg).extension() == Some("yul".as_ref()))
199        {
200            self.opts.language = solar_config::Language::Yul;
201        }
202    }
203
204    /// Validates the session options.
205    pub fn validate(&self) -> crate::Result<()> {
206        let mut result = Ok(());
207        result = result.and(self.check_unique("emit", &self.opts.emit));
208        result
209    }
210
211    /// Reconfigures inner state to match any new options.
212    ///
213    /// Call this after updating options.
214    pub fn reconfigure(&self) {
215        'bp: {
216            let new_base_path = if self.opts.unstable.ui_testing {
217                // `ui_test` relies on absolute paths.
218                None
219            } else if let Some(base_path) =
220                self.opts.base_path.clone().or_else(|| std::env::current_dir().ok())
221                && let Ok(base_path) = self.source_map().file_loader().canonicalize_path(&base_path)
222            {
223                Some(base_path)
224            } else {
225                break 'bp;
226            };
227            self.source_map().set_base_path(new_base_path);
228        }
229    }
230
231    fn check_unique<T: Eq + std::hash::Hash + std::fmt::Display>(
232        &self,
233        name: &str,
234        list: &[T],
235    ) -> crate::Result<()> {
236        let mut result = Ok(());
237        let mut seen = std::collections::HashSet::new();
238        for item in list {
239            if !seen.insert(item) {
240                let msg = format!("cannot specify `--{name} {item}` twice");
241                result = Err(self.dcx.err(msg).emit());
242            }
243        }
244        result
245    }
246
247    /// Returns the unstable options.
248    #[inline]
249    pub fn unstable(&self) -> &UnstableOpts {
250        &self.opts.unstable
251    }
252
253    /// Returns the emitted diagnostics as a result. Can be empty.
254    ///
255    /// Returns `None` if the underlying emitter is not a human buffer emitter created with
256    /// [`with_buffer_emitter`](SessionBuilder::with_buffer_emitter).
257    ///
258    /// Results `Ok` if there are no errors, `Err` otherwise.
259    ///
260    /// # Examples
261    ///
262    /// Print diagnostics to `stdout` if there are no errors, otherwise propagate with `?`:
263    ///
264    /// ```no_run
265    /// # fn f(dcx: solar_interface::diagnostics::DiagCtxt) -> Result<(), Box<dyn std::error::Error>> {
266    /// println!("{}", dcx.emitted_diagnostics_result().unwrap()?);
267    /// # Ok(())
268    /// # }
269    /// ```
270    #[inline]
271    pub fn emitted_diagnostics_result(
272        &self,
273    ) -> Option<Result<EmittedDiagnostics, EmittedDiagnostics>> {
274        self.dcx.emitted_diagnostics_result()
275    }
276
277    /// Returns the emitted diagnostics. Can be empty.
278    ///
279    /// Returns `None` if the underlying emitter is not a human buffer emitter created with
280    /// [`with_buffer_emitter`](SessionBuilder::with_buffer_emitter).
281    #[inline]
282    pub fn emitted_diagnostics(&self) -> Option<EmittedDiagnostics> {
283        self.dcx.emitted_diagnostics()
284    }
285
286    /// Returns `Err` with the printed diagnostics if any errors have been emitted.
287    ///
288    /// Returns `None` if the underlying emitter is not a human buffer emitter created with
289    /// [`with_buffer_emitter`](SessionBuilder::with_buffer_emitter).
290    #[inline]
291    pub fn emitted_errors(&self) -> Option<Result<(), EmittedDiagnostics>> {
292        self.dcx.emitted_errors()
293    }
294
295    /// Returns a reference to the source map.
296    #[inline]
297    pub fn source_map(&self) -> &SourceMap {
298        &self.globals.source_map
299    }
300
301    /// Clones the source map.
302    #[inline]
303    pub fn clone_source_map(&self) -> Arc<SourceMap> {
304        self.globals.source_map.clone()
305    }
306
307    /// Returns `true` if compilation should stop after the given stage.
308    #[inline]
309    pub fn stop_after(&self, stage: CompilerStage) -> bool {
310        self.opts.stop_after >= Some(stage)
311    }
312
313    /// Returns the number of threads to use for parallelism.
314    #[inline]
315    pub fn threads(&self) -> usize {
316        self.opts.threads().get()
317    }
318
319    /// Returns `true` if parallelism is not enabled.
320    #[inline]
321    pub fn is_sequential(&self) -> bool {
322        self.threads() == 1
323    }
324
325    /// Returns `true` if parallelism is enabled.
326    #[inline]
327    pub fn is_parallel(&self) -> bool {
328        !self.is_sequential()
329    }
330
331    /// Returns `true` if the given output should be emitted.
332    #[inline]
333    pub fn do_emit(&self, output: CompilerOutput) -> bool {
334        self.opts.emit.contains(&output)
335    }
336
337    /// Spawns the given closure on the thread pool or executes it immediately if parallelism is not
338    /// enabled.
339    ///
340    /// NOTE: on a `use_current_thread` thread pool `rayon::spawn` will never execute without
341    /// yielding to rayon, so prefer using this method over `rayon::spawn`.
342    #[inline]
343    pub fn spawn(&self, f: impl FnOnce() + Send + 'static) {
344        if self.is_sequential() {
345            f();
346        } else {
347            rayon::spawn(f);
348        }
349    }
350
351    /// Takes two closures and potentially runs them in parallel. It returns a pair of the results
352    /// from those closures.
353    ///
354    /// NOTE: on a `use_current_thread` thread pool `rayon::join` will never execute without
355    /// yielding to rayon, so prefer using this method over `rayon::join`.
356    #[inline]
357    pub fn join<A, B, RA, RB>(&self, oper_a: A, oper_b: B) -> (RA, RB)
358    where
359        A: FnOnce() -> RA + Send,
360        B: FnOnce() -> RB + Send,
361        RA: Send,
362        RB: Send,
363    {
364        if self.is_sequential() { (oper_a(), oper_b()) } else { rayon::join(oper_a, oper_b) }
365    }
366
367    /// Executes the given closure in a fork-join scope.
368    ///
369    /// See [`rayon::scope`] for more details.
370    #[inline]
371    pub fn scope<'scope, OP, R>(&self, op: OP) -> R
372    where
373        OP: FnOnce(solar_data_structures::sync::Scope<'_, 'scope>) -> R + Send,
374        R: Send,
375    {
376        solar_data_structures::sync::scope(self.is_parallel(), op)
377    }
378
379    /// Sets up the session globals and executes the given closure in the thread pool.
380    ///
381    /// The thread pool and globals are stored in this [`Session`] itself, meaning multiple
382    /// consecutive calls to [`enter`](Self::enter) will share the same globals and resources.
383    #[track_caller]
384    pub fn enter<R: Send>(&self, f: impl FnOnce() -> R + Send) -> R {
385        if in_rayon() {
386            // Avoid panicking if we were to build a `current_thread` thread pool.
387            if self.is_sequential() {
388                reentrant_log();
389                return self.enter_sequential(f);
390            }
391            // Avoid creating a new thread pool if it's already set up with the same globals.
392            if self.is_entered() {
393                // No need to set again.
394                return f();
395            }
396        }
397
398        self.enter_sequential(|| self.thread_pool().install(f))
399    }
400
401    /// Sets up the session globals and executes the given closure in the current thread.
402    ///
403    /// Note that this does not set up the rayon thread pool. This is only useful when parsing
404    /// sequentially, like manually using `Parser`. Otherwise, it might cause panics later on if a
405    /// thread pool is expected to be set up correctly.
406    ///
407    /// See [`enter`](Self::enter) for more details.
408    #[inline]
409    #[track_caller]
410    pub fn enter_sequential<R>(&self, f: impl FnOnce() -> R) -> R {
411        self.globals.set(f)
412    }
413
414    /// Interns a string in this session's symbol interner.
415    ///
416    /// The symbol may not be usable on its own if the session has not been [entered](Self::enter).
417    #[inline]
418    #[cfg_attr(debug_assertions, track_caller)]
419    pub fn intern(&self, s: &str) -> Symbol {
420        self.globals.symbol_interner.intern(s)
421    }
422
423    /// Resolves a symbol to its string representation.
424    ///
425    /// The given symbol must have been interned in this session.
426    #[inline]
427    #[cfg_attr(debug_assertions, track_caller)]
428    pub fn resolve_symbol(&self, s: Symbol) -> &str {
429        self.globals.symbol_interner.get(s)
430    }
431
432    /// Interns a byte string in this session's symbol interner.
433    ///
434    /// The symbol may not be usable on its own if the session has not been [entered](Self::enter).
435    #[inline]
436    #[cfg_attr(debug_assertions, track_caller)]
437    pub fn intern_byte_str(&self, s: &[u8]) -> ByteSymbol {
438        self.globals.symbol_interner.intern_byte_str(s)
439    }
440
441    /// Resolves a byte symbol to its string representation.
442    ///
443    /// The given symbol must have been interned in this session.
444    #[inline]
445    #[cfg_attr(debug_assertions, track_caller)]
446    pub fn resolve_byte_str(&self, s: ByteSymbol) -> &[u8] {
447        self.globals.symbol_interner.get_byte_str(s)
448    }
449
450    /// Returns `true` if this session has been entered.
451    pub fn is_entered(&self) -> bool {
452        SessionGlobals::try_with(|g| g.is_some_and(|g| g.maybe_eq(&self.globals)))
453    }
454
455    fn thread_pool(&self) -> &rayon::ThreadPool {
456        self.thread_pool.get_or_init(|| {
457            trace!(threads = self.threads(), "building rayon thread pool");
458            self.thread_pool_builder()
459                .spawn_handler(|thread| {
460                    let mut builder = std::thread::Builder::new();
461                    if let Some(name) = thread.name() {
462                        builder = builder.name(name.to_string());
463                    }
464                    if let Some(size) = thread.stack_size() {
465                        builder = builder.stack_size(size);
466                    }
467                    let globals = self.globals.clone();
468                    builder.spawn(move || globals.set(|| thread.run()))?;
469                    Ok(())
470                })
471                .build()
472                .unwrap_or_else(|e| self.handle_thread_pool_build_error(e))
473        })
474    }
475
476    fn thread_pool_builder(&self) -> rayon::ThreadPoolBuilder {
477        let threads = self.threads();
478        debug_assert!(threads > 0, "number of threads must already be resolved");
479        let mut builder = rayon::ThreadPoolBuilder::new()
480            .thread_name(|i| format!("solar-{i}"))
481            .num_threads(threads);
482        // We still want to use a rayon thread pool with 1 thread so that `ParallelIterator`s don't
483        // install and run in the default global thread pool.
484        if threads == 1 {
485            builder = builder.use_current_thread();
486        }
487        builder
488    }
489
490    #[cold]
491    fn handle_thread_pool_build_error(&self, e: rayon::ThreadPoolBuildError) -> ! {
492        let mut err = self.dcx.fatal(format!("failed to build the rayon thread pool: {e}"));
493        if self.is_parallel() {
494            if SINGLE_THREADED_TARGET {
495                err = err.note("the current target might not support multi-threaded execution");
496            }
497            err = err.help("try running with `--threads 1` / `-j1` to disable parallelism");
498        }
499        err.emit()
500    }
501}
502
503fn reentrant_log() {
504    debug!(
505        "running in the current thread's rayon thread pool; \
506         this could cause panics later on if it was created without setting the session globals!"
507    );
508}
509
510#[inline]
511fn in_rayon() -> bool {
512    rayon::current_thread_index().is_some()
513}
514
515#[cfg(test)]
516mod tests {
517    use super::*;
518    use std::path::PathBuf;
519
520    /// Session to test `enter`.
521    fn enter_tests_session() -> Session {
522        let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
523        sess.source_map().new_source_file(PathBuf::from("test"), "abcd").unwrap();
524        sess
525    }
526
527    #[track_caller]
528    fn use_globals_parallel(sess: &Session) {
529        use rayon::prelude::*;
530
531        use_globals();
532        sess.spawn(|| use_globals());
533        sess.join(|| use_globals(), || use_globals());
534        [1, 2, 3].par_iter().for_each(|_| use_globals());
535        use_globals();
536    }
537
538    #[track_caller]
539    fn use_globals() {
540        use_globals_no_sm();
541
542        let span = crate::Span::new(crate::BytePos(0), crate::BytePos(1));
543        assert_eq!(format!("{span:?}"), "test:1:1: 1:2");
544        assert_eq!(format!("{span:#?}"), "test:1:1: 1:2");
545    }
546
547    #[track_caller]
548    fn use_globals_no_sm() {
549        SessionGlobals::with(|_globals| {});
550
551        let s = "hello";
552        let sym = crate::Symbol::intern(s);
553        assert_eq!(sym.as_str(), s);
554    }
555
556    #[track_caller]
557    fn cant_use_globals() {
558        std::panic::catch_unwind(|| use_globals()).unwrap_err();
559    }
560
561    #[test]
562    fn builder() {
563        let _ = Session::builder().with_stderr_emitter().build();
564    }
565
566    #[test]
567    fn not_builder() {
568        let _ = Session::new(Opts::default());
569        let _ = Session::default();
570    }
571
572    #[test]
573    #[should_panic = "session source map does not match the one in the diagnostics context"]
574    fn sm_mismatch() {
575        let sm1 = Arc::<SourceMap>::default();
576        let sm2 = Arc::<SourceMap>::default();
577        assert!(!Arc::ptr_eq(&sm1, &sm2));
578        Session::builder().source_map(sm1).dcx(DiagCtxt::with_stderr_emitter(Some(sm2))).build();
579    }
580
581    #[test]
582    #[should_panic = "either diagnostics context or options must be set"]
583    fn no_dcx() {
584        Session::builder().build();
585    }
586
587    #[test]
588    fn dcx() {
589        let _ = Session::builder().dcx(DiagCtxt::with_stderr_emitter(None)).build();
590        let _ =
591            Session::builder().dcx(DiagCtxt::with_stderr_emitter(Some(Default::default()))).build();
592    }
593
594    #[test]
595    fn local() {
596        let sess = Session::builder().with_stderr_emitter().build();
597        assert!(sess.emitted_diagnostics().is_none());
598        assert!(sess.emitted_errors().is_none());
599
600        let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
601        sess.dcx.err("test").emit();
602        let err = sess.dcx.emitted_errors().unwrap().unwrap_err();
603        let err = Box::new(err) as Box<dyn std::error::Error>;
604        assert!(err.to_string().contains("error: test"), "{err:?}");
605    }
606
607    #[test]
608    fn enter() {
609        crate::enter(|| {
610            use_globals_no_sm();
611            cant_use_globals();
612        });
613
614        let sess = enter_tests_session();
615        sess.enter(|| use_globals());
616        assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
617        assert!(sess.dcx.emitted_errors().unwrap().is_ok());
618        sess.enter(|| {
619            use_globals();
620            sess.enter(|| use_globals());
621            use_globals();
622        });
623        assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
624        assert!(sess.dcx.emitted_errors().unwrap().is_ok());
625
626        sess.enter_sequential(|| {
627            use_globals();
628            sess.enter(|| {
629                use_globals_parallel(&sess);
630                sess.enter(|| use_globals_parallel(&sess));
631                use_globals_parallel(&sess);
632            });
633            use_globals();
634        });
635        assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
636        assert!(sess.dcx.emitted_errors().unwrap().is_ok());
637    }
638
639    #[test]
640    fn enter_diags() {
641        let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
642        assert!(sess.dcx.emitted_errors().unwrap().is_ok());
643        sess.enter(|| {
644            sess.dcx.err("test1").emit();
645            assert!(sess.dcx.emitted_errors().unwrap().is_err());
646        });
647        assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test1"));
648        sess.enter(|| {
649            sess.dcx.err("test2").emit();
650            assert!(sess.dcx.emitted_errors().unwrap().is_err());
651        });
652        assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test1"));
653        assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test2"));
654    }
655
656    #[test]
657    fn enter_thread_pool() {
658        let sess = enter_tests_session();
659
660        assert!(!in_rayon());
661        sess.enter(|| {
662            assert!(in_rayon());
663            sess.enter(|| {
664                assert!(in_rayon());
665            });
666            assert!(in_rayon());
667        });
668        sess.enter_sequential(|| {
669            assert!(!in_rayon());
670            sess.enter(|| {
671                assert!(in_rayon());
672            });
673            assert!(!in_rayon());
674            sess.enter_sequential(|| {
675                assert!(!in_rayon());
676            });
677            assert!(!in_rayon());
678        });
679        assert!(!in_rayon());
680
681        let pool = rayon::ThreadPoolBuilder::new().build().unwrap();
682        pool.install(|| {
683            assert!(in_rayon());
684            cant_use_globals();
685            sess.enter(|| use_globals_parallel(&sess));
686            assert!(in_rayon());
687            cant_use_globals();
688        });
689        assert!(!in_rayon());
690    }
691
692    #[test]
693    fn enter_different_nested_sessions() {
694        let sess1 = enter_tests_session();
695        let sess2 = enter_tests_session();
696        assert!(!sess1.globals.maybe_eq(&sess2.globals));
697        sess1.enter(|| {
698            SessionGlobals::with(|g| assert!(g.maybe_eq(&sess1.globals)));
699            use_globals();
700            sess2.enter(|| {
701                SessionGlobals::with(|g| assert!(g.maybe_eq(&sess2.globals)));
702                use_globals();
703            });
704            use_globals();
705        });
706    }
707
708    #[test]
709    fn set_opts() {
710        let _ = Session::builder()
711            .with_test_emitter()
712            .opts(Opts {
713                evm_version: solar_config::EvmVersion::Berlin,
714                unstable: UnstableOpts { ast_stats: false, ..Default::default() },
715                ..Default::default()
716            })
717            .build();
718    }
719}