Skip to content

Commit

Permalink
feat(runtime): add runtime re-linking of dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
Wodann committed Mar 7, 2021
1 parent 8db3113 commit 63db093
Show file tree
Hide file tree
Showing 9 changed files with 306 additions and 180 deletions.
39 changes: 18 additions & 21 deletions crates/mun/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,12 @@ where
Arg::with_name("manifest-path")
.long("manifest-path")
.takes_value(true)
.help(&format!("Path to {}", MANIFEST_FILENAME))
)
.arg(
Arg::with_name("watch")
.long("watch")
.help("Run the compiler in watch mode.\
Watch input files and trigger recompilation on changes.",)
.help(&format!("Path to {}", MANIFEST_FILENAME)),
)
.arg(Arg::with_name("watch").long("watch").help(
"Run the compiler in watch mode.\
Watch input files and trigger recompilation on changes.",
))
.arg(
Arg::with_name("opt-level")
.short("O")
Expand All @@ -71,7 +69,7 @@ where
.arg(
Arg::with_name("emit-ir")
.long("emit-ir")
.help("emits IR instead of a *.munlib")
.help("emits IR instead of a *.munlib"),
)
.about("Compiles a local Mun file into a module"),
)
Expand All @@ -88,25 +86,24 @@ where
.long("entry")
.takes_value(true)
.help("the function entry point to call on startup"),
)
.arg(
Arg::with_name("delay")
.long("delay")
.takes_value(true)
.help("how much to delay received filesystem events (in ms). This allows bundling of identical events, e.g. when several writes to the same file are detected. A high delay will make hot reloading less responsive. (defaults to 10 ms)"),
),
)
.subcommand(
SubCommand::with_name("new")
.arg(Arg::with_name("path").help("the path to create a new project").required(true).index(1))
)
.subcommand(
SubCommand::with_name("init")
.arg(Arg::with_name("path").help("the path to create a new project [default: .]").index(1))
SubCommand::with_name("new").arg(
Arg::with_name("path")
.help("the path to create a new project")
.required(true)
.index(1),
),
)
.subcommand(
SubCommand::with_name("language-server")
SubCommand::with_name("init").arg(
Arg::with_name("path")
.help("the path to create a new project [default: .]")
.index(1),
),
)
.subcommand(SubCommand::with_name("language-server"))
.get_matches_from_safe(args);

match matches {
Expand Down
11 changes: 1 addition & 10 deletions crates/mun/src/ops/start.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::time::Duration;
use std::{cell::RefCell, rc::Rc};

use anyhow::anyhow;
use clap::ArgMatches;
Expand Down Expand Up @@ -57,12 +55,5 @@ fn runtime(matches: &ArgMatches) -> Result<Rc<RefCell<Runtime>>, anyhow::Error>
matches.value_of("LIBRARY").unwrap(), // Safe because its a required arg
);

let builder = if let Some(delay) = matches.value_of("delay") {
let delay: u64 = delay.parse()?;
builder.set_delay(Duration::from_millis(delay))
} else {
builder
};

builder.spawn()
}
8 changes: 2 additions & 6 deletions crates/mun_compiler/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,11 @@ pub use self::config::Config;
pub use self::display_color::DisplayColor;

use crate::diagnostics_snippets::{emit_hir_diagnostic, emit_syntax_error};
use mun_project::Package;
use std::collections::HashMap;
use std::convert::TryInto;
use std::path::Path;
use std::time::Duration;
use mun_project::{Package, LOCKFILE_NAME};
use std::{collections::HashMap, convert::TryInto, path::Path, time::Duration};
use walkdir::WalkDir;

pub const WORKSPACE: SourceRootId = SourceRootId(0);
pub const LOCKFILE_NAME: &str = ".munlock";

pub struct Driver {
db: CompilerDatabase,
Expand Down
1 change: 1 addition & 0 deletions crates/mun_project/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ mod package;
mod project_manifest;

pub const MANIFEST_FILENAME: &str = "mun.toml";
pub const LOCKFILE_NAME: &str = ".munlock";
1 change: 1 addition & 0 deletions crates/mun_runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ libloader = { version = "=0.1.0", path = "../mun_libloader", package = "mun_libl
log = "0.4"
md5 = "0.7.0"
memory = { version = "=0.1.0", path = "../mun_memory", package = "mun_memory" }
mun_project = { version = "=0.1.0", path = "../mun_project" }
notify = "4.0.12"
once_cell = "1.4.0"
parking_lot = "0.10"
Expand Down
210 changes: 141 additions & 69 deletions crates/mun_runtime/src/assembly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ use crate::{
garbage_collector::{GarbageCollector, UnsafeTypeInfo},
DispatchTable,
};
use abi::AssemblyInfo;
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,
Expand Down Expand Up @@ -52,31 +54,12 @@ impl Assembly {
Ok(assembly)
}

/// Tries to link the `assemblies`, resulting in a new [`DispatchTable`] on success. This leaves
/// the original `dispatch_table` intact, in case of linking errors.
pub(super) fn link_all<'a>(
assemblies: impl Iterator<Item = &'a mut Assembly>,
dispatch_table: &DispatchTable,
) -> Result<DispatchTable, anyhow::Error> {
let assemblies: Vec<&'a mut _> = assemblies.collect();

// Clone the dispatch table, such that we can roll back if linking fails
let mut dispatch_table = dispatch_table.clone();

// Insert all assemblies' functions into the dispatch table
for assembly in assemblies.iter() {
for function in assembly.info().symbols.functions() {
dispatch_table.insert_fn(function.prototype.name(), function.clone());
}
}

let mut to_link: Vec<_> = assemblies
.into_iter()
.flat_map(|asm| asm.info.dispatch_table.iter_mut())
// Only take signatures into account that do *not* yet have a function pointer assigned
// by the compiler.
.filter(|(ptr, _)| ptr.is_null())
.collect();
/// Private implementation of runtime linking
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 {
Expand Down Expand Up @@ -114,65 +97,154 @@ impl Assembly {
return Err(anyhow!("Failed to link due to missing dependencies."));
}

Ok(())
}

/// Tries to link the `assemblies`, resulting in a new [`DispatchTable`] on success. This leaves
/// the original `dispatch_table` intact, in case of linking errors.
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();

// Clone the dispatch table, such that we can roll back if linking fails
let mut dispatch_table = dispatch_table.clone();

// Insert all assemblies' functions into the dispatch table
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())
// Only take signatures into account that do *not* yet have a function pointer assigned
// by the compiler.
.filter(|(ptr, _)| ptr.is_null())
.collect();

Assembly::link_all_impl(&mut dispatch_table, to_link.into_iter())?;

Ok(dispatch_table)
}

/// Swaps the assembly's shared library and its information for the library at `library_path`.
pub fn swap(
&mut self,
library_path: &Path,
runtime_dispatch_table: &mut DispatchTable,
) -> Result<(), anyhow::Error> {
let mut new_assembly = Assembly::load(library_path, self.allocator.clone())?;

let old_types: Vec<UnsafeTypeInfo> = self
.info
.symbols
.types()
.iter()
.map(|ty| {
// Safety: `ty` is a shared reference, so is guaranteed to not be `ptr::null()`.
UnsafeTypeInfo::new(unsafe {
NonNull::new_unchecked(*ty as *const abi::TypeInfo as *mut _)
})
/// Tries to link the `assemblies`, resulting in a new [`DispatchTable`] on success. This leaves
/// the original `dispatch_table` intact, in case of linking errors.
pub(super) fn relink_all<'a, 'b>(
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<_>>();

// Clone the dispatch table, such that we can roll back if linking fails
let mut dispatch_table = dispatch_table.clone();

// Remove the old assemblies' functions from the dispatch table
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());
}
}
}

// Insert all assemblies' functions into the dispatch table
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())
// Only take signatures into account that do *not* yet have a function pointer assigned
// by the compiler.
.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 new_types: Vec<UnsafeTypeInfo> = new_assembly
.info
.symbols
.types()
.iter()
.map(|ty| {
// Safety: `ty` is a shared reference, so is guaranteed to not be `ptr::null()`.
UnsafeTypeInfo::new(unsafe {
NonNull::new_unchecked(*ty as *const abi::TypeInfo as *mut _)
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| {
// Safety: `ty` is a shared reference, so is guaranteed to not be `ptr::null()`.
UnsafeTypeInfo::new(unsafe {
NonNull::new_unchecked(*ty as *const abi::TypeInfo as *mut _)
})
})
})
.collect();
.collect();

let mapping = Mapping::new(&old_types, &new_types);
let deleted_objects = self.allocator.map_memory(mapping);
let new_types: Vec<UnsafeTypeInfo> = new_assembly
.info
.symbols
.types()
.iter()
.map(|ty| {
// Safety: `ty` is a shared reference, so is guaranteed to not be `ptr::null()`.
UnsafeTypeInfo::new(unsafe {
NonNull::new_unchecked(*ty as *const abi::TypeInfo as *mut _)
})
})
.collect();

// Remove the old assembly's functions
for function in self.info.symbols.functions() {
runtime_dispatch_table.remove_fn(function.prototype.name());
let mapping = Mapping::new(&old_types, &new_types);
let deleted_objects = old_assembly.allocator.map_memory(mapping);

if !deleted_objects.is_empty() {
// Retain the previous assembly
assemblies_to_keep.insert(
old_assembly.library_path().to_path_buf(),
new_assembly.library_path().to_path_buf(),
);
}
}

new_assembly.link(runtime_dispatch_table);
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) {
// Retain all existing legacy libs
new_assembly
.legacy_libs
.append(&mut old_assembly.legacy_libs);

// Retain all existing legacy libs
new_assembly.legacy_libs.append(&mut self.legacy_libs);
new_assembly.legacy_libs.push(old_assembly.into_library());

std::mem::swap(self, &mut new_assembly);
let old_assembly = new_assembly;
new_path
} else {
new_assembly.library_path().to_path_buf()
};

if !deleted_objects.is_empty() {
// Retain the previous assembly
self.legacy_libs.push(old_assembly.into_library());
linked_assemblies.insert(new_path, new_assembly);
}

Ok(())
Ok(dispatch_table)
}

/// Returns the assembly's information.
Expand Down
Loading

0 comments on commit 63db093

Please sign in to comment.