-
Notifications
You must be signed in to change notification settings - Fork 809
Handle arbitrary copies in CFP #7900
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
base: struct-utils-precise-copying
Are you sure you want to change the base?
Conversation
We previously special-cased copies from a field to itself in CFP. Generalize the analysis to handle copies from any field in any type to any field in any type. Along with this change, make the entire analysis more precise by explicit analyzing the written values, analyzing the readable values that result from the written values, then propagating readable values back to the writable values via copies and iterating until a fixed point. Use custom propagation logic after applying the copies the first time to minimize the amount of work done when propagating.
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.
(some initial comments)
@@ -85,6 +86,42 @@ struct PossibleConstantValues { | |||
// identify a constant value here. | |||
void noteUnknown() { value = Many(); } | |||
|
|||
void packForField(const Field& field, bool isSigned = 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.
Please add a comment for this method. On the face of it it seems quite different than the others above it. Is it called when written to a packed field, or read from one?
} | ||
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.
Was this large chunk of code entirely unneeded? I'm not quite seeing the connection to this PR.
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.
Yeah, this was just dead code I removed as a drive-by. I can move it to a separate PR if you prefer.
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's ok as is, I was just confused.
@@ -66,6 +70,40 @@ namespace wasm { | |||
|
|||
namespace { | |||
|
|||
struct CopyInfo { |
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.
Please add a comment on this struct. Copying is not obviously a key part of the optimization so some context could help.
@@ -436,17 +493,11 @@ struct PCVScanner | |||
Type type, | |||
Index index, |
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 type and index could be prefixed dst
to clarify they are the destination.
for (auto& func : module->functions) { | ||
functionCopyInfos[func.get()]; | ||
} |
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.
for (auto& func : module->functions) { | |
functionCopyInfos[func.get()]; | |
} |
FunctionStructValuesMap
does these three lines in the constructor.
} | ||
if (dst.exact == Inexact) { | ||
// Propagate down to subtypes. | ||
written[{dst.type, Exact}][dst.index].combine(val); |
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 does this combine()
not cause later propagation? In general I'd expect any such operation to open up new possible work. And specifically here, if we just found a new written value, then any copies from it should be propagated?
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.
Copies are from readable values to written values, so we only check for further copies to propagate when we update the readable values. Of course we do have to be careful to update the readable values accordingly whenever we update the writable values, too, since the readable values are a superset of the writable values.
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 see, thanks, that makes sense - I missed that. Perhaps a comment?
(local $super (ref null $super)) | ||
(local $struct (ref null $struct)) | ||
(local $sub (ref null $sub)) | ||
(drop |
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.
Please add a comment here. I think it can say "we never wrote to $super, so the only things we can read are what was written to $struct (or $sub, if the reference in $copy was to a subtype)"
test/lit/passes/cfp-copies.wast
Outdated
;; CHECK-NEXT: ) | ||
;; CHECK-NEXT: ) | ||
(func $init | ||
;; Same as above, but now with a different value in $sub1. |
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 previous testcase didn't have $sub1
? Or does this mean "added $sub1, with a different vaue in it" - ?
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.
$sub from the previous case became $sub1 and got a new value, and we also added $sub2. Will clarify the comment.
(local $struct (ref null $struct)) | ||
(local $sub1 (ref null $sub1)) | ||
(local $sub2 (ref null $sub2)) | ||
(drop |
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.
A comment could mention that the copy might write to $sub1
, so we can't optimize it; but $sub2
can only have the parent's value.
;; CHECK-NEXT: ) | ||
;; CHECK-NEXT: ) | ||
(func $init | ||
;; Same as above. |
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.
Previous test didn't have sub1
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 can globally rename sub1
to sub
if that would help clarify things, but then we would lose the symmetry between the sub1
and sub2
names.
I'm halfway through the big testcase, but I wonder: is this maybe something we could test better programmatically? A small unittest/gtest could generate all combinations of "create the super,struct,sub1,sub2,other types, init with {options} and add a {copy}, add reads and exact reads", then run them through That would cover correctness. Covering that we actually optimize is a little harder but maybe we can have a shorthand, something like |
There are so many interesting configurations to test that writing a script to generate them all might have almost been worth it. But even then it would probably make the most sense for such a script to generate lit test inputs so the tests could show the result of the optimization. The number of interesting test cases is unfortunately increased by the fact that the first copies and subsequent copies are propagated by different code paths. Only because of these two code paths do we need tests with multiple copies. An alternative approach would be to have a test-only version of the pass that does the copy propagation in the most naive possible way and asserts that the results are the same as doing it in the more optimal way implemented here. Then we could rely on running that pass in the fuzzer to be sure of the correctness of the optimal approach. |
HeapType type; | ||
Exactness exact; | ||
Index index; | ||
bool isSigned; |
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, can this struct be named WriteInfo? A copy would suggest a source and a destination, but this has just one side. And it is used in the queue in the sense of a write (we add work after adding a write iiuc)
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 that would be confusing because we only use this struct to record information for copies, not other writes.
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.
Fair enough.
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.
Ok, code lgtm, but I do still have half the big test left...
;; CHECK-NEXT: (local.get $super) | ||
;; CHECK-NEXT: ) | ||
;; CHECK-NEXT: ) | ||
;; CHECK-NEXT: (i32.const 10) |
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.
wait, can't some of these be 0 or 10 because of the copy?
We previously special-cased copies from a field to itself in CFP.
Generalize the analysis to handle copies from any field in any type to
any field in any type. Along with this change, make the entire analysis
more precise by explicit analyzing the written values, analyzing the
readable values that result from the written values, then propagating
readable values back to the writable values via copies and iterating
until a fixed point. Use custom propagation logic after applying the
copies the first time to minimize the amount of work done when
propagating.