From 6922872115009244febb7d49aafabf4b0898548e Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Fri, 18 Sep 2020 17:16:12 -0400
Subject: [PATCH 35/49] FIXME: add contrib/analyzer-compare-enodes.py

---
 contrib/analyzer-compare-enodes.py | 181 +++++++++++++++++++++++++++++
 1 file changed, 181 insertions(+)
 create mode 100755 contrib/analyzer-compare-enodes.py

diff --git a/contrib/analyzer-compare-enodes.py b/contrib/analyzer-compare-enodes.py
new file mode 100755
index 00000000000..2425514c4df
--- /dev/null
+++ b/contrib/analyzer-compare-enodes.py
@@ -0,0 +1,181 @@
+#!/usr/bin/env python3
+#
+# Script to analyze the output of -fdump-analyzer-json
+#
+# Copyright (C) 2020 Free Software Foundation, Inc.
+# Contributed by David Malcolm <dmalcolm@redhat.com>
+#
+# This file is part of GCC.
+#
+# GCC is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 3, or (at your option) any later
+# version.
+#
+# GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GCC; see the file COPYING3.  If not see
+# <http://www.gnu.org/licenses/>.  */
+
+import argparse
+import gzip
+import json
+import pprint
+import re
+import sys
+
+def parse_enode_indices(s):
+    """
+    Parse the output of print_enode_indices
+    For example, given "EN: 4-7, EN: 20-23, EN: 42",
+    return [4, 6, 7, 20, 21, 22, 23, 42]
+    """
+    result = []
+    for run in s.split(', '):
+        m = re.match('EN: ([0-9]+)-([0-9]+)', run)
+        if m:
+            result += range(int(m.group(1)), int(m.group(2)) + 1)
+            continue
+        m = re.match('EN: ([0-9]+)', run)
+        if m:
+            result.append(int(m.group(1)))
+            continue
+        raise ValueError('unmatched: %r' % run)
+    return result
+
+# FIXME: convert to unit tests
+assert(parse_enode_indices("EN: 152, EN: 159, EN: 165, EN: 359,"
+                           " EN: 546, EN: 769, EN: 776, EN: 782, EN: 966")
+       == [152, 159, 165, 359, 546, 769, 776, 782, 966])
+assert(parse_enode_indices("EN: 4-7, EN: 20-23, EN: 42")
+       == [4, 5, 6, 7, 20, 21, 22, 23, 42])
+
+class AnalyzerDump:
+    @staticmethod
+    def from_json(js):
+        return AnalyzerDump(js)
+
+    def __init__(self, js):
+        self.js = js
+
+    def get_enode(self, idx):
+        egraph_js = self.js['egraph']
+        nodes = egraph_js['nodes']
+        return nodes[idx]
+        #return Exploded_node.from_js(nodes[idx])
+
+def summarize(vals, enode_idxs):
+    """
+    Given a list of values VALS, and a list of enode_idxs of the
+    same length, stringify the values in VALS (to support hashing),
+    uniqify them, and build a dict mapping from unique
+    values to the list of enode_idxs which have that value.
+    """
+    uniq_vals = set(str(js_obj) for js_obj in vals)
+    result = {}
+    for uniq_val in uniq_vals:
+        enode_idxs_with_val = []
+        for val, enode_idx in zip(vals, enode_idxs):
+            if str(val) == uniq_val:
+                enode_idxs_with_val.append(enode_idx)
+        result[str(uniq_val)] = enode_idxs_with_val
+        # TODO: if it's all of them, maybe return None
+        # or maybe make this an option?
+        if True:
+            if enode_idxs_with_val == enode_idxs:
+                return None
+    return result
+
+def compare_enodes(js, enode_idxs):
+    """
+    Build a comparison dict, summarizing commonality and variability of
+    the various states of the given enodes in the dump JS.
+    """
+    comparison = {}
+
+    d = AnalyzerDump.from_json (js)
+    egraph = js['egraph']
+    init_enode = d.get_enode (enode_idxs[0])
+    enodes = [d.get_enode (enode_idx) for enode_idx in enode_idxs]
+    states = [enode['state'] for enode in enodes]
+
+    enodes = [d.get_enode (enode_idx) for enode_idx in enode_idxs]
+    points = [enode['point'] for enode in enodes]
+    comparison['point'] = summarize(points, enode_idxs)
+
+    comparison['state'] = {}
+
+    checkers = [state['checkers'] for state in states]
+    comparison['state']['checkers'] = summarize(checkers, enode_idxs)
+
+    constraints = [state['constraints'] for state in states]
+    comparison['state']['constraints'] = summarize(constraints, enode_idxs)
+
+    curr_frame = [state['curr_frame'] for state in states]
+    comparison['state']['curr_frame'] = summarize(curr_frame, enode_idxs)
+
+    stores = [state['store'] for state in states]
+
+    parent_regs = set()
+    for state in states:
+        for parent_reg in state['store'].keys():
+            parent_regs.add(parent_reg)
+    #print(parent_regs)
+
+    comparison['state']['store'] = {}
+    for parent_reg in parent_regs:
+        if parent_reg == 'called_unknown_fn':
+            comparison['state']['store'][parent_reg] \
+                = summarize([store.get(parent_reg) for store in stores],
+                            enode_idxs)
+            continue
+        base_regs = set()
+        for state in states:
+            #print(state['store'][parent_reg])
+            for base_reg in state['store'][parent_reg].keys():
+                base_regs.add(base_reg)
+        #print(base_regs)
+        comparison['state']['store'][parent_reg] = {}
+        for base_reg in base_regs:
+            comparison['state']['store'][parent_reg][base_reg] \
+                = summarize([store.get(parent_reg).get(base_reg)
+                             for store in stores],
+                            enode_idxs)
+    return comparison
+
+def main():
+    parser = argparse.ArgumentParser(description='Compare commonality and variability in a -fdump-analyzer-json dump.')
+    parser.add_argument('filename', metavar='FILENAME', type=str,
+                        help='the SRCFILE.analyzer.json.gz file to load')
+    parser.add_argument('enodes', metavar='ENODES', type=str,
+                        help='FIXME')
+    args = parser.parse_args()
+
+    with gzip.open(args.filename) as f:
+        js = json.load(f)
+
+    enode_idxs = parse_enode_indices(args.enodes)
+    print(repr(enode_idxs))
+
+    comparison = compare_enodes(js, enode_idxs)
+
+    #pprint.pprint(comparison)
+    json.dump(comparison, sys.stdout, indent=True)
+
+    if 0:
+        for enode_idx, enode in zip(enode_idxs, enodes):
+            state = enode['state']
+            #print('state for EN: %i' % enode_idx)
+            #pprint.pprint(state)
+            #compare(e'checkers')
+            # TODO: checkers
+            # TODO: constraints
+            # TODO: curr_frame
+            # TODO: store
+
+if __name__ == '__main__':
+    main()
-- 
2.26.2

