-
Notifications
You must be signed in to change notification settings - Fork 23
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
Implement subgraph splitting for overflow resolution #388
Conversation
a122fdf
to
357be56
Compare
write-fonts/src/graph.rs
Outdated
|
||
assert!(!overflows.is_empty()); | ||
self.debug_overflows(&overflows); | ||
//FIXME: return some kind of error here |
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 suggest we might err on the side of issues over comments for this sort of thing, with possible exception for cases where we're going to fix it in O(days)
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.
Oops this comment is out of date; we check the return and convert to an error in the dump_table
fn.
n_overflows == 0 | ||
|
||
// now isolate spaces in a loop, until there are no more left: | ||
let overflows = loop { |
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.
Can you write this as while !overflows.is_empty()
instead of sticking a return in the middle? Hm. Or structure it as iteration?
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.
The problem is that we aren't always going to continue until there are no overflows left; sometimes overflow is unavoidable.
The control flow here is:
- check if there are overflows
- if there are none, return
- if there are some, try to make progress on resolving them
- if we don't make progress, break the loop and print diagnostics
This should always terminate, since we exit the loop if either there are no overflows, or if there are overflows but there are no splittable subgraphs remaining.
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.
The comment above reads as if we are going to continue until there are none so that might need a fix then. IMHO while primary-exit-condition is still nicer, even if there is another path toward failure.
What about using Result<(), SomeErrType>
so you can use normal control flow, e.g. try_splitting_subgraphs(...)?
?
} | ||
} | ||
} | ||
result | ||
} | ||
|
||
fn debug_overflows(&self, overflows: &[Overflow]) { | ||
let (parents, children): (HashSet<_>, HashSet<_>) = |
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.
if debug is not enabled early exit
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.
also should be behind log feature per other conversation? - np if that's out of scope here though.
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.
yea we don't have a log feature yet, will revisit that when I make that change (opened #397 to track this)
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.
It looks like the early exit for log disabled - avoiding the iterations and zips to produce logs we never write - was not done?
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.
We don't have a log feature, so there's nothing to use to gate this code. In any case this exists right now as a utility for me to use while I am implementing the splitting/promotion stuff.
for overflow in overflows { | ||
let child_space = self.nodes[&overflow.child].space; | ||
// we only isolate subgraphs in wide-space | ||
if !child_space.is_custom() || self.num_roots_per_space[&child_space] <= 1 { | ||
continue; | ||
} |
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.
Optional, looks like this would fit very naturally as .iter().filter()
?
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.
It doesn't feel like a clear improvement, because i want to reuse the child_space
binding outside of the filter. 🤷
for overflow in overflows.iter().filter(|overflow| {
let child_space = self.nodes[&overflow.child].space;
// we only isolate subgraphs in wide-space
!(!child_space.is_custom() || self.num_roots_per_space[&child_space] <= 1)
}) {
let child_space = self.nodes[&overflow.child].space;
let root = self.find_root_of_space(overflow.child);
debug_assert_eq!(self.nodes[&root].space, child_space);
to_isolate
.entry(child_space)
.or_insert_with(HashSet::new)
.insert(root);
}
|
||
if to_isolate.is_empty() { | ||
return false; | ||
} |
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.
At a glance it looks like you could write this as to_isolate = overflows.iter.filter.map.collect?
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.
The reason I do this in a loop is because I want to use the 'entry' method on HashMap
, which doesn't work with collect. Basically I'm finding multiple roots per space.
// │ └─8─┘ │ / \ / \ | ||
// │ │ (cov tables) 7' 8' 7 8 | ||
// └───7───┘ | ||
// |
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.
Diagram appreciated, without it the test would be inscrutable
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.
Agreed, I found it impossible to come up with good test cases without drawing them out first.
In the case that a subgraph in 32-bit space contains an overflow, we will now attempt to select some set of roots in that subgraph and move them to a new space, duplicating any children as required.
QQ: |
Hmm... I would expect the process to only be running for a specific compilation, which even in the case of a very large multi-access font it's hard to imagine there being 4.2e9 unique objects? And in that case I would definitely expect us to be on a 64-bit system. That said, it wouldn't hurt... |
A port of the harfbuzz implementation; when overflows occur in subgraphs with multiple 32-bit roots (e.g. extension subtables sharing coverage tables or similar) we will iteratively select some subset of those roots and move them to another subgraph, duplicating and dependencies.
based on #386, which should go in first.