spade/
lib.rs

1pub mod compiler_state;
2mod name_dump;
3pub mod namespaced_file;
4
5use compiler_state::{CompilerState, MirContext};
6use logos::Logos;
7use ron::ser::PrettyConfig;
8use spade_ast_lowering::id_tracker::ExprIdTracker;
9use spade_codespan_reporting::term::termcolor::Buffer;
10use spade_common::location_info::Loc;
11pub use spade_common::namespace::ModuleNamespace;
12use spade_mir::codegen::{prepare_codegen, Codegenable};
13use spade_mir::passes::deduplicate_mut_wires::DeduplicateMutWires;
14use spade_mir::unit_name::InstanceMap;
15use spade_mir::verilator_wrapper::verilator_wrappers;
16use std::collections::{BTreeMap, HashMap};
17use std::io::Write;
18use std::path::PathBuf;
19use std::rc::Rc;
20use std::sync::RwLock;
21use tracing::Level;
22use typeinference::TypeState;
23
24use spade_ast::ModuleBody;
25use spade_ast_lowering::{
26    ensure_unique_anonymous_traits, global_symbols, visit_module_body, Context as AstLoweringCtx,
27    SelfContext,
28};
29use spade_common::id_tracker::ImplIdTracker;
30use spade_common::name::{NameID, Path as SpadePath};
31use spade_diagnostics::{CodeBundle, CompilationError, DiagHandler, Diagnostic};
32use spade_hir::symbol_table::SymbolTable;
33use spade_hir::{ExecutableItem, ItemList};
34use spade_hir_lowering::monomorphisation::MirOutput;
35use spade_hir_lowering::NameSourceMap;
36pub use spade_parser::lexer;
37use spade_parser::Parser;
38use spade_typeinference as typeinference;
39use spade_typeinference::trace_stack::format_trace_stack;
40
41pub struct Opt<'b> {
42    pub error_buffer: &'b mut Buffer,
43    pub outfile: Option<PathBuf>,
44    pub mir_output: Option<PathBuf>,
45    pub verilator_wrapper_output: Option<PathBuf>,
46    pub state_dump_file: Option<PathBuf>,
47    pub item_list_file: Option<PathBuf>,
48    pub print_type_traceback: bool,
49    pub print_parse_traceback: bool,
50    pub opt_passes: Vec<String>,
51}
52
53trait Reportable<T> {
54    /// Report the error, then discard the error, returning Some if it was Ok
55    fn or_report(self, errors: &mut ErrorHandler) -> Option<T>;
56
57    // Report the error and continue without modifying the result
58    fn report(self, errors: &mut ErrorHandler) -> Self;
59}
60
61impl<T, E> Reportable<T> for Result<T, E>
62where
63    E: CompilationError,
64{
65    fn report(self, errors: &mut ErrorHandler) -> Self {
66        if let Err(e) = &self {
67            errors.report(e);
68        }
69        self
70    }
71
72    fn or_report(self, errors: &mut ErrorHandler) -> Option<T> {
73        self.report(errors).ok()
74    }
75}
76
77pub struct ErrorHandler<'a> {
78    pub failed: bool,
79    pub error_buffer: &'a mut Buffer,
80    pub diag_handler: DiagHandler,
81    /// Using a RW lock here is just a lazy way of managing the ownership of code to
82    /// be able to report errors even while modifying CodeBundle
83    pub code: Rc<RwLock<CodeBundle>>,
84}
85
86impl<'a> ErrorHandler<'a> {
87    pub fn report(&mut self, err: &impl CompilationError) {
88        self.failed = true;
89        err.report(
90            self.error_buffer,
91            &self.code.read().unwrap(),
92            &mut self.diag_handler,
93        );
94    }
95}
96
97/// Compiler output.
98pub struct Artefacts {
99    pub code: CodeBundle,
100    pub item_list: ItemList,
101    // MIR entities before aliases have been flattened
102    pub bumpy_mir_entities: Vec<spade_mir::Entity>,
103    // MIR entities after flattening
104    pub flat_mir_entities: Vec<Codegenable>,
105    pub state: CompilerState,
106    pub type_states: BTreeMap<NameID, TypeState>,
107}
108
109/// Like [Artefacts], but if the compiler didn't finish due to errors.
110pub struct UnfinishedArtefacts {
111    pub code: CodeBundle,
112    pub item_list: Option<ItemList>,
113    pub type_states: Option<BTreeMap<NameID, TypeState>>,
114}
115
116struct CodegenArtefacts {
117    bumpy_mir_entities: Vec<spade_mir::Entity>,
118    flat_mir_entities: Vec<Codegenable>,
119    module_code: Vec<String>,
120    mir_code: Vec<String>,
121    instance_map: InstanceMap,
122    mir_context: HashMap<NameID, MirContext>,
123}
124
125#[tracing::instrument(skip_all)]
126pub fn compile(
127    mut sources: Vec<(ModuleNamespace, String, String)>,
128    include_stdlib_and_prelude: bool,
129    opts: Opt,
130    diag_handler: DiagHandler,
131) -> Result<Artefacts, UnfinishedArtefacts> {
132    let mut symtab = SymbolTable::new();
133    let mut item_list = ItemList::new();
134
135    let sources = if include_stdlib_and_prelude {
136        // We want to build stdlib and prelude before building user code,
137        // to give `previously defined <here>` pointing into user code, instead
138        // of stdlib code
139        let mut all_sources = stdlib_and_prelude();
140        all_sources.append(&mut sources);
141        all_sources
142    } else {
143        sources
144    };
145
146    spade_ast_lowering::builtins::populate_symtab(&mut symtab, &mut item_list);
147
148    let code = Rc::new(RwLock::new(CodeBundle::new("".to_string())));
149
150    let mut errors = ErrorHandler {
151        failed: false,
152        error_buffer: opts.error_buffer,
153        diag_handler,
154        code: Rc::clone(&code),
155    };
156
157    let module_asts = parse(
158        sources,
159        Rc::clone(&code),
160        opts.print_parse_traceback,
161        &mut errors,
162    );
163
164    let mut unfinished_artefacts = UnfinishedArtefacts {
165        code: code.read().unwrap().clone(),
166        item_list: None,
167        type_states: None,
168    };
169
170    let pass_impls = spade_mir::passes::mir_passes();
171    let opt_passes = opts
172        .opt_passes
173        .iter()
174        .map(|pass| {
175            if let Some(pass) = pass_impls.get(pass.as_str()) {
176                Ok(pass.as_ref())
177            } else {
178                let err = format!("{pass} is not a known optimization pass.");
179                Err(err)
180            }
181        })
182        .collect::<Result<Vec<_>, _>>();
183    let mut opt_passes = match opt_passes {
184        Ok(p) => p,
185        Err(e) => {
186            errors.error_buffer.write_all(e.as_bytes()).unwrap();
187            return Err(unfinished_artefacts);
188        }
189    };
190    // This is a non-optional pass that prevents codegen bugs
191    let deduplicate_mut_wires = DeduplicateMutWires {};
192    opt_passes.push(&deduplicate_mut_wires);
193
194    if errors.failed {
195        return Err(unfinished_artefacts);
196    }
197
198    let mut ctx = AstLoweringCtx {
199        symtab,
200        item_list,
201        idtracker: ExprIdTracker::new(),
202        impl_idtracker: ImplIdTracker::new(),
203        pipeline_ctx: None,
204        self_ctx: SelfContext::FreeStanding,
205    };
206
207    // Add all "root" project main.spade modules
208    for root in module_asts
209        .iter()
210        .filter(|(ns, _ast)| ns.base_namespace == ns.namespace)
211    {
212        let namespace = &root.0;
213        if !namespace.namespace.0.is_empty() {
214            ctx.symtab.add_thing(
215                namespace.namespace.clone(),
216                spade_hir::symbol_table::Thing::Module(
217                    namespace.namespace.0.last().unwrap().clone(),
218                ),
219            );
220        }
221    }
222
223    let mut missing_namespace_set = module_asts
224        .iter()
225        .map(|(ns, _ast)| (ns.namespace.clone(), ns.file.clone()))
226        .collect::<HashMap<_, _>>();
227
228    for (namespace, module_ast) in &module_asts {
229        do_in_namespace(namespace, &mut ctx, &mut |ctx| {
230            global_symbols::handle_external_modules(
231                &namespace.file,
232                None,
233                module_ast,
234                &mut missing_namespace_set,
235                ctx,
236            )
237            .or_report(&mut errors);
238        })
239    }
240
241    if errors.failed {
242        return Err(unfinished_artefacts);
243    }
244
245    for err in global_symbols::report_missing_mod_declarations(&module_asts, &missing_namespace_set)
246    {
247        errors.report(&err);
248    }
249
250    if errors.failed {
251        return Err(unfinished_artefacts);
252    }
253
254    for (namespace, module_ast) in &module_asts {
255        do_in_namespace(namespace, &mut ctx, &mut |ctx| {
256            global_symbols::gather_types(module_ast, ctx).or_report(&mut errors);
257        })
258    }
259
260    if errors.failed {
261        return Err(unfinished_artefacts);
262    }
263
264    for (namespace, module_ast) in &module_asts {
265        do_in_namespace(namespace, &mut ctx, &mut |ctx| {
266            global_symbols::gather_symbols(module_ast, ctx).or_report(&mut errors);
267        })
268    }
269
270    unfinished_artefacts.item_list = Some(ctx.item_list.clone());
271
272    if errors.failed {
273        return Err(unfinished_artefacts);
274    }
275
276    lower_ast(&module_asts, &mut ctx, &mut errors);
277
278    let AstLoweringCtx {
279        symtab,
280        item_list,
281        mut idtracker,
282        impl_idtracker,
283        pipeline_ctx: _,
284        self_ctx: _,
285    } = ctx;
286
287    unfinished_artefacts.item_list = Some(item_list.clone());
288
289    for e in ensure_unique_anonymous_traits(&item_list) {
290        errors.report(&e)
291    }
292
293    // If we have errors during AST lowering, we need to early return because the
294    // items have already been added to the symtab when they are detected. Further compilation
295    // relies on all names in the symtab being in the item list, which will not be the
296    // case if we failed to compile some
297    if errors.failed {
298        return Err(unfinished_artefacts);
299    }
300
301    let mut frozen_symtab = symtab.freeze();
302
303    let Ok(mapped_trait_impls) = TypeState::new().visit_impl_blocks(&item_list) else {
304        return Err(unfinished_artefacts);
305    };
306
307    let type_inference_ctx = typeinference::Context {
308        symtab: frozen_symtab.symtab(),
309        items: &item_list,
310        trait_impls: &mapped_trait_impls,
311    };
312
313    let executables_and_types = item_list
314        .executables
315        .iter()
316        .filter_map(|(name, item)| match item {
317            ExecutableItem::Unit(u) => {
318                let mut type_state = typeinference::TypeState::new();
319
320                if let Ok(()) = type_state
321                    .visit_unit(u, &type_inference_ctx)
322                    .report(&mut errors)
323                {
324                    if opts.print_type_traceback {
325                        type_state.print_equations();
326                        println!("{}", format_trace_stack(&type_state));
327                    }
328                    Some((name, (item, type_state)))
329                } else {
330                    if opts.print_type_traceback {
331                        type_state.print_equations();
332                        println!("{}", format_trace_stack(&type_state))
333                    }
334                    None
335                }
336            }
337            ExecutableItem::EnumInstance { .. } => None,
338            ExecutableItem::StructInstance { .. } => None,
339            ExecutableItem::ExternUnit(_, _) => None,
340        })
341        .collect::<BTreeMap<_, _>>();
342
343    if errors.failed {
344        return Err(unfinished_artefacts);
345    }
346
347    unfinished_artefacts.type_states = Some(
348        executables_and_types
349            .clone()
350            .into_iter()
351            .map(|(name_id, (_, type_state))| (name_id.clone(), type_state))
352            .collect::<BTreeMap<_, _>>(),
353    );
354
355    let mut name_source_map = NameSourceMap::new();
356    let mir_entities = spade_hir_lowering::monomorphisation::compile_items(
357        &executables_and_types,
358        &mut frozen_symtab,
359        &mut idtracker,
360        &mut name_source_map,
361        &item_list,
362        &mut errors.diag_handler,
363        &opt_passes,
364    );
365
366    let CodegenArtefacts {
367        bumpy_mir_entities,
368        flat_mir_entities,
369        module_code,
370        mir_code,
371        instance_map,
372        mir_context,
373    } = codegen(mir_entities, Rc::clone(&code), &mut errors, &mut idtracker);
374
375    let state = CompilerState {
376        code: code.read().unwrap().dump_files(),
377        symtab: frozen_symtab,
378        idtracker,
379        impl_idtracker,
380        item_list: item_list.clone(),
381        name_source_map,
382        instance_map,
383        mir_context,
384    };
385
386    if errors.failed {
387        return Err(unfinished_artefacts);
388    }
389
390    if let Some(outfile) = opts.outfile {
391        std::fs::write(outfile, module_code.join("\n\n")).or_report(&mut errors);
392    }
393    if let Some(cpp_file) = opts.verilator_wrapper_output {
394        let cpp_code =
395            verilator_wrappers(&flat_mir_entities.iter().map(|e| &e.0).collect::<Vec<_>>());
396        std::fs::write(cpp_file, cpp_code).or_report(&mut errors);
397    }
398    if let Some(mir_output) = opts.mir_output {
399        std::fs::write(mir_output, mir_code.join("\n\n")).or_report(&mut errors);
400    }
401    if let Some(item_list_file) = opts.item_list_file {
402        let list = name_dump::list_names(&item_list);
403
404        match ron::to_string(&list) {
405            Ok(encoded) => {
406                std::fs::write(item_list_file, encoded).or_report(&mut errors);
407            }
408            Err(e) => {
409                errors.failed = true;
410                println!("Failed to encode item list as RON {e:?}")
411            }
412        }
413    }
414    if let Some(state_dump_file) = opts.state_dump_file {
415        let ron = ron::Options::default().without_recursion_limit();
416
417        match ron.to_string_pretty(&state, PrettyConfig::default()) {
418            Ok(encoded) => {
419                std::fs::write(state_dump_file, encoded).or_report(&mut errors);
420            }
421            Err(e) => {
422                errors.failed = true;
423                println!("Failed to encode compiler state info as RON {:?}", e)
424            }
425        }
426    }
427
428    if errors.failed {
429        Err(unfinished_artefacts)
430    } else {
431        Ok(Artefacts {
432            bumpy_mir_entities,
433            flat_mir_entities,
434            code: code.read().unwrap().clone(),
435            item_list,
436            state,
437            type_states: unfinished_artefacts.type_states.unwrap(),
438        })
439    }
440}
441
442fn do_in_namespace(
443    namespace: &ModuleNamespace,
444    ctx: &mut AstLoweringCtx,
445    to_do: &mut dyn FnMut(&mut AstLoweringCtx),
446) {
447    for ident in &namespace.namespace.0 {
448        // NOTE: These identifiers do not have the correct file_id. However,
449        // as far as I know, they will never be part of an error, so we *should*
450        // be safe.
451        ctx.symtab.push_namespace(ident.clone());
452    }
453    ctx.symtab
454        .set_base_namespace(namespace.base_namespace.clone());
455    to_do(ctx);
456    ctx.symtab.set_base_namespace(SpadePath(vec![]));
457    for _ in &namespace.namespace.0 {
458        ctx.symtab.pop_namespace();
459    }
460}
461
462#[tracing::instrument(skip_all)]
463fn parse(
464    sources: Vec<(ModuleNamespace, String, String)>,
465    code: Rc<RwLock<CodeBundle>>,
466    print_parse_traceback: bool,
467    errors: &mut ErrorHandler,
468) -> Vec<(ModuleNamespace, Loc<ModuleBody>)> {
469    let mut module_asts = vec![];
470    // Read and parse input files
471    for (namespace, name, content) in sources {
472        let _span = tracing::span!(Level::TRACE, "source", ?name).entered();
473        let file_id = code.write().unwrap().add_file(name, content.clone());
474        let mut parser = Parser::new(lexer::TokenKind::lexer(&content), file_id);
475
476        let result = parser
477            .top_level_module_body()
478            .map_err(|e| {
479                if print_parse_traceback {
480                    println!("{}", spade_parser::format_parse_stack(&parser.parse_stack));
481                };
482                e
483            })
484            .or_report(errors);
485
486        for error in &parser.errors {
487            errors.report(error)
488        }
489
490        if let Some(ast) = result {
491            module_asts.push((namespace, ast))
492        }
493    }
494
495    module_asts
496}
497
498#[tracing::instrument(skip_all)]
499fn lower_ast(
500    module_asts: &[(ModuleNamespace, Loc<ModuleBody>)],
501    ctx: &mut AstLoweringCtx,
502    errors: &mut ErrorHandler,
503) {
504    for (namespace, module_ast) in module_asts {
505        // Cannot be done by do_in_namespace because the symtab has been moved
506        // into `ctx`
507        for ident in &namespace.namespace.0 {
508            // NOTE: These identifiers do not have the correct file_id. However,
509            // as far as I know, they will never be part of an error, so we *should*
510            // be safe.
511            ctx.symtab.push_namespace(ident.clone());
512        }
513        ctx.symtab
514            .set_base_namespace(namespace.base_namespace.clone());
515        visit_module_body(module_ast, ctx).or_report(errors);
516        ctx.symtab.set_base_namespace(SpadePath(vec![]));
517        for _ in &namespace.namespace.0 {
518            ctx.symtab.pop_namespace();
519        }
520    }
521}
522
523#[tracing::instrument(skip_all)]
524fn codegen(
525    mir_entities: Vec<Result<MirOutput, Diagnostic>>,
526    code: Rc<RwLock<CodeBundle>>,
527    errors: &mut ErrorHandler,
528    idtracker: &mut ExprIdTracker,
529) -> CodegenArtefacts {
530    let mut bumpy_mir_entities = vec![];
531    let mut flat_mir_entities = vec![];
532    let mut module_code = vec![];
533    let mut mir_code = vec![];
534    let mut instance_map = InstanceMap::new();
535    let mut mir_context = HashMap::new();
536
537    for mir in mir_entities {
538        if let Some(MirOutput {
539            mir,
540            type_state,
541            reg_name_map,
542        }) = mir.or_report(errors)
543        {
544            bumpy_mir_entities.push(mir.clone());
545
546            let codegenable = prepare_codegen(mir, idtracker);
547
548            let code = spade_mir::codegen::entity_code(
549                &codegenable,
550                &mut instance_map,
551                &Some(code.read().unwrap().clone()),
552            );
553
554            mir_code.push(format!("{}", codegenable.0));
555
556            flat_mir_entities.push(codegenable.clone());
557
558            let (code, name_map) = code;
559            module_code.push(code.to_string());
560
561            mir_context.insert(
562                codegenable.0.name.source,
563                MirContext {
564                    reg_name_map: reg_name_map.clone(),
565                    // lifeguard spade#254
566                    // FIXME: Insert pipeline register stuff into the type map
567                    type_map: type_state.into(),
568                    verilog_name_map: name_map,
569                },
570            );
571        }
572    }
573
574    CodegenArtefacts {
575        bumpy_mir_entities,
576        flat_mir_entities,
577        module_code,
578        mir_code,
579        instance_map,
580        mir_context,
581    }
582}
583
584/// The spade source files which are included statically in the binary, rather
585/// than being passed on the command line. This includes the stdlib and prelude
586pub fn stdlib_and_prelude() -> Vec<(ModuleNamespace, String, String)> {
587    macro_rules! sources {
588        ($(($base_namespace:expr, $namespace:expr, $filename:expr)),*$(,)?) => {
589            vec! [
590                $(
591                    (
592                        ModuleNamespace {
593                            namespace: SpadePath::from_strs(&$namespace),
594                            base_namespace: SpadePath::from_strs(&$base_namespace),
595                            file: String::from($filename).replace("../", "<compiler dir>/")
596                        },
597                        String::from($filename).replace("../", "<compiler dir>/"),
598                        String::from(include_str!($filename))
599                    )
600                ),*
601            ]
602        }
603    }
604
605    sources! {
606        ([], [], "../prelude/prelude.spade"),
607        (["std"], ["std"], "../stdlib/main.spade"),
608        (["std"], ["std", "conv"], "../stdlib/conv.spade"),
609        (["std"], ["std", "io"], "../stdlib/io.spade"),
610        (["std"], ["std", "mem"], "../stdlib/mem.spade"),
611        (["std"], ["std", "ops"], "../stdlib/ops.spade"),
612        (["std"], ["std", "ports"], "../stdlib/ports.spade"),
613        (["std"], ["std", "cdc"], "../stdlib/cdc.spade"),
614        (["std"], ["std", "option"], "../stdlib/option.spade"),
615    }
616}
617
618#[cfg(test)]
619mod tests {
620    use std::path::PathBuf;
621
622    /// Having to maintain the stdlib list is error prone, so having a test
623    /// here to verify that all files in stdlib/<file>.spade
624    #[test]
625    fn sanity_check_static_sources_stdlib_included() {
626        let included = super::stdlib_and_prelude()
627            .into_iter()
628            .filter_map(|(ns, file, _)| {
629                if ns.base_namespace.as_strs() == ["std"] {
630                    Some(
631                        PathBuf::from(file)
632                            .file_name()
633                            .map(|f| f.to_string_lossy().to_string()),
634                    )
635                } else {
636                    None
637                }
638            })
639            .collect::<Vec<_>>();
640
641        let missing_files = std::fs::read_dir("stdlib/")
642            .expect("Failed to read stdlib")
643            .into_iter()
644            .map(|f| {
645                f.unwrap()
646                    .path()
647                    .file_name()
648                    .map(|f| f.to_string_lossy().to_string())
649            })
650            .filter(|f| !included.contains(f))
651            .collect::<Vec<_>>();
652
653        assert_eq!(missing_files, vec![])
654    }
655}