1use std::convert::Infallible;
2use std::path::Path;
3
4use cranelift_entity::SecondaryMap;
5use ruka_frontend::{FileId, SourceLocation, SourceSpan};
6use thiserror::Error;
7
8use crate::check::{self, CheckedProgram};
9use crate::codegen;
10use crate::elab;
11use crate::hir::browser_graph::BrowserHirGraph;
12use crate::hir::{ExprId, HirProgram};
13use crate::meta;
14use crate::mir::browser_graph::BrowserMirGraph;
15use crate::mir::{self, MirFuncId, MirInstr, MirLocalId, MirProgram, MirSourcePos};
16use crate::modules;
17use crate::pass::{
18 PassContext, PassSnapshot, PassSnapshotField, PassSnapshotKind, PassSnapshotValue, PassTiming,
19 ProvenanceSummary,
20};
21use crate::syntax::browser_graph::BrowserAstGraph;
22use crate::syntax::{self, ast::Program};
23
24mod passes;
25
26pub trait Driver {
28 type Error: std::error::Error + 'static;
30
31 fn on_ast(&mut self, _program: &Program) -> Result<(), Self::Error> {
33 Ok(())
34 }
35
36 fn on_expanded_ast(&mut self, _program: &Program) -> Result<(), Self::Error> {
38 Ok(())
39 }
40
41 fn on_hir(&mut self, _program: &HirProgram) -> Result<(), Self::Error> {
43 Ok(())
44 }
45
46 fn on_hir_origins(&mut self, _origins: &HirOriginTable) -> Result<(), Self::Error> {
48 Ok(())
49 }
50
51 fn on_checked(&mut self, _program: &CheckedProgram) -> Result<(), Self::Error> {
53 Ok(())
54 }
55
56 fn on_mir(&mut self, _program: &MirProgram) -> Result<(), Self::Error> {
58 Ok(())
59 }
60
61 fn on_mir_origins(&mut self, _origins: &MirOriginTable) -> Result<(), Self::Error> {
63 Ok(())
64 }
65
66 fn on_pass_snapshot(&mut self, _snapshot: &PassSnapshot) -> Result<(), Self::Error> {
68 Ok(())
69 }
70
71 fn on_rust(&mut self, _source: &str) -> Result<(), Self::Error> {
73 Ok(())
74 }
75
76 fn on_wat(&mut self, _source: &str) -> Result<(), Self::Error> {
78 Ok(())
79 }
80
81 fn on_wasm(&mut self, _bytes: &[u8]) -> Result<(), Self::Error> {
83 Ok(())
84 }
85
86 fn on_wasm_diagnostics(
88 &mut self,
89 _diagnostics: &[codegen::wasm::WasmDiagnostic],
90 ) -> Result<(), Self::Error> {
91 Ok(())
92 }
93
94 fn on_pass_timings(&mut self, _timings: &[PassTiming]) -> Result<(), Self::Error> {
96 Ok(())
97 }
98
99 fn on_provenance_summary(&mut self, _summary: ProvenanceSummary) -> Result<(), Self::Error> {
101 Ok(())
102 }
103}
104
105pub struct NullDriver;
107
108impl Driver for NullDriver {
109 type Error = Infallible;
110}
111
112#[derive(Debug, Default)]
114pub struct HirOriginTable {
115 pub expr_origins: SecondaryMap<ExprId, Option<crate::pass::OriginId>>,
117}
118
119impl HirOriginTable {
120 pub fn mapping_count(&self) -> usize {
122 self.expr_origins
123 .iter()
124 .filter(|(_, origin)| origin.is_some())
125 .count()
126 }
127}
128
129#[derive(Debug, Default)]
131pub struct MirOriginTable {
132 pub local_origins:
134 SecondaryMap<MirFuncId, Option<SecondaryMap<MirLocalId, Option<crate::pass::OriginId>>>>,
135}
136
137impl MirOriginTable {
138 pub fn mapping_count(&self) -> usize {
140 self.local_origins
141 .iter()
142 .map(|(_, origins)| {
143 origins.as_ref().map_or(0usize, |origins| {
144 origins
145 .iter()
146 .filter(|(_, origin)| origin.is_some())
147 .count()
148 })
149 })
150 .sum()
151 }
152}
153
154#[derive(Debug, Clone, Default, PartialEq, Eq)]
156pub struct BrowserArtifacts {
157 pub ast_graph: BrowserAstGraph,
159 pub hir_graph: BrowserHirGraph,
161 pub mir_graph: BrowserMirGraph,
163 pub rust_source: String,
165 pub wat_source: String,
167 pub wasm_bytes: Option<Vec<u8>>,
169 pub wasm_diagnostics: Vec<codegen::wasm::WasmDiagnostic>,
171}
172
173#[derive(Debug, Default)]
175pub struct BrowserDriver {
176 artifacts: BrowserArtifacts,
177}
178
179impl BrowserDriver {
180 pub fn into_artifacts(self) -> BrowserArtifacts {
182 self.artifacts
183 }
184}
185
186impl Driver for BrowserDriver {
187 type Error = Infallible;
188
189 fn on_expanded_ast(&mut self, program: &Program) -> Result<(), Self::Error> {
190 self.artifacts.ast_graph = crate::syntax::browser_graph::program_to_browser_graph(program);
191 Ok(())
192 }
193
194 fn on_hir(&mut self, program: &HirProgram) -> Result<(), Self::Error> {
195 self.artifacts.hir_graph = crate::hir::browser_graph::program_to_browser_graph(program);
196 Ok(())
197 }
198
199 fn on_mir(&mut self, program: &MirProgram) -> Result<(), Self::Error> {
200 self.artifacts.mir_graph = crate::mir::browser_graph::program_to_browser_graph(program);
201 Ok(())
202 }
203
204 fn on_rust(&mut self, source: &str) -> Result<(), Self::Error> {
205 self.artifacts.rust_source = source.to_owned();
206 Ok(())
207 }
208
209 fn on_wat(&mut self, source: &str) -> Result<(), Self::Error> {
210 self.artifacts.wat_source = source.to_owned();
211 Ok(())
212 }
213
214 fn on_wasm(&mut self, bytes: &[u8]) -> Result<(), Self::Error> {
215 self.artifacts.wasm_bytes = Some(bytes.to_vec());
216 Ok(())
217 }
218
219 fn on_wasm_diagnostics(
220 &mut self,
221 diagnostics: &[codegen::wasm::WasmDiagnostic],
222 ) -> Result<(), Self::Error> {
223 self.artifacts.wasm_diagnostics = diagnostics.to_vec();
224 Ok(())
225 }
226}
227
228#[derive(Debug, Error)]
230pub enum CompileError<DE>
231where
232 DE: std::error::Error + 'static,
233{
234 #[error("{0}")]
236 Module(#[from] modules::ModuleError),
237 #[error("{0}")]
239 Syntax(#[from] syntax::SyntaxError),
240 #[error("{0}")]
242 Meta(#[from] meta::MetaEvalError),
243 #[error("{0}")]
245 Elab(#[from] elab::ElabError),
246 #[error("{0}")]
248 Check(#[from] check::CheckError),
249 #[error("{0}")]
251 MirLower(#[from] mir::lower::LowerError),
252 #[error("{0}")]
254 Codegen(#[from] codegen::rust::CodegenError),
255 #[error("{0}")]
257 Driver(#[source] DE),
258}
259
260pub fn compile_with_driver<D>(
262 source_file: &Path,
263 source_text: &str,
264 driver: &mut D,
265) -> Result<(), CompileError<D::Error>>
266where
267 D: Driver,
268{
269 let ast = modules::resolve_for_native(source_file, source_text)?;
270 driver.on_ast(&ast).map_err(CompileError::Driver)?;
271
272 compile_resolved_program(source_file, source_text, &ast, driver)
273}
274
275pub fn compile_with_driver_for_browser<D>(
277 source_file: &Path,
278 source_text: &str,
279 driver: &mut D,
280) -> Result<(), CompileError<D::Error>>
281where
282 D: Driver,
283{
284 let ast = modules::resolve_for_browser(source_file, source_text)?;
285 driver.on_ast(&ast).map_err(CompileError::Driver)?;
286
287 compile_resolved_program(source_file, source_text, &ast, driver)
288}
289
290fn compile_resolved_program<D>(
291 source_file: &Path,
292 source_text: &str,
293 ast: &Program,
294 driver: &mut D,
295) -> Result<(), CompileError<D::Error>>
296where
297 D: Driver,
298{
299 let mut pass_context = PassContext::new();
300
301 let mut expand_pass = passes::ExpandProgramPass { program: ast };
302 let (meta_pass_id, expanded_ast) = pass_context.run_pass_with_id(&mut expand_pass, ())?;
303 driver
304 .on_pass_snapshot(&PassSnapshot {
305 kind: PassSnapshotKind::MetaProgram,
306 name: "meta.expand_program",
307 detail: format!(
308 "functions={} structs={} enums={}",
309 expanded_ast.functions.len(),
310 expanded_ast.structs.len(),
311 expanded_ast.enums.len()
312 ),
313 fields: vec![
314 PassSnapshotField {
315 key: "functions",
316 value: PassSnapshotValue::U64(expanded_ast.functions.len() as u64),
317 },
318 PassSnapshotField {
319 key: "structs",
320 value: PassSnapshotValue::U64(expanded_ast.structs.len() as u64),
321 },
322 PassSnapshotField {
323 key: "enums",
324 value: PassSnapshotValue::U64(expanded_ast.enums.len() as u64),
325 },
326 ],
327 })
328 .map_err(CompileError::Driver)?;
329
330 let mut elaborate_pass = passes::ElaborateProgramPass {
331 program: &expanded_ast,
332 subpass_timings: Vec::new(),
333 };
334 let (elab_pass_id, elaborated_ast) = pass_context.run_pass_with_id(&mut elaborate_pass, ())?;
335 pass_context.extend_pass_timings(elaborate_pass.take_subpass_timings());
336 driver
337 .on_pass_snapshot(&PassSnapshot {
338 kind: PassSnapshotKind::ElabProgram,
339 name: "elab.elaborate_program",
340 detail: format!(
341 "functions={} structs={} enums={}",
342 elaborated_ast.functions.len(),
343 elaborated_ast.structs.len(),
344 elaborated_ast.enums.len()
345 ),
346 fields: vec![
347 PassSnapshotField {
348 key: "functions",
349 value: PassSnapshotValue::U64(elaborated_ast.functions.len() as u64),
350 },
351 PassSnapshotField {
352 key: "structs",
353 value: PassSnapshotValue::U64(elaborated_ast.structs.len() as u64),
354 },
355 PassSnapshotField {
356 key: "enums",
357 value: PassSnapshotValue::U64(elaborated_ast.enums.len() as u64),
358 },
359 ],
360 })
361 .map_err(CompileError::Driver)?;
362 driver
363 .on_expanded_ast(&elaborated_ast)
364 .map_err(CompileError::Driver)?;
365
366 let mut lower_hir_pass = passes::LowerHirPass {
367 program: &elaborated_ast,
368 };
369 let (hir_pass_id, hir) = pass_context
370 .run_pass_with_id(&mut lower_hir_pass, ())
371 .unwrap_or_else(|never| match never {});
372 driver
373 .on_pass_snapshot(&PassSnapshot {
374 kind: PassSnapshotKind::HirProgram,
375 name: "hir.lower_program",
376 detail: format!(
377 "functions={} exprs={} stmts={}",
378 hir.functions.len(),
379 hir.expressions.len(),
380 hir.statements.len()
381 ),
382 fields: vec![
383 PassSnapshotField {
384 key: "functions",
385 value: PassSnapshotValue::U64(hir.functions.len() as u64),
386 },
387 PassSnapshotField {
388 key: "exprs",
389 value: PassSnapshotValue::U64(hir.expressions.len() as u64),
390 },
391 PassSnapshotField {
392 key: "stmts",
393 value: PassSnapshotValue::U64(hir.statements.len() as u64),
394 },
395 ],
396 })
397 .map_err(CompileError::Driver)?;
398 driver.on_hir(&hir).map_err(CompileError::Driver)?;
399 record_hir_provenance_origins(
400 driver,
401 &mut pass_context,
402 &hir,
403 meta_pass_id,
404 elab_pass_id,
405 hir_pass_id,
406 )
407 .map_err(CompileError::Driver)?;
408
409 let mut check_pass = passes::CheckProgramPass { program: &hir };
410 let (_check_pass_id, checked) = pass_context.run_pass_with_id(&mut check_pass, ())?;
411 driver
412 .on_pass_snapshot(&PassSnapshot {
413 kind: PassSnapshotKind::CheckProgram,
414 name: "check.check_program",
415 detail: format!(
416 "signatures={} local_symbols={} occurrences={}",
417 checked.signatures.len(),
418 checked.local_symbols().len(),
419 checked.local_symbol_occurrences().len()
420 ),
421 fields: vec![
422 PassSnapshotField {
423 key: "signatures",
424 value: PassSnapshotValue::U64(checked.signatures.len() as u64),
425 },
426 PassSnapshotField {
427 key: "local_symbols",
428 value: PassSnapshotValue::U64(checked.local_symbols().len() as u64),
429 },
430 PassSnapshotField {
431 key: "occurrences",
432 value: PassSnapshotValue::U64(checked.local_symbol_occurrences().len() as u64),
433 },
434 ],
435 })
436 .map_err(CompileError::Driver)?;
437 driver.on_checked(&checked).map_err(CompileError::Driver)?;
438
439 let mut lower_mir_pass = passes::LowerMirPass {
440 hir: &hir,
441 checked: &checked,
442 };
443 let (mir_pass_id, mir) = pass_context.run_pass_with_id(&mut lower_mir_pass, ())?;
444 let (mir_local_count, mir_instr_count) = mir_function_totals(&mir);
445 driver
446 .on_pass_snapshot(&PassSnapshot {
447 kind: PassSnapshotKind::MirProgram,
448 name: "mir.lower_program",
449 detail: format!(
450 "functions={} locals={} instrs={}",
451 mir.functions.len(),
452 mir_local_count,
453 mir_instr_count
454 ),
455 fields: vec![
456 PassSnapshotField {
457 key: "functions",
458 value: PassSnapshotValue::U64(mir.functions.len() as u64),
459 },
460 PassSnapshotField {
461 key: "locals",
462 value: PassSnapshotValue::U64(mir_local_count as u64),
463 },
464 PassSnapshotField {
465 key: "instrs",
466 value: PassSnapshotValue::U64(mir_instr_count as u64),
467 },
468 ],
469 })
470 .map_err(CompileError::Driver)?;
471 driver.on_mir(&mir).map_err(CompileError::Driver)?;
472 record_mir_provenance_origins(
473 driver,
474 &mut pass_context,
475 &mir,
476 meta_pass_id,
477 elab_pass_id,
478 hir_pass_id,
479 mir_pass_id,
480 )
481 .map_err(CompileError::Driver)?;
482
483 let mut emit_rust_pass = passes::EmitRustPass {
484 mir: &mir,
485 source_file,
486 source_text,
487 };
488 let (_emit_rust_pass_id, rust_source) =
489 pass_context.run_pass_with_id(&mut emit_rust_pass, ())?;
490 driver
491 .on_pass_snapshot(&PassSnapshot {
492 kind: PassSnapshotKind::CodegenRust,
493 name: "codegen.rust.emit_program",
494 detail: format!(
495 "lines={} bytes={}",
496 rust_source.lines().count(),
497 rust_source.len()
498 ),
499 fields: vec![
500 PassSnapshotField {
501 key: "lines",
502 value: PassSnapshotValue::U64(rust_source.lines().count() as u64),
503 },
504 PassSnapshotField {
505 key: "bytes",
506 value: PassSnapshotValue::U64(rust_source.len() as u64),
507 },
508 ],
509 })
510 .map_err(CompileError::Driver)?;
511 driver.on_rust(&rust_source).map_err(CompileError::Driver)?;
512
513 let mut emit_wasm_pass = passes::EmitWasmPass {
514 mir: &mir,
515 source_text,
516 };
517 let (_emit_wasm_pass_id, wasm_artifacts) = pass_context
518 .run_pass_with_id(&mut emit_wasm_pass, ())
519 .unwrap_or_else(|never| match never {});
520 driver
521 .on_pass_snapshot(&PassSnapshot {
522 kind: PassSnapshotKind::CodegenWasm,
523 name: "codegen.wasm.emit_program",
524 detail: format!(
525 "wat_bytes={} wasm_bytes={} diagnostics={}",
526 wasm_artifacts.wat_source.len(),
527 wasm_artifacts
528 .wasm_bytes
529 .as_ref()
530 .map_or(0, std::vec::Vec::len),
531 wasm_artifacts.diagnostics.len()
532 ),
533 fields: vec![
534 PassSnapshotField {
535 key: "wat_bytes",
536 value: PassSnapshotValue::U64(wasm_artifacts.wat_source.len() as u64),
537 },
538 PassSnapshotField {
539 key: "wasm_bytes",
540 value: PassSnapshotValue::U64(
541 wasm_artifacts
542 .wasm_bytes
543 .as_ref()
544 .map_or(0usize, std::vec::Vec::len) as u64,
545 ),
546 },
547 PassSnapshotField {
548 key: "diagnostics",
549 value: PassSnapshotValue::U64(wasm_artifacts.diagnostics.len() as u64),
550 },
551 ],
552 })
553 .map_err(CompileError::Driver)?;
554 driver
555 .on_wat(&wasm_artifacts.wat_source)
556 .map_err(CompileError::Driver)?;
557 if let Some(bytes) = &wasm_artifacts.wasm_bytes {
558 driver.on_wasm(bytes).map_err(CompileError::Driver)?;
559 }
560 driver
561 .on_wasm_diagnostics(&wasm_artifacts.diagnostics)
562 .map_err(CompileError::Driver)?;
563 driver
564 .on_pass_timings(pass_context.pass_timings())
565 .map_err(CompileError::Driver)?;
566 driver
567 .on_provenance_summary(pass_context.provenance().summary())
568 .map_err(CompileError::Driver)?;
569
570 Ok(())
571}
572
573fn mir_function_totals(mir: &MirProgram) -> (usize, usize) {
575 let mut local_count = 0usize;
576 let mut instr_count = 0usize;
577 for (_, function) in mir.functions.iter() {
578 local_count += function.locals.len();
579 for (_, block) in function.blocks.iter() {
580 instr_count += block.instrs.len();
581 }
582 }
583 (local_count, instr_count)
584}
585
586fn record_hir_provenance_origins<D>(
588 driver: &mut D,
589 pass_context: &mut PassContext,
590 hir: &HirProgram,
591 meta_pass_id: crate::pass::PassId,
592 elab_pass_id: crate::pass::PassId,
593 hir_pass_id: crate::pass::PassId,
594) -> Result<(), D::Error>
595where
596 D: Driver,
597{
598 let mut expr_origins = SecondaryMap::new();
599 for (expr_id, span) in hir.expr_spans.iter() {
600 if let Some(span) = span {
601 let parsed = pass_context.provenance_mut().record_parsed(*span);
602 let expanded = pass_context
603 .provenance_mut()
604 .record_expanded(parsed, meta_pass_id);
605 let elaborated = pass_context
606 .provenance_mut()
607 .record_lowered(expanded, elab_pass_id);
608 let lowered = pass_context
609 .provenance_mut()
610 .record_lowered(elaborated, hir_pass_id);
611 expr_origins[expr_id] = Some(lowered);
612 }
613 }
614 driver.on_hir_origins(&HirOriginTable { expr_origins })
615}
616
617fn record_mir_provenance_origins<D>(
619 driver: &mut D,
620 pass_context: &mut PassContext,
621 mir: &MirProgram,
622 meta_pass_id: crate::pass::PassId,
623 elab_pass_id: crate::pass::PassId,
624 hir_pass_id: crate::pass::PassId,
625 mir_pass_id: crate::pass::PassId,
626) -> Result<(), D::Error>
627where
628 D: Driver,
629{
630 let mut local_origins = SecondaryMap::new();
631 for (func_id, function) in mir.functions.iter() {
632 let mut origins = SecondaryMap::new();
633 for (_, block) in function.blocks.iter() {
634 for instr in &block.instrs {
635 let Some((dst, source_pos)) = mir_instr_source_pos_and_dst(instr) else {
636 continue;
637 };
638 let Some(span) = source_pos_to_span(source_pos) else {
639 continue;
640 };
641 let parsed = pass_context.provenance_mut().record_parsed(span);
642 let expanded = pass_context
643 .provenance_mut()
644 .record_expanded(parsed, meta_pass_id);
645 let elaborated = pass_context
646 .provenance_mut()
647 .record_lowered(expanded, elab_pass_id);
648 let hir_lowered = pass_context
649 .provenance_mut()
650 .record_lowered(elaborated, hir_pass_id);
651 let mir_lowered = pass_context
652 .provenance_mut()
653 .record_lowered(hir_lowered, mir_pass_id);
654 origins[dst] = Some(mir_lowered);
655 }
656 }
657 local_origins[func_id] = Some(origins);
658 }
659 driver.on_mir_origins(&MirOriginTable { local_origins })
660}
661
662fn mir_instr_source_pos_and_dst(instr: &MirInstr) -> Option<(MirLocalId, MirSourcePos)> {
664 match instr {
665 MirInstr::MakeArray {
666 dst,
667 source_pos: Some(source_pos),
668 ..
669 }
670 | MirInstr::MakeTuple {
671 dst,
672 source_pos: Some(source_pos),
673 ..
674 }
675 | MirInstr::MakeStruct {
676 dst,
677 source_pos: Some(source_pos),
678 ..
679 }
680 | MirInstr::MakeEnum {
681 dst,
682 source_pos: Some(source_pos),
683 ..
684 }
685 | MirInstr::CallIntrinsic {
686 dst,
687 source_pos: Some(source_pos),
688 ..
689 } => Some((*dst, *source_pos)),
690 _ => None,
691 }
692}
693
694fn source_pos_to_span(source_pos: MirSourcePos) -> Option<SourceSpan> {
696 let line = usize::try_from(source_pos.line).ok()?;
697 let column = usize::try_from(source_pos.column).ok()?;
698 let location = SourceLocation {
699 byte: 0,
700 line,
701 column,
702 };
703 Some(SourceSpan::new(
704 FileId::from_u32(source_pos.file_id),
705 location,
706 location,
707 ))
708}
709
710pub fn compile_for_browser(
723 source_file: &Path,
724 source_text: &str,
725) -> Result<BrowserArtifacts, CompileError<Infallible>> {
726 let mut driver = BrowserDriver::default();
727 compile_with_driver_for_browser(source_file, source_text, &mut driver)?;
728
729 Ok(driver.into_artifacts())
730}
731
732#[cfg(test)]
733mod tests;