use std::collections::BTreeMap;
use crate::check::core;
use crate::compile::symbols::CompiledItem;
use crate::eval::interp::Uncompilable;
use crate::{EvalError, Heap, Interp, Path, Value};
pub fn load(heap: &mut Heap, chk: &mut crate::Checker, interp: &mut Interp) {
let units = typelisp_front::dump::parse(crate::prelude::DUMP, "prelude")
.unwrap_or_else(|e| panic!("prelude: {}", e));
let unit = units.first().unwrap_or_else(|| panic!("prelude: the committed dump holds no units"));
let state =
crate::compile::dump::read_types(unit, "prelude").unwrap_or_else(|e| panic!("prelude: {}", e));
typelisp_front::dump::verify_sources_digest(&state, crate::prelude::DUMPED_SOURCES, REGEN_SCRIPT)
.unwrap_or_else(|e| panic!("{}", e));
crate::compile::dump::load_unit(heap, chk, interp, state, unit.bitcode)
.unwrap_or_else(|e| panic!("prelude: {}", e));
interp.push_dump_source(std::borrow::Cow::Borrowed(crate::prelude::DUMP));
}
pub struct AotPrelude {
pub bitcode: &'static [u8],
pub global_inits: Vec<(Path, Value)>,
}
pub fn load_for_aot(heap: &mut Heap, chk: &mut crate::Checker, interp: &mut Interp) -> Result<AotPrelude, String> {
let units = typelisp_front::dump::parse(crate::prelude::DUMP, "prelude")?;
let unit = units.first().ok_or_else(|| "prelude: the committed dump holds no units".to_string())?;
let state = crate::compile::dump::read_types(unit, "prelude")?;
typelisp_front::dump::verify_sources_digest(&state, crate::prelude::DUMPED_SOURCES, REGEN_SCRIPT)?;
let mut global_inits: Vec<(Path, Value)> = Vec::new();
for f in &state.forms {
let tl = crate::owned_form::owned_to_value(heap, f).map_err(|e| e.to_string())?;
heap.push_permanent_root(tl);
collect_defvars(heap, tl, &mut global_inits)?;
}
let globals = state.globals.clone();
crate::compile::dump::load_unit_types_only(heap, chk, interp, state)?;
if globals.len() != global_inits.len()
|| globals.iter().zip(&global_inits).any(|((name, _), (path, _))| name != &path.to_string())
{
return Err(format!(
"prelude: the dump records {} global(s) but its forms hold {} `defvar`(s), or they are \
in a different order — an AOT executable cannot reproduce the slot numbering the \
bitcode was compiled against",
globals.len(),
global_inits.len()
));
}
Ok(AotPrelude { bitcode: unit.bitcode, global_inits })
}
fn collect_defvars(heap: &Heap, tl: Value, out: &mut Vec<(Path, Value)>) -> Result<(), String> {
if core::op_is(heap, tl, typelisp_mem::wk::MODULE) {
let body = core::fields(heap, tl).map_err(|e| e.to_string())?;
for item in body.into_iter().skip(1) {
collect_defvars(heap, item, out)?;
}
} else if core::op_is(heap, tl, typelisp_mem::wk::DEFVAR) {
let path = core::path_field(heap, tl, 0).ok_or_else(|| "prelude: defvar without a name".to_string())?;
out.push((path, tl));
}
Ok(())
}
pub use crate::prelude::REGEN_SCRIPT;
pub fn load_interpreted_plan(heap: &mut Heap, chk: &mut crate::Checker, interp: &mut Interp) -> PreludePlan {
let mut plan = PreludePlan::default();
let mut read_forms: Vec<crate::Value> = Vec::new();
crate::prelude::load_interpreted_with(
heap,
chk,
interp,
&mut |_heap, v| {
read_forms.push(v);
},
&mut |heap, tl| {
heap.push_root(tl);
plan.forms.push(tl);
collect_item(heap, tl, &mut plan).expect("prelude: collect failed");
},
);
plan.source_hash = crate::compile::bootstrap::hash_read_forms(heap, &read_forms)
.expect("prelude: hashing the read forms failed");
plan
}
pub const PRELUDE_COMPILE_UNSUPPORTED: &[(&str, &str)] = &[];
#[derive(Default)]
pub struct PreludePlan {
pub items: Vec<CompiledItem>,
pub globals: Vec<Path>,
pub forms: Vec<Value>,
pub source_hash: u64,
}
impl PreludePlan {
pub fn compilable(&self, heap: &Heap, interp: &Interp) -> Vec<CompiledItem> {
self.items
.iter()
.filter(|item| crate::compile::driver::precheck_compilable(interp, heap, &item.node_name()).is_ok())
.cloned()
.collect()
}
}
pub(crate) fn collect_item(heap: &Heap, tl: Value, plan: &mut PreludePlan) -> Result<(), String> {
let tag = core::op(heap, tl).map(str::to_string).unwrap_or_default();
match tag.as_str() {
"module" => {
let body = core::fields(heap, tl).map_err(|e| e.to_string())?;
for item in body.into_iter().skip(1) {
collect_item(heap, item, plan)?;
}
}
"defun" => {
let path = core::path_field(heap, tl, 0).ok_or_else(|| "prelude: defun without a name".to_string())?;
plan.items.push(CompiledItem::Fn(path));
}
"defmethod" => {
let type_path = core::path_field(heap, tl, 0).ok_or_else(|| "prelude: defmethod without a type".to_string())?;
let method = match core::field(heap, tl, 1) {
Some(Value::Symbol(id)) => heap.symbol_name(id).to_string(),
_ => return Err("prelude: defmethod without a name".to_string()),
};
plan.items.push(CompiledItem::Method(type_path, method));
}
"defvar" => {
let path = core::path_field(heap, tl, 0).ok_or_else(|| "prelude: defvar without a name".to_string())?;
plan.globals.push(path);
}
"defmacro" => {
let path = core::path_field(heap, tl, 0).ok_or_else(|| "prelude: defmacro without a name".to_string())?;
plan.items.push(CompiledItem::Fn(path));
}
"defstruct" | "defenum" | "use" | "defffi" => {}
other => {
return Err(format!(
"prelude: top-level `{}` has no place in the compiled artifact — \
the prelude is definitions only (defun/defmethod/defvar/defconstant/\
defstruct/defenum/defmacro/deftrait/impl/module)",
other
))
}
}
Ok(())
}
pub fn promote_globals(
heap: &mut Heap,
interp: &Interp,
plan: &PreludePlan,
) -> Result<Vec<(String, usize)>, String> {
let mut out = Vec::with_capacity(plan.globals.len());
for path in &plan.globals {
let id =
interp.promote_global(heap, path).map_err(|e| format!("prelude: promoting `{}`: {}", path, e))?;
out.push((path.to_string(), id));
}
Ok(out)
}
fn reconcile_unsupported(heap: &Heap, interp: &Interp, plan: &PreludePlan) -> Result<(), String> {
let mut blockers: BTreeMap<String, Vec<String>> = BTreeMap::new();
let mut broken: Vec<(String, EvalError)> = Vec::new();
for item in &plan.items {
let node = item.node_name();
match crate::compile::driver::precheck_compilable(interp, heap, &node) {
Ok(()) => {}
Err(Uncompilable::MissingTarget(target)) => blockers.entry(target).or_default().push(node),
Err(Uncompilable::Other(e)) => broken.push((node, e)),
}
}
let listed: Vec<&str> = PRELUDE_COMPILE_UNSUPPORTED.iter().map(|(t, _)| *t).collect();
let unlisted: Vec<&String> = blockers.keys().filter(|t| !listed.contains(&t.as_str())).collect();
let gone: Vec<&str> = listed.iter().copied().filter(|t| !blockers.contains_key(*t)).collect();
if broken.is_empty() && unlisted.is_empty() && gone.is_empty() {
return Ok(());
}
let mut msg = String::new();
for (node, e) in &broken {
msg.push_str(&format!("prelude: compiling `{}` broke on its own shape: {}\n", node, e));
}
if !unlisted.is_empty() || !gone.is_empty() {
msg.push_str("prelude: PRELUDE_COMPILE_UNSUPPORTED does not match what the compiler can do.\n");
for target in &unlisted {
msg.push_str(&format!(" blocks compilation, but not listed: {}\n", target));
}
for target in &gone {
msg.push_str(&format!(" listed, but blocks nothing now: {}\n", target));
}
msg.push_str("\nThe current set, ready to paste into PRELUDE_COMPILE_UNSUPPORTED:\n");
for (target, blocked) in &blockers {
msg.push_str(&format!(" ({:?}, {:?}), // {} definition(s)\n", target, blocked[0], blocked.len()));
}
}
Err(msg)
}
pub fn build_prelude_artifact() -> Result<Vec<u8>, String> {
let mut heap = Heap::with_capacity(1 << 18);
let mut chk = crate::Checker::new();
let mut interp = Interp::new();
let before = chk.signature(&heap)?;
let plan = load_interpreted_plan(&mut heap, &mut chk, &mut interp);
let globals = promote_globals(&mut heap, &interp, &plan)?;
let delta = chk.capture_delta(&heap, &before)?;
crate::load_compiler(&mut heap, &mut chk, &mut interp);
reconcile_unsupported(&heap, &interp, &plan)?;
let items = plan.compilable(&heap, &interp);
let module = crate::compile::driver::fresh_module_with_declarations("prelude_compiled", &items);
for item in &items {
let node = item.node_name();
crate::compile::driver::add_compiled_function(&interp, &mut heap, module.clone(), &node, &item.symbol_name()).map_err(|e| {
format!(
"prelude: compiling `{}` failed: {} — the precheck said it was fine, so this is a \
gap the call-graph walk cannot see rather than a missing lowering to record in \
PRELUDE_COMPILE_UNSUPPORTED",
node, e
)
})?;
}
if !interp.vtable_descriptors().is_empty() || !interp.upcast_descriptors().is_empty() {
return Err(
"prelude: a compiled body boxes or upcasts a trait object, whose vtable/trait ids are \
baked in per site — the artifact needs an ordered replay of those tables at load time \
before this can be shipped (see this function's comment)"
.to_string(),
);
}
let bitcode = {
let _guard = crate::compile::COMPILE_LOCK.lock().unwrap();
let bitcode = {
let m = module.borrow();
crate::compile::verify_module_naming_functions(&m, "prelude module").map(|()| {
m.write_bitcode_to_memory().as_slice().to_vec()
})
};
drop(module);
bitcode?
};
let state = typelisp_front::dump::capture_types_with_abi(
&heap,
delta,
"prelude",
Some(typelisp_front::dump::sources_digest(crate::prelude::DUMPED_SOURCES)),
Some(plan.source_hash),
&plan.forms,
items.iter().map(unit_item).collect(),
globals,
crate::compile::EMITTED_BODY_ABI,
crate::compile::EMITTED_BODY_ABI,
crate::compile::EMITTED_LAYOUT,
crate::compile::EMITTED_LAYOUT,
)?;
let types = typelisp_front::dump::write_state(&state)?;
Ok(typelisp_front::dump::write(&[(types, bitcode)]))
}
pub(crate) fn unit_item(item: &CompiledItem) -> typelisp_front::dump::UnitItem {
match item {
CompiledItem::Fn(path) => typelisp_front::dump::UnitItem::Fn(path.clone()),
CompiledItem::Method(path, name) => {
typelisp_front::dump::UnitItem::Method(path.clone(), name.clone())
}
}
}