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
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 };
169
170 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 _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 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 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 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 for ident in &namespace.namespace.0 {
489 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
587pub 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 #[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}