use crate::{
garbage_collector::{GarbageCollector, UnsafeTypeInfo},
DispatchTable,
};
use abi::{AssemblyInfo, FunctionPrototype};
use anyhow::anyhow;
use libloader::{MunLibrary, TempLibrary};
use log::error;
use memory::mapping::{Mapping, MemoryMapper};
use std::{
collections::HashMap,
ffi::c_void,
path::{Path, PathBuf},
ptr::NonNull,
sync::Arc,
};
pub struct Assembly {
library_path: PathBuf,
library: TempLibrary,
legacy_libs: Vec<TempLibrary>,
info: AssemblyInfo,
allocator: Arc<GarbageCollector>,
}
impl Assembly {
pub fn load(library_path: &Path, gc: Arc<GarbageCollector>) -> Result<Self, anyhow::Error> {
let mut library = MunLibrary::new(library_path)?;
let version = library.get_abi_version();
if abi::ABI_VERSION != version {
return Err(anyhow::anyhow!(
"ABI version mismatch. munlib is `{}` but runtime is `{}`",
version,
abi::ABI_VERSION
));
}
let allocator_ptr = Arc::into_raw(gc.clone()) as *mut std::ffi::c_void;
library.set_allocator_handle(allocator_ptr);
let info = library.get_info();
let assembly = Assembly {
library_path: library_path.to_path_buf(),
library: library.into_inner(),
legacy_libs: Vec::new(),
info,
allocator: gc,
};
Ok(assembly)
}
fn link_all_impl<'a>(
dispatch_table: &mut DispatchTable,
to_link: impl Iterator<Item = (&'a mut *const c_void, &'a FunctionPrototype)>,
) -> anyhow::Result<()> {
let mut to_link: Vec<_> = to_link.collect();
let mut retry = true;
while retry {
retry = false;
let mut failed_to_link = Vec::new();
for (dispatch_ptr, fn_prototype) in to_link.into_iter() {
if let Some(fn_def) = dispatch_table.get_fn(fn_prototype.name()) {
if fn_prototype.signature != fn_def.prototype.signature {
return Err(anyhow!("Failed to link: function '{}' is missing. A function with the same name does exist, but the signatures do not match (expected: {}, found: {}).", fn_prototype.name(), fn_prototype, fn_def.prototype));
}
*dispatch_ptr = fn_def.fn_ptr;
retry = true;
} else {
failed_to_link.push((dispatch_ptr, fn_prototype));
}
}
to_link = failed_to_link;
}
if !to_link.is_empty() {
for (_, fn_prototype) in to_link {
error!(
"Failed to link: function `{}` is missing.",
fn_prototype.name()
);
}
return Err(anyhow!("Failed to link due to missing dependencies."));
}
Ok(())
}
pub(super) fn link_all<'a>(
assemblies: impl Iterator<Item = &'a mut Assembly>,
dispatch_table: &DispatchTable,
) -> anyhow::Result<DispatchTable> {
let assemblies: Vec<&'a mut _> = assemblies.collect();
let mut dispatch_table = dispatch_table.clone();
for assembly in assemblies.iter() {
for function in assembly.info().symbols.functions() {
dispatch_table.insert_fn(function.prototype.name(), function.clone());
}
}
let to_link: Vec<_> = assemblies
.into_iter()
.flat_map(|asm| asm.info.dispatch_table.iter_mut())
.filter(|(ptr, _)| ptr.is_null())
.collect();
Assembly::link_all_impl(&mut dispatch_table, to_link.into_iter())?;
Ok(dispatch_table)
}
pub(super) fn relink_all(
unlinked_assemblies: &mut HashMap<PathBuf, Assembly>,
linked_assemblies: &mut HashMap<PathBuf, Assembly>,
dispatch_table: &DispatchTable,
) -> anyhow::Result<DispatchTable> {
let mut assemblies = unlinked_assemblies
.iter_mut()
.map(|(old_path, asm)| {
let old_assembly = linked_assemblies.get(old_path);
(asm, old_assembly)
})
.collect::<Vec<_>>();
let mut dispatch_table = dispatch_table.clone();
for (_, old_assembly) in assemblies.iter() {
if let Some(assembly) = old_assembly {
for function in assembly.info.symbols.functions() {
dispatch_table.remove_fn(function.prototype.name());
}
}
}
for (new_assembly, _) in assemblies.iter() {
for function in new_assembly.info().symbols.functions() {
dispatch_table.insert_fn(function.prototype.name(), function.clone());
}
}
let to_link = assemblies
.iter_mut()
.flat_map(|(asm, _)| asm.info.dispatch_table.iter_mut())
.filter(|(ptr, _)| ptr.is_null());
Assembly::link_all_impl(&mut dispatch_table, to_link)?;
let assemblies_to_map: Vec<_> = assemblies
.into_iter()
.filter_map(|(new_asm, old_asm)| old_asm.map(|old_asm| (old_asm, new_asm)))
.collect();
let mut assemblies_to_keep = HashMap::new();
for (old_assembly, new_assembly) in assemblies_to_map.iter() {
let old_types: Vec<UnsafeTypeInfo> = old_assembly
.info
.symbols
.types()
.iter()
.map(|ty| {
UnsafeTypeInfo::new(unsafe {
NonNull::new_unchecked(*ty as *const abi::TypeInfo as *mut _)
})
})
.collect();
let new_types: Vec<UnsafeTypeInfo> = new_assembly
.info
.symbols
.types()
.iter()
.map(|ty| {
UnsafeTypeInfo::new(unsafe {
NonNull::new_unchecked(*ty as *const abi::TypeInfo as *mut _)
})
})
.collect();
let mapping = Mapping::new(&old_types, &new_types);
let deleted_objects = old_assembly.allocator.map_memory(mapping);
if !deleted_objects.is_empty() {
assemblies_to_keep.insert(
old_assembly.library_path().to_path_buf(),
new_assembly.library_path().to_path_buf(),
);
}
}
let mut newly_linked = HashMap::new();
std::mem::swap(unlinked_assemblies, &mut newly_linked);
for (old_path, mut new_assembly) in newly_linked.into_iter() {
let mut old_assembly = linked_assemblies
.remove(&old_path)
.expect("Assembly must exist.");
let new_path = if let Some(new_path) = assemblies_to_keep.remove(&old_path) {
new_assembly
.legacy_libs
.append(&mut old_assembly.legacy_libs);
new_assembly.legacy_libs.push(old_assembly.into_library());
new_path
} else {
new_assembly.library_path().to_path_buf()
};
linked_assemblies.insert(new_path, new_assembly);
}
Ok(dispatch_table)
}
pub fn info(&self) -> &AssemblyInfo {
&self.info
}
pub fn library_path(&self) -> &Path {
self.library_path.as_path()
}
pub fn into_library(self) -> TempLibrary {
self.library
}
}