-
Notifications
You must be signed in to change notification settings - Fork 234
Improve logic around data directive references #261
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
Conversation
ui/src/asm_cleanup.rs
Outdated
for label_ref_cap in LABEL_REF_REGEX.captures_iter(line).skip(1).filter_map(|cap| cap.get(1)) { | ||
opcode_operands.insert(label_ref_cap.as_str()); | ||
// Capture unique labels referenced by data dirs in the current label | ||
let label_set = data_dir_label_refs.entry(current_label).or_insert(HashSet::new()); |
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.
.or_insert_with(HashSet::new)
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.
What's the advantage of HashSet::new
over HashSet::new()
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.
It avoids creating the HashSet
when the value already exists.
In this case, it's not a big benefit as an empty HashSet
doesn't allocate memory and is just a few pointers-worth of stack data. In other cases, it can have strong performance implications I (and clippy!) prefer it this way to be clear that the code only executes when needed.
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 I understand, the function in or_insert
is eagerly evaluated, but or_insert_with
is lazy, right?
If that's correct, I might put a PR in for the entry API docs to clarify this; I had to look at the "or" method under Result for the implications to be spelled out.
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 function in
or_insert
is eagerly evaluated, but or_insert_with is lazy, right?
That's right, except that or_insert
doesn't take a function/closure, but only a value, so it must be evaluated.
ui/src/asm_cleanup.rs
Outdated
opcode_operands.insert(label_ref_cap.as_str()); | ||
// Capture unique labels referenced by data dirs in the current label | ||
let label_set = data_dir_label_refs.entry(current_label).or_insert(HashSet::new()); | ||
&label_set.insert(label_ref_cap.as_str()); |
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.
Why is a reference being taken 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.
Mistake, I misread the docs for or_insert
.
ui/src/asm_cleanup.rs
Outdated
let mut iteration_count = 0; | ||
const MAX_ITERATIONS: u32 = 100; // Safeguard against hangs, should not be an issue but better to be safe | ||
|
||
while new_used_label_found && iteration_count < MAX_ITERATIONS { |
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.
Since the iteration limit is a safety concern, let's promote it to the primary loop condition and make it less prone to failure. Untested changes:
let mut checked_labels = HashSet::new();
const MAX_ITERATIONS: u32 = 100; // Safeguard against hangs, should not be an issue but better to be safe
for _ in 0..MAX_ITERATIONS {
let mut new_used_label_found = false;
for (label, references) in &data_dir_label_refs {
if used_labels.contains(label) && checked_labels.insert(label) {
new_used_label_found = true;
for referenced_label in references.iter() {
used_labels.insert(referenced_label);
}
}
}
if !new_used_label_found {
break;
}
}
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.
That's a big improvement, thanks!
To be clear, I have not experienced any hangs in my testing and in practice this whole check only adds a 5% performance penalty, but I worry about someone compiling a truly massive file that interacts with this loop in ways I did not anticipate.
ui/src/asm_cleanup.rs
Outdated
@@ -73,7 +74,8 @@ pub fn filter_asm(block: &str) -> String { | |||
let mut current_label: &str = ""; | |||
let mut line_info: Vec<LineType> = Vec::new(); | |||
let mut labels: HashSet<&str> = HashSet::new(); | |||
let mut opcode_operands: HashSet<&str> = HashSet::new(); | |||
let mut opcode_operands: HashSet<&str> = HashSet::new(); | |||
let mut data_dir_label_refs: HashMap<&str, HashSet<&str>> = HashMap::new(); |
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.
FWIW, you don't have to change this in this PR, but I'll probably go though here with Clippy and remove a bunch of the explicit types on variable declarations — they aren't very idiomatic.
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.
Removed.
ui/src/asm_cleanup.rs
Outdated
@@ -73,7 +74,8 @@ pub fn filter_asm(block: &str) -> String { | |||
let mut current_label: &str = ""; | |||
let mut line_info: Vec<LineType> = Vec::new(); | |||
let mut labels: HashSet<&str> = HashSet::new(); | |||
let mut opcode_operands: HashSet<&str> = HashSet::new(); |
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.
Why is this line changing? Is there extra whitespace?
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.
Yup, must have added an extra space. Removing.
ui/src/asm_cleanup.rs
Outdated
@@ -4,6 +4,7 @@ | |||
use regex::Regex; | |||
use regex::Captures; | |||
use rustc_demangle::demangle; | |||
use std::collections::HashMap; |
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.
FWIW, you don't have to change this in this PR, but I'll probably rewrite these as std::collections::{HashMap, HashSet}
at a later point;
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'll condense the collections and regex imports.
2947900
to
7ae82da
Compare
ui/src/asm_cleanup.rs
Outdated
if used_labels.contains(label) && checked_labels.insert(label) { | ||
new_used_label_found = true; | ||
|
||
for referenced_label in references.iter() { |
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.
Maybe just used_labels.extend(&references)
?
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, that's much better. I was bothered by the nested for loops, but wasn't coming up with anything more expressive.
I decided to just move references
into used_labels
instead of passing a reference, as it's not used again in the HashMap.
7ae82da
to
2e8afca
Compare
ui/src/asm_cleanup.rs
Outdated
break; | ||
} | ||
} | ||
} |
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 afraid I've busted your logic here... or at least I don't understand it anymore.
On the first iteration of the for
loop, new_used_label_found
is false. So long as we find the current label
, the loop will continue, but as soon as we hit the first one, the loop stops. I think it's equivalent to this:
for _ in 0..MAX_ITERATIONS {
for (label, references) in &data_dir_label_refs {
if used_labels.contains(label) && checked_labels.insert(label) {
new_used_label_found = true;
used_labels.extend(references);
} else {
break;
}
}
}
This seems a little strange that we'd exit the inner loop like that; maybe the if !new_used_label_found
should be outside the for
?
Is this algorithm essentially trying to find the reachable nodes of a graph starting from a set of nodes? If so, you might want to look at petgraph::GraphMap
instead of rebuilding a graph by hand. It may even have an algorithm for finding the reachable nodes...
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.
Sloppy mistake, my apologies.
Yes, your summary is correct. I'll look into petgraph, it should be an interesting to try something new.
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.
That was much less painful than I was expecting, thanks for the suggestion!
5c2c297
to
1cbd19d
Compare
ui/src/asm_cleanup.rs
Outdated
used_labels.iter() | ||
.filter(|label| label_graph.contains_node(label)) | ||
.flat_map(|n| label_graph.neighbors_directed(n, Outgoing)) | ||
.for_each(|node| data_labels.push(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.
Very succinct! Could this now be
let data_labels: Vec<_> = used_labels.iter()
.filter(|label| label_graph.contains_node(label))
.flat_map(|n| label_graph.neighbors_directed(n, Outgoing))
.collect();
I noticed the loop is gone; do we no longer need to deeply trace the graph?
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.
Gah, you're right! I apologize, let me take another pass at 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.
Thanks for putting up with all my pedantic quibbles ;-)
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 guessing the loop can be broken as:
if data_labels.is_empty() {
break;
}
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.
Actually, it's probably if used_labels
doesn't change size when the data_labels
are added...
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 consider "not working correctly" to be a pedantic quibble 😄
It should be checking the full graph now, and I added a test, which I should have done earlier.
1cbd19d
to
0d75432
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.
That's some nice crate usage!
Thanks! As always, thank you for all your help and patience. |
The present assembly filter is a bit overeager with label references in data directives. This results in labels being included in filtered results that are not referenced by an opcode, directly or indirectly.
This change adds a check to ensure that label references from data directive are only treated as used if the label the data directive is part of is used by an opcode, or referenced by a data directive that is itself used by an opcode.
This should also be the final polish for the current filtering logic, at least until I notice another bug 😄