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 itertools::Itertools;
9use logos::Logos;
10use rayon::prelude::*;
11use rustc_hash::FxHashMap as HashMap;
12use spade_ast_lowering::id_tracker::ExprIdTracker;
13use spade_codespan_reporting::term::termcolor::Buffer;
14use spade_common::location_info::{Loc, WithLocation};
15pub use spade_common::namespace::ModuleNamespace;
16use spade_diagnostics::diag_list::DiagList;
17use spade_hir::expression::Safety;
18use spade_mir::codegen::{prepare_codegen, Codegenable};
19use spade_mir::passes::deduplicate_mut_wires::DeduplicateMutWires;
20use spade_mir::unit_name::InstanceMap;
21use spade_mir::verilator_wrapper::verilator_wrappers;
22use spade_typeinference::traits::TraitImplList;
23use std::collections::BTreeMap;
24use std::io::Write;
25use std::path::PathBuf;
26use std::sync::{Arc, RwLock};
27use tracing::Level;
28use typeinference::TypeState;
29
30use spade_ast::ModuleBody;
31use spade_ast_lowering::{
32 ensure_unique_anonymous_traits, global_symbols, visit_module_body, Context as AstLoweringCtx,
33 SelfContext,
34};
35use spade_common::id_tracker::ImplIdTracker;
36use spade_common::name::{NameID, Path as SpadePath, Visibility};
37use spade_diagnostics::{CodeBundle, DiagHandler, Diagnostic};
38use spade_hir::symbol_table::SymbolTable;
39use spade_hir::{ExecutableItem, ItemList};
40use spade_hir_lowering::monomorphisation::MirOutput;
41use spade_hir_lowering::NameSourceMap;
42pub use spade_parser::lexer;
43use spade_parser::Parser;
44use spade_typeinference::trace_stack::format_trace_stack;
45use spade_typeinference::{self as typeinference, SharedTypeState};
46
47pub struct Opt<'b> {
48 pub error_buffer: &'b mut Buffer,
49 pub outfile: Option<PathBuf>,
50 pub mir_output: Option<PathBuf>,
51 pub verilator_wrapper_output: Option<PathBuf>,
52 pub state_dump_file: Option<PathBuf>,
53 pub item_list_file: Option<PathBuf>,
54 pub print_type_traceback: bool,
55 pub print_parse_traceback: bool,
56 pub opt_passes: Vec<String>,
57}
58
59pub struct Artefacts {
61 pub code: CodeBundle,
62 pub item_list: ItemList,
63 pub bumpy_mir_entities: Vec<spade_mir::Entity>,
65 pub flat_mir_entities: Vec<Codegenable>,
67 pub state: CompilerState,
68 pub impl_list: TraitImplList,
69 pub type_states: BTreeMap<NameID, TypeState>,
70}
71
72pub struct UnfinishedArtefacts {
74 pub code: CodeBundle,
75 pub symtab: Option<SymbolTable>,
76 pub item_list: Option<ItemList>,
77 pub type_states: Option<BTreeMap<NameID, TypeState>>,
78}
79
80pub enum CompilationResult {
81 EarlyFailure(UnfinishedArtefacts),
82 LateFailure(Artefacts),
83}
84
85struct CodegenArtefacts {
86 bumpy_mir_entities: Vec<spade_mir::Entity>,
87 flat_mir_entities: Vec<Codegenable>,
88 module_code: Vec<String>,
89 mir_code: Vec<String>,
90 instance_map: InstanceMap,
91 mir_context: HashMap<NameID, MirContext>,
92}
93
94#[tracing::instrument(skip_all)]
95pub fn compile(
96 mut sources: Vec<(ModuleNamespace, String, String)>,
97 include_stdlib_and_prelude: bool,
98 opts: Opt,
99 diag_handler: DiagHandler,
100) -> Result<Artefacts, CompilationResult> {
101 let mut symtab = SymbolTable::new();
102 let mut item_list = ItemList::new();
103
104 let mut sources = if include_stdlib_and_prelude {
105 let mut all_sources = stdlib_and_prelude();
109 all_sources.append(&mut sources);
110 all_sources
111 } else {
112 sources
113 };
114 sources.append(&mut core_files());
115
116 spade_ast_lowering::builtins::populate_symtab(&mut symtab, &mut item_list);
117
118 let code = Arc::new(RwLock::new(CodeBundle::new("".to_string())));
119
120 let mut errors = ErrorHandler::new(opts.error_buffer, diag_handler, Arc::clone(&code));
121
122 let module_asts = parse(
123 sources,
124 Arc::clone(&code),
125 opts.print_parse_traceback,
126 &mut errors,
127 );
128 errors.errors_are_recoverable();
129
130 let mut unfinished_artefacts = UnfinishedArtefacts {
131 code: code.read().unwrap().clone(),
132 symtab: None,
133 item_list: None,
134 type_states: None,
135 };
136
137 let pass_impls = spade_mir::passes::mir_passes();
138 let opt_passes = opts
139 .opt_passes
140 .iter()
141 .map(|pass| {
142 if let Some(pass) = pass_impls.get(pass.as_str()) {
143 Ok(pass.as_ref())
144 } else {
145 let err = format!("{pass} is not a known optimization pass.");
146 Err(err)
147 }
148 })
149 .collect::<Result<Vec<_>, _>>();
150 let mut opt_passes = match opt_passes {
151 Ok(p) => p,
152 Err(e) => {
153 errors.error_buffer.write_all(e.as_bytes()).unwrap();
154 return Err(CompilationResult::EarlyFailure(unfinished_artefacts));
155 }
156 };
157 let deduplicate_mut_wires = DeduplicateMutWires {};
159 opt_passes.push(&deduplicate_mut_wires);
160
161 let mut ctx = AstLoweringCtx {
162 symtab,
163 item_list,
164 idtracker: Arc::new(ExprIdTracker::new()),
165 impl_idtracker: ImplIdTracker::new(),
166 pipeline_ctx: None,
167 self_ctx: SelfContext::FreeStanding,
168 current_unit: None,
169 diags: DiagList::new(),
170 safety: Safety::Default,
171 };
172
173 for root in module_asts
175 .iter()
176 .filter(|(ns, _ast)| ns.base_namespace == ns.namespace)
177 {
178 let namespace = &root.0;
179 if !namespace.namespace.0.is_empty() {
180 let name_id = ctx.symtab.add_thing(
181 namespace.namespace.clone(),
182 spade_hir::symbol_table::Thing::Module(
183 ().nowhere(),
184 namespace.namespace.0.last().unwrap().unwrap_named().clone(),
185 ),
186 Some(Visibility::Implicit.nowhere()),
187 );
188 ctx.item_list.modules.insert(
189 name_id.clone(),
190 spade_hir::Module {
191 name: name_id.at_loc(&root.1),
192 documentation: "".to_string(),
193 },
194 );
195 }
196 }
197
198 let mut missing_namespace_set = module_asts
199 .iter()
200 .map(|(ns, _ast)| (ns.namespace.clone(), ns.file.clone()))
201 .collect::<HashMap<_, _>>();
202
203 for (namespace, module_ast) in &module_asts {
204 do_in_namespace(namespace, &mut ctx, &mut |ctx| {
205 global_symbols::handle_external_modules(
206 &namespace.file,
207 None,
208 module_ast,
209 &mut missing_namespace_set,
210 ctx,
211 )
212 .or_report(&mut errors);
213 })
214 }
215
216 if errors.failed_now() {
217 unfinished_artefacts.symtab = Some(ctx.symtab);
218 return Err(CompilationResult::EarlyFailure(unfinished_artefacts));
219 }
220
221 for err in global_symbols::report_missing_mod_declarations(&module_asts, &missing_namespace_set)
222 {
223 errors.report(&err);
224 }
225
226 errors.errors_are_recoverable();
227
228 for (namespace, module_ast) in &module_asts {
229 do_in_namespace(namespace, &mut ctx, &mut |ctx| {
230 global_symbols::gather_types(module_ast, ctx).or_report(&mut errors);
231 })
232 }
233
234 if errors.failed_now() {
235 unfinished_artefacts.symtab = Some(ctx.symtab);
236 errors.drain_diag_list(&mut ctx.diags);
237 return Err(CompilationResult::EarlyFailure(unfinished_artefacts));
238 }
239
240 for (namespace, module_ast) in &module_asts {
241 do_in_namespace(namespace, &mut ctx, &mut |ctx| {
242 global_symbols::gather_symbols(module_ast, ctx).or_report(&mut errors);
243 })
244 }
245
246 unfinished_artefacts.item_list = Some(ctx.item_list.clone());
247
248 if errors.failed_now() {
249 unfinished_artefacts.symtab = Some(ctx.symtab);
250 errors.drain_diag_list(&mut ctx.diags);
251 return Err(CompilationResult::EarlyFailure(unfinished_artefacts));
252 }
253 let _unfinished_artefacts = unfinished_artefacts;
255
256 lower_ast(&module_asts, &mut ctx, &mut errors);
257
258 let AstLoweringCtx {
259 symtab,
260 mut item_list,
261 idtracker,
262 impl_idtracker,
263 pipeline_ctx: _,
264 self_ctx: _,
265 current_unit: _,
266 mut diags,
267 safety: _,
268 } = ctx;
269
270 errors.drain_diag_list(&mut diags);
271
272 for e in ensure_unique_anonymous_traits(&mut item_list) {
273 errors.report(&e)
274 }
275
276 let frozen_symtab = symtab.freeze();
277
278 let shared_type_state = Arc::new(SharedTypeState::new());
279 let mut impl_type_state = TypeState::fresh(Arc::clone(&shared_type_state));
280 let mapped_trait_impls = impl_type_state.visit_impl_blocks(&item_list);
281
282 errors.drain_diag_list(&mut impl_type_state.owned.diags);
283
284 let type_inference_ctx = typeinference::Context {
285 symtab: frozen_symtab.symtab(),
286 items: &item_list,
287 trait_impls: &mapped_trait_impls,
288 };
289
290 let mut type_states = BTreeMap::new();
291
292 let executables_and_types = item_list
293 .executables
294 .iter()
295 .filter_map(|(name, item)| match item {
296 ExecutableItem::Unit(u) => {
297 let mut type_state = impl_type_state.create_child();
298
299 let result = type_state
300 .visit_unit(u, &type_inference_ctx)
301 .report(&mut errors);
302
303 let failures = type_state.owned.diags.errors.len() != 0;
304 errors.drain_diag_list(&mut type_state.owned.diags);
305
306 type_states.insert(name.clone(), type_state.clone());
310
311 if let Ok(()) = result {
312 if opts.print_type_traceback {
313 type_state.print_equations();
314 println!("{}", format_trace_stack(&type_state));
315 }
316 if !failures {
317 Some((name, (item, type_state)))
318 } else {
319 None
320 }
321 } else {
322 if opts.print_type_traceback {
323 type_state.print_equations();
324 println!("{}", format_trace_stack(&type_state))
325 }
326 None
327 }
328 }
329 ExecutableItem::EnumInstance { .. } => None,
330 ExecutableItem::StructInstance { .. } => None,
331 ExecutableItem::ExternUnit(_, _) => None,
332 })
333 .collect::<BTreeMap<_, _>>();
334
335 let name_source_map = Arc::new(RwLock::new(NameSourceMap::new()));
336 let mir_entities = spade_hir_lowering::monomorphisation::compile_items(
337 &executables_and_types,
338 &frozen_symtab,
339 &idtracker,
340 &name_source_map,
341 &item_list,
342 &opt_passes,
343 &impl_type_state,
344 &mapped_trait_impls,
345 );
346
347 let CodegenArtefacts {
348 bumpy_mir_entities,
349 flat_mir_entities,
350 module_code,
351 mir_code,
352 instance_map,
353 mir_context,
354 } = codegen(mir_entities, Arc::clone(&code), &mut errors, &idtracker);
355
356 let state = CompilerState {
357 code: code
358 .read()
359 .unwrap()
360 .dump_files()
361 .into_iter()
362 .map(|(n, s)| (n.to_string(), s.to_string()))
363 .collect(),
364 symtab: frozen_symtab,
365 idtracker,
366 impl_idtracker,
367 item_list: item_list.clone(),
368 name_source_map,
369 instance_map,
370 mir_context,
371 shared_type_state,
372 trait_impl_list: mapped_trait_impls.clone(),
373 };
374
375 let code = code.read().unwrap();
376
377 if !errors.failed() {
378 if let Some(outfile) = opts.outfile {
379 std::fs::write(outfile, module_code.join("\n\n")).or_report(&mut errors);
380 }
381 if let Some(cpp_file) = opts.verilator_wrapper_output {
382 let cpp_code =
383 verilator_wrappers(&flat_mir_entities.iter().map(|e| &e.0).collect::<Vec<_>>());
384 std::fs::write(cpp_file, cpp_code).or_report(&mut errors);
385 }
386 if let Some(mir_output) = opts.mir_output {
387 std::fs::write(mir_output, mir_code.join("\n\n")).or_report(&mut errors);
388 }
389 if let Some(item_list_file) = opts.item_list_file {
390 let list = name_dump::list_names(&item_list);
391
392 match ron::to_string(&list) {
393 Ok(encoded) => {
394 std::fs::write(item_list_file, encoded).or_report(&mut errors);
395 }
396 Err(e) => {
397 errors.set_failed();
398 println!("Failed to encode item list as RON {e:?}")
399 }
400 }
401 }
402
403 let stored_state = state.into_stored();
404
405 if let Some(state_dump_file) = opts.state_dump_file {
406 if std::env::var("SPADE_REPORT_SERIALIZED_SIZE").is_ok() {
407 spade_common::sizes::SerializedSize::report_size(&stored_state);
408 }
409
410 match postcard::to_stdvec(&stored_state) {
411 Ok(encoded) => {
412 std::fs::write(state_dump_file, encoded).or_report(&mut errors);
413 }
414 Err(e) => {
415 errors.set_failed();
416 println!("Failed to encode compiler state info as bincode {:?}", e)
417 }
418 }
419 }
420 let artefacts = Artefacts {
421 bumpy_mir_entities,
422 flat_mir_entities,
423 code: code.clone(),
424 item_list,
425 impl_list: mapped_trait_impls,
426 state: stored_state.into_compiler_state(),
427 type_states,
428 };
429
430 Ok(artefacts)
431 } else {
432 let artefacts = Artefacts {
433 bumpy_mir_entities,
434 flat_mir_entities,
435 code: code.clone(),
436 item_list,
437 impl_list: mapped_trait_impls,
438 state,
439 type_states,
440 };
441
442 Err(CompilationResult::LateFailure(artefacts))
443 }
444}
445
446fn do_in_namespace(
447 namespace: &ModuleNamespace,
448 ctx: &mut AstLoweringCtx,
449 to_do: &mut dyn FnMut(&mut AstLoweringCtx),
450) {
451 for segment in &namespace.namespace.0 {
452 ctx.symtab.push_namespace(segment.clone());
456 }
457 ctx.symtab
458 .set_base_namespace(namespace.base_namespace.clone());
459 to_do(ctx);
460 ctx.symtab.set_base_namespace(SpadePath(vec![]));
461 for _ in &namespace.namespace.0 {
462 ctx.symtab.pop_namespace();
463 }
464}
465
466#[tracing::instrument(skip_all)]
467fn parse(
468 sources: Vec<(ModuleNamespace, String, String)>,
469 code: Arc<RwLock<CodeBundle>>,
470 print_parse_traceback: bool,
471 errors: &mut ErrorHandler,
472) -> Vec<(ModuleNamespace, Loc<ModuleBody>)> {
473 let mut module_asts = vec![];
474 for (namespace, name, content) in sources {
476 let _span = tracing::span!(Level::TRACE, "source", ?name).entered();
477 let file_id = code.write().unwrap().add_file(name, content.clone());
478 let mut parser = Parser::new(lexer::TokenKind::lexer(&content), file_id);
479
480 let result = parser
481 .top_level_module_body()
482 .map_err(|e| {
483 if print_parse_traceback {
484 println!("{}", spade_parser::format_parse_stack(&parser.parse_stack));
485 };
486 e
487 })
488 .or_report(errors);
489
490 errors.drain_diag_list(&mut parser.diags);
491
492 if let Some(ast) = result {
493 module_asts.push((namespace, ast))
494 }
495 }
496
497 module_asts
498}
499
500#[tracing::instrument(skip_all)]
501fn lower_ast(
502 module_asts: &[(ModuleNamespace, Loc<ModuleBody>)],
503 ctx: &mut AstLoweringCtx,
504 errors: &mut ErrorHandler,
505) {
506 for (namespace, module_ast) in module_asts {
507 for segment in &namespace.namespace.0 {
510 ctx.symtab.push_namespace(segment.clone());
514 }
515 ctx.symtab
516 .set_base_namespace(namespace.base_namespace.clone());
517 visit_module_body(module_ast, ctx).or_report(errors);
518 ctx.symtab.set_base_namespace(SpadePath(vec![]));
519 for _ in &namespace.namespace.0 {
520 ctx.symtab.pop_namespace();
521 }
522 }
523}
524
525struct CodegenArtefact {
526 bumpy_mir_entity: spade_mir::Entity,
527 flat_mir_entity: Codegenable,
528 local_module_code: String,
529 local_mir_code: String,
530 local_instance_map: InstanceMap,
531 local_mir_context: (NameID, MirContext),
532}
533
534#[tracing::instrument(skip_all)]
535fn codegen(
536 mir_entities: Vec<Result<MirOutput, Diagnostic>>,
537 code: Arc<RwLock<CodeBundle>>,
538 error_handler: &mut ErrorHandler,
539 idtracker: &Arc<ExprIdTracker>,
540) -> CodegenArtefacts {
541 let codegen_results = mir_entities
542 .into_iter()
543 .filter_map(|m| match m {
544 Ok(mir) => Some(mir),
545 Err(e) => {
546 e.or_report(error_handler);
547 None
548 }
549 })
550 .collect::<Vec<_>>()
551 .into_par_iter()
552 .enumerate()
553 .filter_map(
554 |(
555 i,
556 MirOutput {
557 mir,
558 type_state,
559 reg_name_map,
560 },
561 )| {
562 if mir
565 .statements
566 .iter()
567 .any(|stmt| matches!(stmt, spade_mir::Statement::Error))
568 {
569 return None;
570 }
571 let bumpy_mir_entity = mir.clone();
572
573 let codegenable = prepare_codegen(mir, idtracker);
574
575 let mut local_instance_map = InstanceMap::new();
576 let code = spade_mir::codegen::entity_code(
577 &codegenable,
578 &mut local_instance_map,
579 &Some(code.read().unwrap().clone()),
580 );
581
582 let flat_mir_entity = codegenable.clone();
583
584 let (code, name_map) = code;
585
586 let mir_context = (
587 codegenable.0.name.source.clone(),
588 MirContext {
589 reg_name_map: reg_name_map.clone(),
590 type_state,
591 verilog_name_map: name_map,
592 },
593 );
594 Some((
595 i,
596 CodegenArtefact {
597 bumpy_mir_entity,
598 flat_mir_entity,
599 local_module_code: code.to_string(),
600 local_mir_code: codegenable.0.to_string(),
601 local_instance_map,
602 local_mir_context: mir_context,
603 },
604 ))
605 },
606 )
607 .collect::<Vec<_>>();
608
609 let mut bumpy_mir_entities = Vec::with_capacity(codegen_results.len());
610 let mut flat_mir_entities = Vec::with_capacity(codegen_results.len());
611 let mut module_code = Vec::with_capacity(codegen_results.len());
612 let mut mir_code = Vec::with_capacity(codegen_results.len());
613 let mut instance_map = InstanceMap::new();
614 let mut mir_context = HashMap::default();
615
616 for CodegenArtefact {
617 bumpy_mir_entity,
618 flat_mir_entity,
619 local_module_code,
620 local_mir_code,
621 local_instance_map,
622 local_mir_context,
623 } in codegen_results
624 .into_iter()
625 .sorted_by_key(|(k, _)| *k)
626 .map(|(_, v)| v)
627 {
628 bumpy_mir_entities.push(bumpy_mir_entity);
629 flat_mir_entities.push(flat_mir_entity);
630 module_code.push(local_module_code);
631 mir_code.push(local_mir_code);
632 instance_map
633 .inner
634 .extend(&mut local_instance_map.inner.into_iter());
635 mir_context.insert(local_mir_context.0, local_mir_context.1);
636 }
637
638 module_code.push("`default_nettype none".into());
641
642 CodegenArtefacts {
643 bumpy_mir_entities,
644 flat_mir_entities,
645 module_code,
646 mir_code,
647 instance_map,
648 mir_context,
649 }
650}
651
652macro_rules! sources {
653 ($(($base_namespace:expr, $namespace:expr, $filename:expr)),*$(,)?) => {
654 vec! [
655 $(
656 (
657 ModuleNamespace {
658 namespace: SpadePath::from_strs(&$namespace),
659 base_namespace: SpadePath::from_strs(&$base_namespace),
660 file: String::from($filename).replace("../", "<compiler dir>/")
661 },
662 String::from($filename).replace("../", "<compiler dir>/"),
663 String::from(include_str!($filename))
664 )
665 ),*
666 ]
667 }
668}
669
670pub fn core_files() -> Vec<(ModuleNamespace, String, String)> {
671 sources! {
672 ([], [], "../core/core.spade"),
673 }
674}
675
676pub fn stdlib_and_prelude() -> Vec<(ModuleNamespace, String, String)> {
679 sources! {
680 ([], [], "../prelude/prelude.spade"),
681
682 (["std"], ["std"], "../stdlib/main.spade"),
683 (["std"], ["std", "array"], "../stdlib/array.spade"),
684 (["std"], ["std", "cdc"], "../stdlib/cdc.spade"),
685 (["std"], ["std", "conv"], "../stdlib/conv.spade"),
686 (["std"], ["std", "io"], "../stdlib/io.spade"),
687 (["std"], ["std", "mem"], "../stdlib/mem.spade"),
688 (["std"], ["std", "ops"], "../stdlib/ops.spade"),
689 (["std"], ["std", "option"], "../stdlib/option.spade"),
690 (["std"], ["std", "ports"], "../stdlib/ports.spade"),
691 (["std"], ["std", "undef"], "../stdlib/undef.spade"),
692 }
693}
694
695#[cfg(test)]
696mod tests {
697 use std::path::PathBuf;
698
699 #[test]
702 fn sanity_check_static_sources_stdlib_included() {
703 let included = super::stdlib_and_prelude()
704 .into_iter()
705 .filter_map(|(ns, file, _)| {
706 if ns.base_namespace.to_named_strs() == [Some("std")] {
707 Some(
708 PathBuf::from(file)
709 .file_name()
710 .map(|f| f.to_string_lossy().to_string()),
711 )
712 } else {
713 None
714 }
715 })
716 .collect::<Vec<_>>();
717
718 let missing_files = std::fs::read_dir("stdlib/")
719 .expect("Failed to read stdlib")
720 .into_iter()
721 .map(|f| {
722 f.unwrap()
723 .path()
724 .file_name()
725 .map(|f| f.to_string_lossy().to_string())
726 })
727 .filter(|f| !included.contains(f))
728 .collect::<Vec<_>>();
729
730 assert_eq!(missing_files, vec![])
731 }
732}