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 fn or_report(self, errors: &mut ErrorHandler) -> Option<T>;
56
57 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 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
97pub struct Artefacts {
99 pub code: CodeBundle,
100 pub item_list: ItemList,
101 pub bumpy_mir_entities: Vec<spade_mir::Entity>,
103 pub flat_mir_entities: Vec<Codegenable>,
105 pub state: CompilerState,
106 pub type_states: BTreeMap<NameID, TypeState>,
107}
108
109pub 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 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 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 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 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 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 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 for ident in &namespace.namespace.0 {
508 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 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
584pub 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 #[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}