spade/
lib.rs

1pub mod compiler_state;
2pub mod error_handling;
3mod name_dump;
4pub mod namespaced_file;
5
6use compiler_state::{CompilerState, MirContext};
7use error_handling::{ErrorHandler, Reportable};
8use logos::Logos;
9use spade_ast_lowering::id_tracker::ExprIdTracker;
10use spade_codespan_reporting::term::termcolor::Buffer;
11use spade_common::location_info::{Loc, WithLocation};
12pub use spade_common::namespace::ModuleNamespace;
13use spade_diagnostics::diag_list::DiagList;
14use spade_hir::expression::Safety;
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        safety: Safety::Default,
169    };
170
171    // Add all "root" project main.spade modules
172    for root in module_asts
173        .iter()
174        .filter(|(ns, _ast)| ns.base_namespace == ns.namespace)
175    {
176        let namespace = &root.0;
177        if !namespace.namespace.0.is_empty() {
178            let name_id = ctx.symtab.add_thing(
179                namespace.namespace.clone(),
180                spade_hir::symbol_table::Thing::Module(
181                    namespace.namespace.0.last().unwrap().clone(),
182                ),
183            );
184            ctx.item_list.modules.insert(
185                name_id.clone(),
186                spade_hir::Module {
187                    name: name_id.at_loc(&root.1),
188                    documentation: "".to_string(),
189                },
190            );
191        }
192    }
193
194    let mut missing_namespace_set = module_asts
195        .iter()
196        .map(|(ns, _ast)| (ns.namespace.clone(), ns.file.clone()))
197        .collect::<HashMap<_, _>>();
198
199    for (namespace, module_ast) in &module_asts {
200        do_in_namespace(namespace, &mut ctx, &mut |ctx| {
201            global_symbols::handle_external_modules(
202                &namespace.file,
203                None,
204                module_ast,
205                &mut missing_namespace_set,
206                ctx,
207            )
208            .or_report(&mut errors);
209        })
210    }
211
212    if errors.failed_now() {
213        unfinished_artefacts.symtab = Some(ctx.symtab);
214        return Err(CompilationResult::EarlyFailure(unfinished_artefacts));
215    }
216
217    for err in global_symbols::report_missing_mod_declarations(&module_asts, &missing_namespace_set)
218    {
219        errors.report(&err);
220    }
221
222    errors.errors_are_recoverable();
223
224    for (namespace, module_ast) in &module_asts {
225        do_in_namespace(namespace, &mut ctx, &mut |ctx| {
226            global_symbols::gather_types(module_ast, ctx).or_report(&mut errors);
227        })
228    }
229
230    if errors.failed_now() {
231        unfinished_artefacts.symtab = Some(ctx.symtab);
232        errors.drain_diag_list(&mut ctx.diags);
233        return Err(CompilationResult::EarlyFailure(unfinished_artefacts));
234    }
235
236    for (namespace, module_ast) in &module_asts {
237        do_in_namespace(namespace, &mut ctx, &mut |ctx| {
238            global_symbols::gather_symbols(module_ast, ctx).or_report(&mut errors);
239        })
240    }
241
242    unfinished_artefacts.item_list = Some(ctx.item_list.clone());
243
244    if errors.failed_now() {
245        unfinished_artefacts.symtab = Some(ctx.symtab);
246        errors.drain_diag_list(&mut ctx.diags);
247        return Err(CompilationResult::EarlyFailure(unfinished_artefacts));
248    }
249    // Let's prevent using this thing again
250    let _unfinished_artefacts = unfinished_artefacts;
251
252    lower_ast(&module_asts, &mut ctx, &mut errors);
253
254    let AstLoweringCtx {
255        symtab,
256        mut item_list,
257        mut idtracker,
258        impl_idtracker,
259        pipeline_ctx: _,
260        self_ctx: _,
261        current_unit: _,
262        mut diags,
263        safety: _,
264    } = ctx;
265
266    errors.drain_diag_list(&mut diags);
267
268    for e in ensure_unique_anonymous_traits(&mut item_list) {
269        errors.report(&e)
270    }
271
272    let mut frozen_symtab = symtab.freeze();
273
274    let mut impl_type_state = TypeState::fresh();
275    let mapped_trait_impls = impl_type_state.visit_impl_blocks(&item_list);
276
277    errors.drain_diag_list(&mut impl_type_state.diags);
278
279    let type_inference_ctx = typeinference::Context {
280        symtab: frozen_symtab.symtab(),
281        items: &item_list,
282        trait_impls: &mapped_trait_impls,
283    };
284
285    let mut type_states = BTreeMap::new();
286
287    let executables_and_types = item_list
288        .executables
289        .iter()
290        .filter_map(|(name, item)| match item {
291            ExecutableItem::Unit(u) => {
292                let mut type_state = impl_type_state.create_child();
293
294                let result = type_state
295                    .visit_unit(u, &type_inference_ctx)
296                    .report(&mut errors);
297
298                let failures = type_state.diags.errors.len() != 0;
299                errors.drain_diag_list(&mut type_state.diags);
300
301                // Later stages will fail if we don't have a a complete type state,
302                // so we'll need to filter out modules that failed. However, for the LSP
303                // we still want to retain the incomplete type state
304                type_states.insert(name.clone(), type_state.clone());
305
306                if let Ok(()) = result {
307                    if opts.print_type_traceback {
308                        type_state.print_equations();
309                        println!("{}", format_trace_stack(&type_state));
310                    }
311                    if !failures {
312                        Some((name, (item, type_state)))
313                    } else {
314                        None
315                    }
316                } else {
317                    if opts.print_type_traceback {
318                        type_state.print_equations();
319                        println!("{}", format_trace_stack(&type_state))
320                    }
321                    None
322                }
323            }
324            ExecutableItem::EnumInstance { .. } => None,
325            ExecutableItem::StructInstance { .. } => None,
326            ExecutableItem::ExternUnit(_, _) => None,
327        })
328        .collect::<BTreeMap<_, _>>();
329
330    let mut name_source_map = NameSourceMap::new();
331    let mir_entities = spade_hir_lowering::monomorphisation::compile_items(
332        &executables_and_types,
333        &mut frozen_symtab,
334        &mut idtracker,
335        &mut name_source_map,
336        &item_list,
337        &mut errors.diag_handler,
338        &opt_passes,
339        &impl_type_state,
340    );
341
342    let CodegenArtefacts {
343        bumpy_mir_entities,
344        flat_mir_entities,
345        module_code,
346        mir_code,
347        instance_map,
348        mir_context,
349    } = codegen(mir_entities, Rc::clone(&code), &mut errors, &mut idtracker);
350
351    let state = CompilerState {
352        code: code
353            .read()
354            .unwrap()
355            .dump_files()
356            .into_iter()
357            .map(|(n, s)| (n.to_string(), s.to_string()))
358            .collect(),
359        symtab: frozen_symtab,
360        idtracker,
361        impl_idtracker,
362        item_list: item_list.clone(),
363        name_source_map,
364        instance_map,
365        mir_context,
366    };
367
368    let code = code.read().unwrap();
369
370    if !errors.failed() {
371        if let Some(outfile) = opts.outfile {
372            std::fs::write(outfile, module_code.join("\n\n")).or_report(&mut errors);
373        }
374        if let Some(cpp_file) = opts.verilator_wrapper_output {
375            let cpp_code =
376                verilator_wrappers(&flat_mir_entities.iter().map(|e| &e.0).collect::<Vec<_>>());
377            std::fs::write(cpp_file, cpp_code).or_report(&mut errors);
378        }
379        if let Some(mir_output) = opts.mir_output {
380            std::fs::write(mir_output, mir_code.join("\n\n")).or_report(&mut errors);
381        }
382        if let Some(item_list_file) = opts.item_list_file {
383            let list = name_dump::list_names(&item_list);
384
385            match ron::to_string(&list) {
386                Ok(encoded) => {
387                    std::fs::write(item_list_file, encoded).or_report(&mut errors);
388                }
389                Err(e) => {
390                    errors.set_failed();
391                    println!("Failed to encode item list as RON {e:?}")
392                }
393            }
394        }
395        if let Some(state_dump_file) = opts.state_dump_file {
396            // let ron = ron::Options::default().without_recursion_limit();
397            match bincode::serde::encode_to_vec(&state, bincode::config::standard()) {
398                Ok(encoded) => {
399                    std::fs::write(state_dump_file, encoded).or_report(&mut errors);
400                }
401                Err(e) => {
402                    errors.set_failed();
403                    println!("Failed to encode compiler state info as bincode {:?}", e)
404                }
405            }
406        }
407        let artefacts = Artefacts {
408            bumpy_mir_entities,
409            flat_mir_entities,
410            code: code.clone(),
411            item_list,
412            impl_list: mapped_trait_impls,
413            state,
414            type_states,
415        };
416
417        Ok(artefacts)
418    } else {
419        let artefacts = Artefacts {
420            bumpy_mir_entities,
421            flat_mir_entities,
422            code: code.clone(),
423            item_list,
424            impl_list: mapped_trait_impls,
425            state,
426            type_states,
427        };
428
429        Err(CompilationResult::LateFailure(artefacts))
430    }
431}
432
433fn do_in_namespace(
434    namespace: &ModuleNamespace,
435    ctx: &mut AstLoweringCtx,
436    to_do: &mut dyn FnMut(&mut AstLoweringCtx),
437) {
438    for ident in &namespace.namespace.0 {
439        // NOTE: These identifiers do not have the correct file_id. However,
440        // as far as I know, they will never be part of an error, so we *should*
441        // be safe.
442        ctx.symtab.push_namespace(ident.clone());
443    }
444    ctx.symtab
445        .set_base_namespace(namespace.base_namespace.clone());
446    to_do(ctx);
447    ctx.symtab.set_base_namespace(SpadePath(vec![]));
448    for _ in &namespace.namespace.0 {
449        ctx.symtab.pop_namespace();
450    }
451}
452
453#[tracing::instrument(skip_all)]
454fn parse(
455    sources: Vec<(ModuleNamespace, String, String)>,
456    code: Rc<RwLock<CodeBundle>>,
457    print_parse_traceback: bool,
458    errors: &mut ErrorHandler,
459) -> Vec<(ModuleNamespace, Loc<ModuleBody>)> {
460    let mut module_asts = vec![];
461    // Read and parse input files
462    for (namespace, name, content) in sources {
463        let _span = tracing::span!(Level::TRACE, "source", ?name).entered();
464        let file_id = code.write().unwrap().add_file(name, content.clone());
465        let mut parser = Parser::new(lexer::TokenKind::lexer(&content), file_id);
466
467        let result = parser
468            .top_level_module_body()
469            .map_err(|e| {
470                if print_parse_traceback {
471                    println!("{}", spade_parser::format_parse_stack(&parser.parse_stack));
472                };
473                e
474            })
475            .or_report(errors);
476
477        errors.drain_diag_list(&mut parser.diags);
478
479        if let Some(ast) = result {
480            module_asts.push((namespace, ast))
481        }
482    }
483
484    module_asts
485}
486
487#[tracing::instrument(skip_all)]
488fn lower_ast(
489    module_asts: &[(ModuleNamespace, Loc<ModuleBody>)],
490    ctx: &mut AstLoweringCtx,
491    errors: &mut ErrorHandler,
492) {
493    for (namespace, module_ast) in module_asts {
494        // Cannot be done by do_in_namespace because the symtab has been moved
495        // into `ctx`
496        for ident in &namespace.namespace.0 {
497            // NOTE: These identifiers do not have the correct file_id. However,
498            // as far as I know, they will never be part of an error, so we *should*
499            // be safe.
500            ctx.symtab.push_namespace(ident.clone());
501        }
502        ctx.symtab
503            .set_base_namespace(namespace.base_namespace.clone());
504        visit_module_body(module_ast, ctx).or_report(errors);
505        ctx.symtab.set_base_namespace(SpadePath(vec![]));
506        for _ in &namespace.namespace.0 {
507            ctx.symtab.pop_namespace();
508        }
509    }
510}
511
512#[tracing::instrument(skip_all)]
513fn codegen(
514    mir_entities: Vec<Result<MirOutput, Diagnostic>>,
515    code: Rc<RwLock<CodeBundle>>,
516    errors: &mut ErrorHandler,
517    idtracker: &mut ExprIdTracker,
518) -> CodegenArtefacts {
519    let mut bumpy_mir_entities = vec![];
520    let mut flat_mir_entities = vec![];
521    let mut module_code = vec![];
522    let mut mir_code = vec![];
523    let mut instance_map = InstanceMap::new();
524    let mut mir_context = HashMap::new();
525
526    // Acts as a sanity check to catch if we ever attempt to use a wire that isn't
527    // defined, for example if a zero-sized wire is used.
528    module_code.push("`default_nettype none".into());
529
530    for mir in mir_entities {
531        if let Some(MirOutput {
532            mir,
533            type_state,
534            reg_name_map,
535        }) = mir.or_report(errors)
536        {
537            // Codegen breaks if not all statements are valid, and since we don't need
538            // codegen if there are errors, we can safely bail from codegen of units with errors
539            if mir
540                .statements
541                .iter()
542                .any(|stmt| matches!(stmt, spade_mir::Statement::Error))
543            {
544                continue;
545            }
546            bumpy_mir_entities.push(mir.clone());
547
548            let codegenable = prepare_codegen(mir, idtracker);
549
550            let code = spade_mir::codegen::entity_code(
551                &codegenable,
552                &mut instance_map,
553                &Some(code.read().unwrap().clone()),
554            );
555
556            mir_code.push(format!("{}", codegenable.0));
557
558            flat_mir_entities.push(codegenable.clone());
559
560            let (code, name_map) = code;
561            module_code.push(code.to_string());
562
563            mir_context.insert(
564                codegenable.0.name.source,
565                MirContext {
566                    reg_name_map: reg_name_map.clone(),
567                    type_state,
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
584macro_rules! sources {
585    ($(($base_namespace:expr, $namespace:expr, $filename:expr)),*$(,)?) => {
586        vec! [
587            $(
588                (
589                    ModuleNamespace {
590                        namespace: SpadePath::from_strs(&$namespace),
591                        base_namespace: SpadePath::from_strs(&$base_namespace),
592                        file: String::from($filename).replace("../", "<compiler dir>/")
593                    },
594                    String::from($filename).replace("../", "<compiler dir>/"),
595                    String::from(include_str!($filename))
596                )
597            ),*
598        ]
599    }
600}
601
602pub fn core_files() -> Vec<(ModuleNamespace, String, String)> {
603    sources! {
604        ([], [], "../core/core.spade"),
605    }
606}
607
608/// The spade source files which are included statically in the binary, rather
609/// than being passed on the command line. This includes the stdlib and prelude
610pub fn stdlib_and_prelude() -> Vec<(ModuleNamespace, String, String)> {
611    sources! {
612        ([], [], "../prelude/prelude.spade"),
613
614        (["std"], ["std"], "../stdlib/main.spade"),
615        (["std"], ["std", "array"], "../stdlib/array.spade"),
616        (["std"], ["std", "cdc"], "../stdlib/cdc.spade"),
617        (["std"], ["std", "conv"], "../stdlib/conv.spade"),
618        (["std"], ["std", "io"], "../stdlib/io.spade"),
619        (["std"], ["std", "mem"], "../stdlib/mem.spade"),
620        (["std"], ["std", "ops"], "../stdlib/ops.spade"),
621        (["std"], ["std", "option"], "../stdlib/option.spade"),
622        (["std"], ["std", "ports"], "../stdlib/ports.spade"),
623        (["std"], ["std", "undef"], "../stdlib/undef.spade"),
624    }
625}
626
627#[cfg(test)]
628mod tests {
629    use std::path::PathBuf;
630
631    /// Having to maintain the stdlib list is error prone, so having a test
632    /// here to verify that all files in stdlib/<file>.spade
633    #[test]
634    fn sanity_check_static_sources_stdlib_included() {
635        let included = super::stdlib_and_prelude()
636            .into_iter()
637            .filter_map(|(ns, file, _)| {
638                if ns.base_namespace.as_strs() == ["std"] {
639                    Some(
640                        PathBuf::from(file)
641                            .file_name()
642                            .map(|f| f.to_string_lossy().to_string()),
643                    )
644                } else {
645                    None
646                }
647            })
648            .collect::<Vec<_>>();
649
650        let missing_files = std::fs::read_dir("stdlib/")
651            .expect("Failed to read stdlib")
652            .into_iter()
653            .map(|f| {
654                f.unwrap()
655                    .path()
656                    .file_name()
657                    .map(|f| f.to_string_lossy().to_string())
658            })
659            .filter(|f| !included.contains(f))
660            .collect::<Vec<_>>();
661
662        assert_eq!(missing_files, vec![])
663    }
664}