This adds more robust support for a number of edge cases that occur when attempting to resolve control flow synchronization between programs. The major changes are:
1) Generalizes the work queue to contain a set of work items, which may contain directives to merge or split the analysis. This simplifies the needed processing when a control flow split/merge is encountered, since the relevant actions are simply queued rather than handled immediately. When a sync/cut address is encountered, all possible merge combinations are added to the queue and then handled one by one through normal queue processing.
2) Generalizes synchronization points to either be at the start of a node, or at a subset of the exits to the node. This preserves the functionality where the semantics of the final transition from the single-sided to two-sided analysis are used when generating the initial equivalence domain, but also supports cases where that can't be represented. This can happen for two reasons:
a) One of the sides of the analysis takes zero steps between the desync and sync points.
b) One (or both) sides of the analysis return directly to the synchronization point following a function call.
When queuing a merge operation in either of these cases, the already-synchronized exit point (each starting at exactly one of the provided sync addresses) for both programs is considered to be the first node in the merged two-sided analysis.
This adds more robust support for a number of edge cases that occur when attempting to resolve control flow synchronization between programs. The major changes are:
1) Generalizes the work queue to contain a set of work items, which may contain directives to merge or split the analysis. This simplifies the needed processing when a control flow split/merge is encountered, since the relevant actions are simply queued rather than handled immediately. When a sync/cut address is encountered, all possible merge combinations are added to the queue and then handled one by one through normal queue processing.
2) Generalizes synchronization points to either be at the start of a node, or at a subset of the exits to the node. This preserves the functionality where the semantics of the final transition from the single-sided to two-sided analysis are used when generating the initial equivalence domain, but also supports cases where that can't be represented. This can happen for two reasons: a) One of the sides of the analysis takes zero steps between the desync and sync points. b) One (or both) sides of the analysis return directly to the synchronization point following a function call. When queuing a merge operation in either of these cases, the already-synchronized exit point (each starting at exactly one of the provided sync addresses) for both programs is considered to be the first node in the merged two-sided analysis.