Skip to main content

gc/
lib.rs

1#[cfg(test)]
2mod gc_test;
3#[cfg(test)]
4mod report_test;
5#[cfg(test)]
6mod runner_test;
7#[cfg(test)]
8mod value_test;
9#[cfg(test)]
10mod vm_test;
11
12pub mod frame;
13pub mod header;
14pub mod heap;
15pub mod list;
16pub mod malloc;
17pub mod report;
18pub mod runner;
19// The file is named gc_runtime.rs for editor clarity, but the module keeps
20// its historical public path `gc::runtime`.
21#[path = "gc_runtime.rs"]
22pub mod runtime;
23pub mod value;
24pub mod vm;
25
26pub use frame::Frame;
27pub use header::{GcId, GcObjectHeader, GcObjectType, GcPhase, RefCountHeader, RefCountId};
28pub use heap::{GcHeap, GcRef};
29pub use malloc::{MallocState, DEFAULT_GC_THRESHOLD, MALLOC_OVERHEAD};
30pub use report::{
31    EdgeRelation, FinalFate, FreeCycleStats, GcCollectionReport, GcObjectSummary, GcPhaseStats,
32    GcStatsBundle, GlobalRoot, HashKeyKind, HeapSnapshot, ObjectDecision, RestorationWitness,
33    ScanStats, TrialDecision, TrialDeletionStats, ValueKindCounts, VisitedEdge,
34};
35pub use runner::{compile_source, run_bytecode};
36pub use runtime::{GcObject, GcRuntime, MarkFunc};
37pub use value::{
38    export_object, import_object, try_export_object, value_to_string, GcClosure, Value, ValueKind,
39};
40pub use vm::{GcRuntimeError, GcVM, DEFAULT_INSTRUCTION_BUDGET};
41
42use compiler::compiler::{Bytecode, Compiler};
43use object::Object;
44use parser::ast::Node;
45use parser::lexer::token::Span;
46use serde::Serialize;
47
48#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
49#[serde(rename_all = "lowercase")]
50pub enum GcRunStage {
51    Parse,
52    Compile,
53    Runtime,
54}
55
56#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
57#[serde(rename_all = "camelCase")]
58pub struct GcRunSuccess {
59    pub result: String,
60    pub report: GcCollectionReport,
61}
62
63#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
64#[serde(rename_all = "camelCase")]
65pub struct GcRunError {
66    pub stage: GcRunStage,
67    pub message: String,
68    pub span: Option<Span>,
69}
70
71/// Compile Monkey source using the existing bytecode compiler.
72pub fn compile(program: &Node) -> Result<Bytecode, String> {
73    let mut compiler = Compiler::new();
74    compiler.compile(program)
75}
76
77/// Compile and execute on the GC-backed VM.
78pub fn eval(program: &Node) -> Result<Object, String> {
79    let bytecode = compile(program)?;
80    let mut vm = GcVM::new(bytecode);
81    vm.run_with_budget(usize::MAX)
82        .map_err(|error| error.message)?;
83    vm.try_export_last_result()
84}
85
86/// Parse, compile, and execute Monkey source.
87pub fn eval_source(source: &str) -> Result<Object, String> {
88    let program = parser::parse(source).map_err(|errors| errors[0].clone())?;
89    eval(&program)
90}
91
92/// Parse, compile, execute with deterministic GC settings, then collect cycles.
93pub fn run_source_with_report(
94    source: &str,
95    instruction_budget: usize,
96) -> Result<GcRunSuccess, GcRunError> {
97    let program = parser::parse(source).map_err(|errors| GcRunError {
98        stage: GcRunStage::Parse,
99        message: errors
100            .first()
101            .cloned()
102            .unwrap_or_else(|| "unknown parse error".to_string()),
103        span: None,
104    })?;
105    let mut compiler = Compiler::new();
106    let bytecode = compiler.compile(&program).map_err(|message| GcRunError {
107        stage: GcRunStage::Compile,
108        message,
109        span: None,
110    })?;
111    let global_names = compiler.symbol_table.global_symbols();
112    let mut vm = GcVM::new(bytecode);
113    vm.set_global_names(global_names);
114    vm.heap_mut().set_gc_threshold(usize::MAX);
115    vm.run_with_budget(instruction_budget)
116        .map_err(|error| GcRunError {
117            stage: GcRunStage::Runtime,
118            message: error.message,
119            span: error.span,
120        })?;
121    let result = vm.last_result_string();
122    let report = vm.collect_garbage();
123    Ok(GcRunSuccess {
124        result,
125        report,
126    })
127}