spade/
lib.rs

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