-
Notifications
You must be signed in to change notification settings - Fork 173
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
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.
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.
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?
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 added a merge_advice_map
step that it's triggered at every call of vendor_or_ensure_external
.
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.
Would it not be pretty expensive to do this on every call to vendor_or_ensure_external()
? I there reason not to do this at the very end (e.g., in MastForestBuilder::build()
)?
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.
Yes, the other option is to do it in the constructor new
or at the end in build
. In that case we risk copying the advice map even if we didn't vendor anything. I think both approaches are ok. If you used new
with vendored libraries you probably are going to vendor something, so we could copy the advice map in there.
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'm fine with the current approach - but let's create an issue to improve on it. A simple short-term improvement could be to keep track if at least one procedure was vendored. A more long-term approach would be to somehow track of which key-value pairs from the advice map are required but the set of vendored procedures.
@plafer, @bitwalker - could you also take a look at this PR? |
let mut node = self.vendored_mast[old_id].clone(); | ||
node.remap(&self.vendored_remapping); |
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.
Mutably remapping a MastNode
sounds dangerous to me. Instead of cloning + mutating the copy, would it not be better to make remap methods be fn remap(&self, &Remapping) -> MastNode
?
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'm not sure it's dangerous, Rust tracks mutability very explicitly. With a mutable remapping you have the choice to use additional memory by cloning+mutating like above or you can directly mutate if you are not using the old node. If the function implicitly returns a new Node then you are always forced to use additional memory.
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 guess one question I had here is whether we are doing a lot of extra work with the current approach. Specifically, I think remap()
recursively updates all the children, but then we iterate through all the children here as well. Or am I misunderstanding how it works?
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.
No, it's not recursive, remap
will simply change the ids of the children of the current node.
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'm not sure it's dangerous, Rust tracks mutability very explicitly.
I meant "dangerous" more in the sense of "bug-prone" - e.g. if you remove the clone()
on line 481, everything still compiles, but you're mutating the wrong MastNode
(and it's relatively easy to miss).
If the function implicitly returns a new Node then you are always forced to use additional memory.
Not necessarily, the value returned could be used to mutate a node in an existing MastForest
, e.g.
let node = self.vendored_mast[old_id].remap(...);
self.vendored_mast[old_id] = node;
and I would expect compiler optimizations to compile this down to an in-place mutation of self.vendored_mast[old_id]
.
So then it comes down to a question of which version is less bug-prone, which I believe my suggestion is? Plus, I don't expect we'll ever want to mutate a node in-place, since this implies we're modifying a MastForest
in-place (by e.g. deleting a node and needing to remap virtually all nodes), which is very bug-prone and we'll likely never want to do that.
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.
e.g. if you remove the clone() on line 481, everything still compiles, but you're mutating the wrong MastNode (and it's relatively easy to miss).
I agree that's very easy to miss, but you can't remove the clone w/o having a compiler error. Unless I'm missing something.
I don't expect we'll ever want to mutate a node in-place,
I agree.
I don't mind changing it to return a new node, I'm still trying to understand where to use different styles (functional vs mutable).
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.
you can't remove the clone w/o having a compiler error. Unless I'm missing something.
Right, I was being imprecise. I meant basically writing self.vendored_mast[old_id].remap(...)
is allowed - but then in this specific case the next call to self.ensure_node()
then fails to compile before the MastForest
is behind an Arc
.
Ultimately this was more a minor hunch for me more than anything else, and I'm okay with leaving it as is.
3b35e0a
to
902fcee
Compare
I'd like to improve the tests before merging. Working on it now. |
63358ce
to
52ea6a6
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.
LGTM!
Also, we could create an issue for adding a test that checks that the advice provider is correctly vendored as well.
let advice_map = mast_forest.advice_map_mut(); | ||
*advice_map = vendored_mast.advice_map().clone(); |
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: I would write this on one line for better readability
*mast_forest.advice_map_mut() = vendored_mast.advice_map().clone();
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.
done
52ea6a6
to
622dee4
Compare
Issue for the adviceMap test #1655 |
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 left some comments line (mostly minor nits). Once these are addressed, we can merge.
622dee4
to
6d04a86
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.
All looks good! Thank you!
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: