Closed Mm7 closed 5 years ago
@Mm7 do you have a telegram? We have a dedicated radeco channel - would be awesome if you join it.
I'd rather prefer to keep my telegram personal, but if this is the only way to coordinate I'm fine with joining.
You can join #radeco IRC channel on Freenode if you like.
@chinmaydd have a look please too.
Hi @Mm7, this is great work! I have some comments which I would like to hear your thoughts on:
AnalyzerResult
trait should specify more requirements for an implementation. This could include mechanisms to verify/collect changes that were made during the analysis passes.run_all_analysis()
call. For example, it might be beneficial to run DCE after a high-level analysis pass (such as CSE) to take advantage of more optimizations. (I could be wrong here, in which case ignore it).Hi @Mm7, this is great work! I have some comments which I would like to hear your thoughts on:
* I feel that the `AnalyzerResult` trait should specify more requirements for an implementation. This could include mechanisms to verify/collect changes that were made during the analysis passes.
To do that, I think that the best approach is splitting the job in two different passes:
analyze
to get a list of Change
s that can be applied to the IR (which is left untouched). analyze
could also be made return some extra infos.Change
s are inspected and applied as taste (by the user).* Some analysis passes might inherently depend on the other for better/optimal results. I would like if you could incorporate a way to take such information into account, especially for the `run_all_analysis()` call. For example, it might be beneficial to run DCE after a high-level analysis pass (such as CSE) to take advantage of more optimizations. (I could be wrong here, in which case ignore it).
To do that you need analysis-specific knowledge (for example you need to know that DCE should be run after CSE). run_all_analysis
was a "dump" way to run all the analysis without knowing which ones are available and when they should be called. Probably we should remove that function.
In my opinion, the user (aka the one that calls the analysis) should be implemented somewhere else (like in a new module). Here we can take care of calling the more appropriate one, apply useful Changes
s and drop the useless ones.
Analyzer
should only provide a uniform interface to all the possible analyzer passes.
@Mm7 I agree on preserving also the original IR tree before the analysis pass. This way we can run two non-dependent analysis passes in parallel, maybe compare the results and apply only one or merge them into one.
I'm trying to implement a graph-preserving version of copy_propagation
. but I'm facing a problem: as soon as you start collecting changes to apply, you have no way to analyze the graph as it would look like after you applied all the previous changes, you can only see it in the original form!
For example, imagine that dce is called on the following IR. The changes
vector (aka the list of changes to return to the user) is empty.
%1 = 10
%2 = 20
%3 = 30
return %2
The analyzer will notice that %1
is unused, so a removeNode(%1)
is pushed to the changes
vec. Let's suppose that when executed removeNode
not only removes %1 but also renames %2 -> %1
, %3 -> %2
(in order to keep ids small). The analyzer is oblivious of this because changes are applied by the user after the execution of the analyze
func. If will then notice that %3
is unsued, so again a removeNode(%3)
is pushed.
When the user will try to apply the changes It will lead to wrong results (return of %3
instead of %2
, removal on non-existing %3
).
You may argue that my example is useless because removeNode
does not rename stuff, and you would probably be correct. But still, In case of complex changes (not just removing nodes), you could get some nasty side effects messing up the graph. Keeping track of all of this would be a nightmare, IMHO.
Here's few solutions I have in mind:
removeNode
breaks the property: nodeIdsNotTouched
, then the analyzer will not be allowed to run IR accessors that would change their result in case of a change in node ids. (rust's borrow checker could be exploited to enforce these rules)While 1 and 2 look saner it will be a pain to run it on any sufficiently large programs indeed. So looks like 3 is the only viable choice. @kriw @chinmaydd @radare what do you think?
@XVilka Maybe we could also add an indirection layer to access the graph: that layer should provide normal access while we are not inside an analysis, but during analysis it should collect all the changes applied to the graph; when an accessor is called it should dynamically patch the result according to the previously collected changes. This way change
s could still be applied during the analysis pass without touching the graph.
This idea should be simpler to implement than 2. because it is at a "lower" level (aka graph storage level rather than ssa accessor level).
The access mechanism would look like:
While 1 and 2 look saner it will be a pain to run it on any sufficiently large programs indeed. So looks like 3 is the only viable choice. @kriw @chinmaydd @radare what do you think?
I think the combination of 1 and 3 is also viable. We can reduce the number of clones with 3 and possibly a few clones are acceptable.
Sorry for my delay in response.
run_all_analysis was a "dump" way to run all the analysis without knowing which ones are available and when they should be called. Probably we should remove that function.
Agree. I do not see any consumer interested in calling run_all_analysis()
since it does not really provide any control over order and choice.
Maybe we could also add an indirection layer to access the graph
The design looks good, but I agree with @kriw. How much performance hit due to cloning are we talking here ? Dont think it should be too much.
@XVilka @chinmaydd @kriw I just uploaded a new version.
Action
enum to store "atomic" changes to the IR. For now only "replaceValue" is implemented. ASAP I'll add one variant for each one of the SSAMod
accessors.clone
way.@Mm7 ping?
Sorry for the delay. Currently I'm studying hard for my exams. But I'm still working on this
@Mm7 ah, ok then. Good luck with your exams!
@XVilka @kriw @chinmaydd After some local work on this patch it seems that things will get messy when dropping Change
s.
Suppose an analyzer produces the changes X, Y, Z. Due to the way they are created you must first apply X, then Y, then Z. You cannot skip Y and apply directly Z because the analyzer generated Z while analyzing the IR as it looks like after you apply Y.
For example, let's apply DCE on the following
%1 = 5
%2 = %1
%3 = %2
return 100
A possible output could be: changes=['remove %3', 'remove %2', 'remove %1']
. You can't apply changes[2]
without applying first changes[1,2]
(%2 and %1 would be referencing to a non-existing variable).
In general, I don't really see a solution unless some mechanism is added to sort of "cherry-pick" (Let me borrow this term from the git world :smile: ) a change and apply it to a different IR. But honestly I don't think this is a good idea because 1) that mechanism should be analyzer specific 2) debugging won't be easy in case of errors during the cherry-pick procedure 3) implementation will be probably complex.
If you have some better ideas let me know: I would be happy to implement it.
On the other hand we could drop this analyze-without-touching thing and simply let the analyzers do their job. I agree that collecting/applying changes on the consumer side is nice, but IMHO it is not worth it.
@Mm7 well, if it is too hard to implement, then it is probably a wrong solution. Lets proceed then with mutable transformations then.
@kriw @chinmaydd please take a look again to the proposed API.
I think this is a good starting point for a standard api. LGTM :)
Yep, seems good @Mm7 :-)
OK, thanks for you feedback. Next week my exams will be over so I'll have more time to finish this.
@XVilka @chinmaydd @kriw IMHO this is a good point to push this PR. Please can you take a look? Thank you. Note: https://github.com/radareorg/radeco/pull/66 updates radeco to use this api
Looks good, with this API code looks much clearer, good job!
LGTM :)
Initial attempt to solve #172. If you like this proposal I'll add support for more analysis. Suggetions and comments are welcome! Thank you.