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
57pub struct Artefacts {
59 pub code: CodeBundle,
60 pub item_list: ItemList,
61 pub bumpy_mir_entities: Vec<spade_mir::Entity>,
63 pub flat_mir_entities: Vec<Codegenable>,
65 pub state: CompilerState,
66 pub impl_list: TraitImplList,
67 pub type_states: BTreeMap<NameID, TypeState>,
68}
69
70pub 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 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 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 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 _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 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 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 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 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 for ident in &namespace.namespace.0 {
497 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 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 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
608pub 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 #[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}