-
Notifications
You must be signed in to change notification settings - Fork 170
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Assembler: enable vendoring of compiled libraries (fixes #1435) #1643
base: next
Are you sure you want to change the base?
Conversation
129d91e
to
437a846
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Thank you. Not a full review yet, but I left a few comments inline - one of them describing a potential alternative approach.
f73998a
to
806a91e
Compare
I pushed a new version that uses the approach mentioned by @bobbinth in the comment above. At assembly time, we merge all the vendored libraries collected into a single MAST forest that is passed to the builder. On a call to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Thank you! Not a full review, but I left some comments inline.
Overall, it feels like this approach should work better than the previous one.
assembly/src/assembler/mod.rs
Outdated
pub fn add_vendored_library(&mut self, library: impl AsRef<Library>) -> Result<(), Report> { | ||
self.add_library(&library)?; | ||
self.vendored_libraries | ||
.insert(*library.as_ref().digest(), library.as_ref().clone()); | ||
Ok(()) | ||
} | ||
|
||
pub fn with_vendored_library(mut self, library: impl AsRef<Library>) -> Result<Self, Report> { | ||
self.add_vendored_library(library)?; | ||
Ok(self) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add doc comments to these functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think add_library
and add_vendored_library
are a bit confusing names, especially the former. Would it make sense to rename them dynamically_link
and statically_link
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't mind renaming, but I'm not sure dynamically_link()
and statically_link()
are better options. Curious what others think though.
d45855f
to
825057c
Compare
825057c
to
ea39c05
Compare
606ea88
to
00a4327
Compare
Are there more interesting tests that we could run? Right now there is a single test checking that a used procedure is inlined and an unused one is deleted. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Thank you. I reviewed pretty much all non-test and on-CLI code and left some comments inline.
pub fn new<'a>( | ||
vendored_libraries: impl IntoIterator<Item = &'a Library>, | ||
) -> Result<Self, Report> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add doc comments to this function.
Also, nit: I'd move it above the build()
function (e.g., above line 79).
/// Iterates over all the nodes a root depends on, in pre-order. | ||
/// The iteration can include other roots in the same forest. | ||
pub struct RootIterator<'a> { | ||
forest: &'a MastForest, | ||
discovered: Vec<MastNodeId>, | ||
unvisited: Vec<MastNodeId>, | ||
} | ||
impl<'a> RootIterator<'a> { | ||
pub fn new(root: &MastNodeId, forest: &'a MastForest) -> Self { | ||
let discovered = vec![]; | ||
let unvisited = vec![*root]; | ||
RootIterator { forest, discovered, unvisited } | ||
} | ||
} | ||
impl Iterator for RootIterator<'_> { | ||
type Item = MastNodeId; | ||
fn next(&mut self) -> Option<MastNodeId> { | ||
while let Some(id) = self.unvisited.pop() { | ||
let mut children = self.forest[id].children(); | ||
if children.is_empty() { | ||
return Some(id); | ||
}; | ||
self.discovered.push(id); | ||
self.unvisited.append(&mut children); | ||
} | ||
self.discovered.pop() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: it feels a bit weird to have this defined between MastForest
impl blocks. I would probably move down to line 657 and add a section separator there.
Also, RootIterator
feels like we are iterating over roots. Maybe it RootSubtreeIterator
or something similar would be more clear?
pub fn remap(self, remapping: &Remapping) -> Self { | ||
*remapping.get(&self).unwrap_or(&self) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add doc comments to this function.
} | ||
} | ||
|
||
pub fn children(&self) -> Vec<MastNodeId> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will result on a vector allocation on every invocation of this method. Looking at how this method is used, a more efficient alternative could be:
pub fn append_children_to(&self, target: &mut Vec<MastNodeId>) {
...
}
@@ -143,6 +143,34 @@ impl MastNode { | |||
} | |||
} | |||
|
|||
pub fn remap(&mut self, remapping: &BTreeMap<MastNodeId, MastNodeId>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add doc comments to this function.
let mut children = self.forest[id].children(); | ||
if children.is_empty() { | ||
return Some(id); | ||
}; | ||
self.discovered.push(id); | ||
self.unvisited.append(&mut children); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Related to the previous comment, we could re-write this as:
let node = self.forest[id];
if !node.has_children() {
return Some(id);
} else {
self.discovered.push(id);
node.append_children_to(&mut self.unvisited);
}
pub fn remove_nodes( | ||
&mut self, | ||
nodes_to_remove: &BTreeSet<MastNodeId>, | ||
) -> Option<BTreeMap<MastNodeId, MastNodeId>> { | ||
) -> BTreeMap<MastNodeId, MastNodeId> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this change needed for this PR?
/// Adds a compiled library that can be used to copy procedures during assembly instead of | ||
/// introducing external nodes. | ||
pub fn add_vendored_library(&mut self, library: impl AsRef<Library>) -> Result<(), Report> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would write this as:
/// Adds a compiled library procedures from which will be vendored into the assembled code.
///
/// Vendoring in this context means that when a procedure from this library is invoked from the
/// assembled code, the entire procedure MAST will be copied into the assembled code. Thus,
/// when the resulting code is executed on the VM, the vendored library does not need to be
/// provided to the VM to resolve external calls.
We should also update the description of add_library()
procedure above.
/// See [`Self::add_vendored_library`] | ||
pub fn with_vendored_library(mut self, library: impl AsRef<Library>) -> Result<Self, Report> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would write this as:
/// Adds a compiled library procedures from which will be vendored into the assembled code.
///
/// See [`Self::add_vendored_library`]
for old_id in RootIterator::new(&root_id, &self.vendored_mast.clone()) { | ||
let mut node = self.vendored_mast[old_id].clone(); | ||
node.remap(&self.vendored_remapping); | ||
let new_id = self.ensure_node(node)?; | ||
self.vendored_remapping.insert(old_id, new_id); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this will copy the node + its decorators. But where do we copy the advice map data from the vendored libraries?
@plafer, @bitwalker - could you also take a look at this PR? |
Before this MR, the only ways to link a library during assembly are:
This MR adds the possibility to link a compiled library during assembly but have its MAST forest be merged in the resulting library/program. Two desirable properties: