1use parking_lot::RwLock;
2use rayon::prelude::*;
3use smallvec::SmallVec;
4use std::cmp::Ordering as CmpOrdering;
5use std::fs;
6use std::io::Write;
7use std::ops::Deref;
8use std::time::Instant;
9use tree_sitter::Node;
10use uuid::Uuid;
11
12use crate::block::{BasicBlock, BlockArena, BlockId, reset_block_id_counter};
13use crate::block_rel::{BlockIndexMaps, BlockRelationMap};
14use crate::file::File;
15use crate::meta::{UnitMeta, UnitMetaBuilder};
16
17#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
19pub enum FileOrder {
20 #[default]
22 Original,
23 BySizeDescending,
25}
26use crate::interner::{InternPool, InternedStr};
27use crate::ir::{Arena, HirBase, HirId, HirIdent, HirKind, HirNode};
28use crate::ir_builder::reset_hir_id_counter;
29use crate::lang_def::{LanguageTrait, ParseTree};
30use crate::scope::Scope;
31use crate::symbol::{ScopeId, SymId, Symbol, reset_scope_id_counter, reset_symbol_id_counter};
32
33#[derive(Clone, Copy)]
37pub struct SymbolPtr(*const Symbol);
38
39unsafe impl Send for SymbolPtr {}
43unsafe impl Sync for SymbolPtr {}
44
45impl SymbolPtr {
46 pub fn new(ptr: *const Symbol) -> Self {
47 Self(ptr)
48 }
49
50 pub fn as_ptr(&self) -> *const Symbol {
51 self.0
52 }
53}
54
55#[derive(Clone, Copy)]
57pub struct ScopePtr(*const Scope<'static>);
58
59unsafe impl Send for ScopePtr {}
63unsafe impl Sync for ScopePtr {}
64
65impl ScopePtr {
66 pub fn new<'a>(ptr: *const Scope<'a>) -> Self {
67 Self(ptr.cast())
70 }
71
72 pub fn as_ptr<'a>(&self) -> *const Scope<'a> {
74 self.0.cast()
76 }
77}
78
79#[derive(Clone, Copy)]
81pub struct HirNodePtr(*const HirNode<'static>);
82
83unsafe impl Send for HirNodePtr {}
84unsafe impl Sync for HirNodePtr {}
85
86impl HirNodePtr {
87 pub fn new<'a>(ptr: *const HirNode<'a>) -> Self {
88 Self(ptr.cast())
90 }
91
92 pub fn as_ptr<'a>(&self) -> *const HirNode<'a> {
93 self.0.cast()
95 }
96}
97
98#[derive(Debug, Copy, Clone)]
99pub struct CompileUnit<'tcx> {
100 pub cc: &'tcx CompileCtxt<'tcx>,
101 pub index: usize,
102}
103
104impl<'tcx> CompileUnit<'tcx> {
105 pub fn file(&self) -> &'tcx File {
106 &self.cc.files[self.index]
107 }
108
109 pub fn parse_tree(&self) -> Option<&dyn ParseTree> {
111 self.cc
112 .parse_trees
113 .get(self.index)
114 .and_then(|t| t.as_deref())
115 }
116
117 pub fn interner(&self) -> &InternPool {
119 &self.cc.interner
120 }
121
122 pub fn intern_str<S>(&self, value: S) -> InternedStr
124 where
125 S: AsRef<str>,
126 {
127 self.cc.interner.intern(value)
128 }
129
130 pub fn resolve_interned_owned(&self, symbol: InternedStr) -> Option<String> {
132 self.cc.interner.resolve_owned(symbol)
133 }
134
135 pub fn resolve_name_or(&self, symbol: InternedStr, default: &str) -> String {
138 self.resolve_interned_owned(symbol)
139 .unwrap_or_else(|| default.to_string())
140 }
141
142 pub fn resolve_name(&self, symbol: InternedStr) -> String {
144 self.resolve_name_or(symbol, "<unnamed>")
145 }
146
147 pub fn file_root_id(&self) -> Option<HirId> {
148 self.cc.file_root_id(self.index)
149 }
150
151 pub fn file_path(&self) -> Option<&str> {
152 self.cc.file_path(self.index)
153 }
154
155 pub fn unit_meta(&self) -> &UnitMeta {
158 &self.cc.unit_metas[self.index]
159 }
160
161 pub fn reserve_block_id(&self) -> BlockId {
163 BlockId::allocate()
164 }
165
166 pub fn get_text(&self, start: usize, end: usize) -> String {
168 self.file().get_text(start, end)
169 }
170
171 pub fn ts_text(&self, node: Node<'tcx>) -> String {
173 self.get_text(node.start_byte(), node.end_byte())
174 }
175
176 pub fn hir_text(&self, node: &HirNode<'tcx>) -> String {
178 self.get_text(node.start_byte(), node.end_byte())
179 }
180
181 pub fn opt_hir_node(self, id: HirId) -> Option<HirNode<'tcx>> {
183 self.cc.get_hir_node(id)
184 }
185
186 pub fn hir_node(self, id: HirId) -> HirNode<'tcx> {
188 self.opt_hir_node(id)
189 .unwrap_or_else(|| panic!("hir node not found {id}"))
190 }
191
192 pub fn opt_bb(self, id: BlockId) -> Option<BasicBlock<'tcx>> {
194 self.cc.block_arena.get_bb(id.0 as usize).cloned()
196 }
197
198 pub fn bb(self, id: BlockId) -> BasicBlock<'tcx> {
200 self.opt_bb(id)
201 .unwrap_or_else(|| panic!("basic block not found: {id}"))
202 }
203
204 pub fn root_block(self) -> Option<BasicBlock<'tcx>> {
207 let root_blocks = self
208 .cc
209 .find_blocks_by_kind_in_unit(crate::block::BlockKind::Root, self.index);
210 root_blocks.first().and_then(|&id| self.opt_bb(id))
211 }
212
213 pub fn parent_node(self, id: HirId) -> Option<HirId> {
215 self.opt_hir_node(id).and_then(|node| node.parent())
216 }
217
218 pub fn opt_get_scope(self, scope_id: ScopeId) -> Option<&'tcx Scope<'tcx>> {
220 self.cc.opt_get_scope(scope_id)
221 }
222
223 pub fn opt_get_symbol(self, owner: SymId) -> Option<&'tcx Symbol> {
225 self.cc.opt_get_symbol(owner)
226 }
227
228 pub fn get_scope(self, scope_id: ScopeId) -> &'tcx Scope<'tcx> {
230 self.opt_get_scope(scope_id)
231 .expect("ScopeId not mapped to Scope in CompileCtxt")
232 }
233
234 pub fn insert_block(&self, id: BlockId, block: BasicBlock<'tcx>, _parent: BlockId) {
235 let block_kind = block.kind();
237 let block_name = block
238 .base()
239 .and_then(|base| base.opt_get_name())
240 .map(|s| s.to_string());
241
242 self.cc.block_arena.alloc_with_id(id.0 as usize, block);
244
245 self.cc
247 .block_indexes
248 .insert_block(id, block_name, block_kind, self.index);
249 }
250}
251
252impl<'tcx> Deref for CompileUnit<'tcx> {
253 type Target = CompileCtxt<'tcx>;
254
255 #[inline(always)]
256 fn deref(&self) -> &Self::Target {
257 self.cc
258 }
259}
260
261#[derive(Debug, Clone)]
262pub struct ParentedNode<'tcx> {
263 pub node: HirNode<'tcx>,
264}
265
266impl<'tcx> ParentedNode<'tcx> {
267 pub fn new(node: HirNode<'tcx>) -> Self {
268 Self { node }
269 }
270
271 pub fn node(&self) -> &HirNode<'tcx> {
273 &self.node
274 }
275
276 pub fn parent(&self) -> Option<HirId> {
278 self.node.parent()
279 }
280}
281
282#[derive(Debug, Clone, Default)]
283pub struct FileParseMetric {
284 pub path: String,
285 pub seconds: f64,
286}
287
288#[derive(Debug, Clone, Default)]
289pub struct BuildMetrics {
290 pub file_read_seconds: f64,
291 pub parse_wall_seconds: f64,
292 pub parse_cpu_seconds: f64,
293 pub parse_avg_seconds: f64,
294 pub parse_file_count: usize,
295 pub parse_slowest: Vec<FileParseMetric>,
296}
297
298#[derive(Default)]
299pub struct CompileCtxt<'tcx> {
300 pub arena: Arena<'tcx>,
301 pub interner: InternPool,
302 pub files: Vec<File>,
303 pub unit_metas: Vec<UnitMeta>,
305 pub parse_trees: Vec<Option<Box<dyn ParseTree>>>,
307 pub hir_root_ids: RwLock<Vec<Option<HirId>>>,
308
309 pub block_arena: BlockArena<'tcx>,
310 pub related_map: BlockRelationMap,
311
312 pub block_indexes: BlockIndexMaps,
315
316 pub build_metrics: BuildMetrics,
318}
319
320impl<'tcx> std::fmt::Debug for CompileCtxt<'tcx> {
321 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
322 f.debug_struct("CompileCtxt")
323 .field("files", &self.files.len())
324 .field("parse_trees", &self.parse_trees.len())
325 .field("build_metrics", &self.build_metrics)
326 .finish()
327 }
328}
329
330impl<'tcx> CompileCtxt<'tcx> {
331 pub fn from_sources<L: LanguageTrait>(sources: &[Vec<u8>]) -> Self {
333 let temp_dir = std::env::temp_dir()
335 .join("llmcc")
336 .join(Uuid::new_v4().to_string());
337 let _ = fs::create_dir_all(&temp_dir);
338
339 let paths: Vec<String> = sources
340 .iter()
341 .enumerate()
342 .map(|(index, src)| {
343 let path = temp_dir.join(format!("source_{index}.rs"));
344 if let Ok(mut file) = fs::File::create(&path) {
345 let _ = file.write_all(src);
346 }
347 path.to_string_lossy().to_string()
348 })
349 .collect();
350
351 Self::from_files::<L>(&paths).unwrap_or_else(|_| {
353 Self::default()
355 })
356 }
357
358 pub fn from_files<L: LanguageTrait>(paths: &[String]) -> std::io::Result<Self> {
360 Self::from_files_with_order::<L>(paths, FileOrder::Original)
361 }
362
363 pub fn from_files_with_order<L: LanguageTrait>(
365 paths: &[String],
366 order: FileOrder,
367 ) -> std::io::Result<Self> {
368 reset_hir_id_counter();
369 reset_symbol_id_counter();
370 reset_scope_id_counter();
371 reset_block_id_counter();
372
373 let read_start = Instant::now();
374
375 let mut files_with_index: Vec<(usize, File)> = paths
376 .par_iter()
377 .enumerate()
378 .map(|(index, path)| -> std::io::Result<(usize, File)> {
379 let file = File::new_file(path.clone())?;
380 Ok((index, file))
381 })
382 .collect::<std::io::Result<Vec<_>>>()?;
383
384 match order {
386 FileOrder::Original => files_with_index.sort_by_key(|(index, _)| *index),
387 FileOrder::BySizeDescending => {
388 files_with_index.sort_by_key(|(_, file)| std::cmp::Reverse(file.content().len()))
389 }
390 }
391 let files: Vec<File> = files_with_index.into_iter().map(|(_, file)| file).collect();
392
393 let file_read_seconds = read_start.elapsed().as_secs_f64();
394
395 let (parse_trees, mut metrics) = Self::parse_files_with_metrics::<L>(&files);
396 metrics.file_read_seconds = file_read_seconds;
397
398 let unit_metas = Self::build_unit_metas::<L>(&files);
400 let count = unit_metas.len();
401
402 Ok(Self {
403 arena: Arena::default(),
404 interner: InternPool::default(),
405 files,
406 unit_metas,
407 parse_trees,
408 hir_root_ids: RwLock::new(vec![None; count]),
409 block_arena: BlockArena::default(),
410 related_map: BlockRelationMap::default(),
411 block_indexes: BlockIndexMaps::new(),
412 build_metrics: metrics,
413 })
414 }
415
416 pub fn from_files_with_logical<L: LanguageTrait>(
420 paths: &[(String, String)],
421 ) -> std::io::Result<Self> {
422 Self::from_files_with_logical_and_order::<L>(paths, FileOrder::Original)
423 }
424
425 pub fn from_files_with_logical_and_order<L: LanguageTrait>(
427 paths: &[(String, String)],
428 order: FileOrder,
429 ) -> std::io::Result<Self> {
430 reset_hir_id_counter();
431 reset_symbol_id_counter();
432 reset_scope_id_counter();
433 reset_block_id_counter();
434
435 let read_start = Instant::now();
436
437 let mut files_with_index: Vec<(usize, File)> = paths
438 .par_iter()
439 .enumerate()
440 .map(
441 |(index, (physical, logical))| -> std::io::Result<(usize, File)> {
442 let file = File::new_file_with_logical(physical, logical.clone())?;
443 Ok((index, file))
444 },
445 )
446 .collect::<std::io::Result<Vec<_>>>()?;
447
448 match order {
450 FileOrder::Original => files_with_index.sort_by_key(|(index, _)| *index),
451 FileOrder::BySizeDescending => {
452 files_with_index.sort_by_key(|(_, file)| std::cmp::Reverse(file.content().len()))
453 }
454 }
455 let files: Vec<File> = files_with_index.into_iter().map(|(_, file)| file).collect();
456
457 let file_read_seconds = read_start.elapsed().as_secs_f64();
458
459 let (parse_trees, mut metrics) = Self::parse_files_with_metrics::<L>(&files);
460 metrics.file_read_seconds = file_read_seconds;
461
462 let unit_metas = Self::build_unit_metas::<L>(&files);
464 let count = unit_metas.len();
465
466 Ok(Self {
467 arena: Arena::default(),
468 interner: InternPool::default(),
469 files,
470 unit_metas,
471 parse_trees,
472 hir_root_ids: RwLock::new(vec![None; count]),
473 block_arena: BlockArena::default(),
474 related_map: BlockRelationMap::default(),
475 block_indexes: BlockIndexMaps::new(),
476 build_metrics: metrics,
477 })
478 }
479
480 fn build_unit_metas<L: LanguageTrait>(files: &[File]) -> Vec<UnitMeta> {
482 if files.is_empty() {
483 return Vec::new();
484 }
485
486 let file_paths: Vec<std::path::PathBuf> = files
488 .iter()
489 .filter_map(|f| f.path().map(std::path::PathBuf::from))
490 .collect();
491
492 if file_paths.is_empty() {
493 return vec![UnitMeta::default(); files.len()];
494 }
495
496 let builder = UnitMetaBuilder::from_lang_trait::<L>(&file_paths);
498
499 let mut metas: Vec<UnitMeta> = files
501 .iter()
502 .map(|f| {
503 if let Some(path) = f.path() {
504 builder.get_module_info(std::path::Path::new(path))
505 } else {
506 UnitMeta::default()
507 }
508 })
509 .collect();
510
511 let mut package_root_to_crate_index: std::collections::HashMap<std::path::PathBuf, usize> =
514 std::collections::HashMap::new();
515 let mut next_crate_index = 0usize;
516
517 for meta in &mut metas {
518 if let Some(ref package_root) = meta.package_root {
519 let crate_idx = *package_root_to_crate_index
520 .entry(package_root.clone())
521 .or_insert_with(|| {
522 let idx = next_crate_index;
523 next_crate_index += 1;
524 idx
525 });
526 meta.crate_index = crate_idx;
527 }
528 }
529
530 metas
531 }
532
533 fn parse_files_with_metrics<L: LanguageTrait>(
534 files: &[File],
535 ) -> (Vec<Option<Box<dyn ParseTree>>>, BuildMetrics) {
536 struct ParseRecord {
537 tree: Option<Box<dyn ParseTree>>,
538 elapsed: f64,
539 path: Option<String>,
540 }
541
542 let parse_wall_start = Instant::now();
543 let records: Vec<ParseRecord> = files
544 .par_iter()
545 .map(|file| {
546 let path = file.path().map(|p| p.to_string());
547 let per_file_start = Instant::now();
548 let tree = L::parse(file.content());
549 let elapsed = per_file_start.elapsed().as_secs_f64();
550 ParseRecord {
551 tree,
552 elapsed,
553 path,
554 }
555 })
556 .collect();
557 let parse_wall_seconds = parse_wall_start.elapsed().as_secs_f64();
558
559 let mut trees = Vec::with_capacity(records.len());
560 let parse_file_count = records.len();
561 let mut parse_cpu_seconds = 0.0;
562 let mut slowest = Vec::with_capacity(records.len());
563
564 for record in records {
565 parse_cpu_seconds += record.elapsed;
566 trees.push(record.tree);
567 let path = record.path.unwrap_or_else(|| "<memory>".to_string());
568 slowest.push(FileParseMetric {
569 path,
570 seconds: record.elapsed,
571 });
572 }
573
574 slowest.sort_by(|a, b| {
575 b.seconds
576 .partial_cmp(&a.seconds)
577 .unwrap_or(CmpOrdering::Equal)
578 });
579 slowest.truncate(5);
580
581 let metrics = BuildMetrics {
582 file_read_seconds: 0.0,
583 parse_wall_seconds,
584 parse_cpu_seconds,
585 parse_avg_seconds: if parse_file_count == 0 {
586 0.0
587 } else {
588 parse_cpu_seconds / parse_file_count as f64
589 },
590 parse_file_count,
591 parse_slowest: slowest,
592 };
593
594 (trees, metrics)
595 }
596
597 pub const GLOBAL_SCOPE_OWNER: HirId = HirId(usize::MAX);
600
601 pub fn compile_unit(&'tcx self, index: usize) -> CompileUnit<'tcx> {
603 CompileUnit { cc: self, index }
604 }
605
606 pub fn create_unit_globals(&'tcx self, owner: HirId) -> &'tcx Scope<'tcx> {
607 let scope = Scope::new_with(owner, None, Some(&self.interner));
609 let id = scope.id().0;
610 self.arena.alloc_with_id(id, scope)
611 }
612
613 pub fn create_globals(&'tcx self) -> &'tcx Scope<'tcx> {
614 let scope =
616 Scope::new_with_shards(Self::GLOBAL_SCOPE_OWNER, None, Some(&self.interner), 256);
617 let id = scope.id().0;
618 self.arena.alloc_with_id(id, scope)
619 }
620
621 pub fn get_scope(&'tcx self, scope_id: ScopeId) -> &'tcx Scope<'tcx> {
622 self.arena
623 .get_scope(scope_id.0)
624 .expect("ScopeId not mapped to Scope in CompileCtxt")
625 }
626
627 pub fn opt_get_scope(&'tcx self, scope_id: ScopeId) -> Option<&'tcx Scope<'tcx>> {
628 self.arena.get_scope(scope_id.0).and_then(|scope| {
630 if let Some(target_id) = scope.get_redirect() {
631 self.opt_get_scope(target_id)
633 } else {
634 Some(scope)
635 }
636 })
637 }
638
639 pub fn opt_get_symbol(&'tcx self, owner: SymId) -> Option<&'tcx Symbol> {
640 self.arena.get_symbol(owner.0)
641 }
642
643 pub fn get_symbol(&'tcx self, owner: SymId) -> &'tcx Symbol {
644 self.opt_get_symbol(owner)
645 .expect("SymId not mapped to Symbol in CompileCtxt")
646 }
647
648 pub fn find_symbol_by_block_id(&'tcx self, block_id: BlockId) -> Option<&'tcx Symbol> {
650 self.arena
651 .iter_symbol()
652 .find(|symbol| symbol.block_id() == Some(block_id))
653 }
654
655 pub fn arena(&'tcx self) -> &'tcx Arena<'tcx> {
657 &self.arena
658 }
659
660 pub fn alloc_file_ident(
662 &'tcx self,
663 id: HirId,
664 name: &'tcx str,
665 symbol: &'tcx Symbol,
666 ) -> &'tcx HirIdent<'tcx> {
667 let base = HirBase {
668 id,
669 parent: None,
670 kind_id: 0,
671 start_byte: 0,
672 end_byte: 0,
673 kind: HirKind::Identifier,
674 field_id: u16::MAX,
675 children: SmallVec::new(),
676 };
677 let ident = self.arena.alloc(HirIdent::new(base, name));
678 ident.set_symbol(symbol);
679 ident
680 }
681
682 pub fn alloc_scope(&'tcx self, owner: HirId) -> &'tcx Scope<'tcx> {
683 let scope = Scope::new_with(owner, None, Some(&self.interner));
684 let id = scope.id().0;
685 self.arena.alloc_with_id(id, scope)
686 }
687
688 pub fn merge_two_scopes(&'tcx self, first: &'tcx Scope<'tcx>, second: &'tcx Scope<'tcx>) {
693 first.merge_with(second, self.arena());
695 second.set_redirect(first.id());
697 }
698
699 pub fn set_file_root_id(&self, index: usize, start: HirId) {
700 let mut starts = self.hir_root_ids.write();
701 if index < starts.len() && starts[index].is_none() {
702 starts[index] = Some(start);
703 }
704 }
705
706 pub fn file_root_id(&self, index: usize) -> Option<HirId> {
707 self.hir_root_ids.read().get(index).and_then(|opt| *opt)
708 }
709
710 pub fn file_path(&self, index: usize) -> Option<&str> {
711 self.files.get(index).and_then(|file| file.path())
712 }
713
714 pub fn get_parse_tree(&self, index: usize) -> Option<&dyn ParseTree> {
716 self.parse_trees.get(index).and_then(|t| t.as_deref())
717 }
718
719 pub fn get_files(&self) -> Vec<String> {
721 self.files
722 .iter()
723 .filter_map(|f| f.path().map(|p| p.to_string()))
724 .collect()
725 }
726
727 pub fn get_hir_node(&'tcx self, id: HirId) -> Option<HirNode<'tcx>> {
731 self.arena.get_hir_node(id.0).copied()
732 }
733
734 pub fn hir_node_exists(&self, id: HirId) -> bool {
736 self.arena.hir_node.contains_key(&id.0)
737 }
738
739 pub fn hir_node_count(&self) -> usize {
741 self.arena.len_hir_node()
742 }
743
744 pub fn all_hir_node_ids(&'tcx self) -> Vec<HirId> {
746 self.arena.iter_hir_node().map(|node| node.id()).collect()
747 }
748
749 pub fn find_blocks_by_name(
753 &self,
754 name: &'tcx str,
755 ) -> Vec<(usize, crate::block::BlockKind, BlockId)> {
756 self.block_indexes.find_by_name(name)
757 }
758
759 pub fn find_blocks_by_kind(
761 &self,
762 kind: crate::block::BlockKind,
763 ) -> Vec<(usize, Option<String>, BlockId)> {
764 self.block_indexes.find_by_kind(kind)
765 }
766
767 pub fn find_blocks_in_unit(
769 &self,
770 unit_index: usize,
771 ) -> Vec<(Option<String>, crate::block::BlockKind, BlockId)> {
772 self.block_indexes.find_by_unit(unit_index)
773 }
774
775 pub fn find_blocks_by_kind_in_unit(
777 &self,
778 kind: crate::block::BlockKind,
779 unit_index: usize,
780 ) -> Vec<BlockId> {
781 self.block_indexes.find_by_kind_and_unit(kind, unit_index)
782 }
783
784 pub fn get_block_info(
786 &self,
787 block_id: BlockId,
788 ) -> Option<(usize, Option<String>, crate::block::BlockKind)> {
789 self.block_indexes.get_block_info(block_id)
790 }
791
792 pub fn get_all_blocks(&self) -> Vec<(BlockId, usize, Option<String>, crate::block::BlockKind)> {
794 self.block_indexes.iter_all_blocks()
795 }
796
797 pub fn get_all_symbols(&'tcx self) -> Vec<&'tcx Symbol> {
801 self.arena.iter_symbol().collect()
802 }
803
804 pub fn symbol_count(&self) -> usize {
806 self.arena.len_symbol()
807 }
808
809 pub fn for_each_symbol<F>(&'tcx self, mut f: F)
811 where
812 F: FnMut(SymId, &'tcx Symbol),
813 {
814 for symbol in self.arena.iter_symbol() {
815 f(symbol.id(), symbol);
816 }
817 }
818}