From 77e8243cb0fc3bc78a6454c08ca64cb5587ce6e3 Mon Sep 17 00:00:00 2001
From: Christian Dietrich <christian.dietrich@informatik.uni-erlangen.de>
Date: Wed, 7 Sep 2016 17:14:16 +0200
Subject: [PATCH] pml: cumulative commit

I should have committed earlier. But I didn't; I feel ashamed and i
admit, that it was a bad idea in the first place. So, my dear reader,
what did happen here:

- We detect loops on the State level and put that information into the
  PML. For this, an dependency to networkx was introduced, I should make
  this optional in the future.

- timing_start and timing_end can now take flags that influence the
  detection of OS-STG regions. Especially the TIMING_STOP_BEFORE flag is
  important for exit nodes. It has the semantic, that this ABB is NOT
  part of the circuit, but all states that would dispatch here are part
  of the circuit.
---
 generator/transform/GeneratePML.py | 257 +++++++++++++++++++----------
 1 file changed, 174 insertions(+), 83 deletions(-)

diff --git a/generator/transform/GeneratePML.py b/generator/transform/GeneratePML.py
index ecb5dfd..1661d71 100644
--- a/generator/transform/GeneratePML.py
+++ b/generator/transform/GeneratePML.py
@@ -1,12 +1,14 @@
 from generator.analysis.Analysis import Analysis, PassInformation
 from generator.analysis import SavedStateTransition, Function
 from generator.analysis.AtomicBasicBlock import E, S
+from generator.analysis.common import *
 from collections import defaultdict
 import os
 import logging
 import yaml
+import networkx
 
-class GeneratePML(Analysis):
+class GeneratePML(Analysis, GraphObject):
     """ FIXME
 
     """
@@ -16,54 +18,92 @@ class GeneratePML(Analysis):
         used_cfg_edges = {E.function_level, E.timing_edge_0, E.timing_edge_1}
     )
 
-    def get_circuit_states(self, start, end):
-        start, end = set(start), set(end)
-        # 0. Find all CFG successors
-        stop = set()
-        for abb in end:
-            stop.update(abb.get_outgoing_nodes(E.function_level))
-        # 1. Find all states that will execute one ABB from the end set
+    def __init__(self):
+        Analysis.__init__(self)
+        GraphObject.__init__(self, "GeneratePML", root = True)
+
+    def graph_subobjects(self):
+        subobjects = []
+        for circuit in self.circuits:
+            if len(circuit['all_states']) > 500:
+                continue
+
+            mapping = {}
+            for state in circuit['all_states'].values():
+                color = 'green'
+                if id(state) in circuit['end_states']:
+                    color = 'red'
+                if id(state) in circuit['start_states']:
+                    color='blue'
+                label="N%d:%s/%s" %(circuit['state_to_idx'][id(state)],
+                                    state.current_subtask,
+                                    state.current_abb)
+                node = GraphObjectContainer(label, color=color)
+                mapping[id(state)] = node
+                subobjects.append(node)
+
+            for a in circuit['all_states'].values():
+                for b in a.get_outgoing_nodes(SavedStateTransition):
+                    if id(b) in circuit['all_states']:
+                        mapping[id(a)].edges.append(
+                            Edge(mapping[id(a)], mapping[id(b)]))
+
+        return subobjects
+
+
+    def get_circuit_states(self, start_abbs, end_abbs):
+        # 1. Find all starting states
         sse = self.system_graph.get_pass("sse")
-        start_states, end_states, stop_states = [], [], []
+        start_states, end_states = {}, {}
         for state in sse.states:
-            if state.current_abb in start:
-                start_states.append(state)
-            if state.current_abb in end:
-                end_states.append(state)
-        start_state_ids = {id(x) for x in start_states}
-        end_state_ids = {id(x) for x in end_states}
-        # stop_state_ids = {id(x) for x in stop_states}
+            if state.current_abb in start_abbs:
+                start_states[id(state)] = state
 
         # 2. DFS starting from the first element in the graph.
         # 2.1 All end states are definitly part of the graph
         good_states = { }
+        def add_to_good_states(path, curr):
+            for x in path + [curr]:
+                good_states[id(x)] = x
         def dfs(path, curr):
             # Uncomment for debugging purposes
             # print("V", [(x.current_subtask, x.current_abb) for x in path + [curr]])
 
-            if id(curr) in end_state_ids or id(curr) in good_states or\
-               id(curr) in [id(x) for x in path]:
+            state_loop = id(curr) in [id(x) for x in path]
+            hit_on_good_state = id(curr) in good_states
+            is_end_state = curr.current_abb in end_abbs
+
+            if state_loop or hit_on_good_state or is_end_state:
                 # print("A")
                 # Add the path to the good states
-                for x in path + [curr]:
-                    good_states[id(x)] = x
+                add_to_good_states(path, curr)
+
+            assert not is_end_state or (end_abbs[curr.current_abb] & 1) == 0, \
+                "TIMING_POINT_STOP_BEFORE should not be directly visited"
+
+            if is_end_state:
+                end_states[id(curr)] = curr
 
             # Stop DFS, if we were here already
             if id(curr) in visited:
                 return
 
-
             visited.add(id(curr))
             path = path + [curr]
             for succ in curr.get_outgoing_nodes(SavedStateTransition):
-                # Do not proceed locally on state graph within subtask
-                if id(curr) in end_state_ids:
-                    if succ.current_subtask == curr.current_subtask:
+                # Edges that origin from an end state to the same
+                # subtask are not taken.
+                if is_end_state and succ.current_subtask == curr.current_subtask:
                         continue
+                if succ.current_abb in end_abbs and (end_abbs[succ.current_abb] & 1) == 1:
+                    # TIMING_POINT_STOP_BEFORE
+                    end_states[id(curr)] = curr
+                    add_to_good_states(path, curr)
+                    continue
                 dfs(path, succ)
 
         visited = set()
-        for state in start_states:
+        for state in start_states.values():
             dfs([], state)
 
         # for state in good_states.values():
@@ -73,11 +113,11 @@ class GeneratePML(Analysis):
                 logging.warning("Idle state part of circuit... unanalyzable")
 
         # Every State can only occur only once in the result
-        return good_states.values()
+        return start_states, good_states, end_states
 
-    def find_irq_regions(self, circuit):
+    def find_irq_regions(self, all_states):
         """ Find all IRQ regions in a circuit. A IRQ region has one ISR kickoff, and N ISR exits"""
-        for state in circuit['states']:
+        for state in all_states.values():
             if state.current_subtask and state.current_subtask.conf.is_isr \
                and state.current_abb.isA(S.kickoff):
                 assert len(state.get_incoming_nodes(SavedStateTransition)) == 1,\
@@ -86,7 +126,7 @@ class GeneratePML(Analysis):
                 stack = [state]
                 visited = set()
                 entry = state
-                exits = set()
+                exits = {}
                 states = []
                 while stack:
                     p = stack.pop()
@@ -97,18 +137,22 @@ class GeneratePML(Analysis):
                     if p.current_abb.isA(S.iret):
                         assert len(p.get_outgoing_nodes(SavedStateTransition)) == 1,\
                             "Unexpected IRET state"
-                        exits.add(p)
+                        exits[id(p)] = p
                     else:
-                        stack.extend(p.get_outgoing_nodes(SavedStateTransition))
-                yield {'entry': entry, 'states': states, 'exits': exits}
-
-    def add_cfg_edges(self, circuit):
+                        for n in p.get_outgoing_nodes(SavedStateTransition):
+                            if id(n) in all_states:
+                                stack.append(n)
+                            else:
+                                exits[id(p)] = p
+                yield {'entry': entry, 'states': states, 'exits': exits.values()}
+
+    def add_cfg_edges(self, circuit_number, all_states):
         """Draw Edges (only for visualization) between the connected ABBs"""
         edges = set([])
-        edge_type = [E.timing_edge_0, E.timing_edge_1][circuit["circuit"] % 2]
-        for a in circuit["states"]:
+        edge_type = [E.timing_edge_0, E.timing_edge_1][circuit_number % 2]
+        for a in all_states.values():
             for b in a.get_outgoing_nodes(SavedStateTransition):
-                if id(b) not in circuit["state-ids"]:
+                if id(b) not in all_states:
                     continue
                 if (a.current_abb, b.current_abb) in edges:
                     continue
@@ -121,9 +165,11 @@ class GeneratePML(Analysis):
         self.flow_frequency_variable_counter += 1
         return "flow_var_" + hint + "_" + str(tmp)
 
-    def add_circuit_to_pml(self, pml, circuit):
+    def add_circuit_to_pml(self, pml, circuit_number,
+                           start_states, all_states, end_states,
+                           loops):
         """Adds the global-cfg field to the PML"""
-        gcfg = {'name': 'timing-%d' % circuit['circuit'],
+        gcfg = {'name': 'timing-%d' % circuit_number,
                 'level': 'bitcode',
                 'entry-nodes': [],
                 'exit-nodes': [],
@@ -136,23 +182,34 @@ class GeneratePML(Analysis):
             gcfg['blocks'].append(data)
             return i, data
         def delete_block(idx):
+            assert False
             gcfg['blocks'][idx] = None
 
         # First we generate the artificial blocks
         isr_entry_abb_idx, _ = add_block({'function': 'irq_entry', 'name':"isr_entry_block"})
 
-        # Assign an idx to every state and abb
+        # Assign an idx to every state and abb. States are monotonly
+        # increasing numbers that refence objects in the PML
         abb_to_idx = {}
         state_to_idx = {}
-        def idx(state):
-            if id(state) in state_to_idx:
-                return state_to_idx[id(state)]
-            return abb_to_idx[state]
+        def idx(obj):
+            if id(obj) in state_to_idx:
+                return state_to_idx[id(obj)]
+            return abb_to_idx[obj]
 
         timer_function_name = '_ZN2os7Counter4tickEv'
 
-        for state in circuit['states']:
+        # Some states are part of a toplevel loop
+        state_to_loops = defaultdict(lambda: list())
+        for loop, states in loops.items():
+            for state in states:
+                state_to_loops[id(state)].append(loop)
+
+        for state in all_states.values():
+            # We first assign an id to the state
             state_to_idx[id(state)] = len(state_to_idx)
+
+            # We have to add an ABB object to the PML, for every ABB we newly ancounter
             abb = state.current_abb
             if abb not in abb_to_idx:
                 # ISR entry and Exit Nodes are filled separatly
@@ -184,25 +241,30 @@ class GeneratePML(Analysis):
                         record['exit-block'] = 'idle_loop_again'
 
 
-            # Some states are entry states, some states are know to be
-            # exit nodes
-            if abb in circuit['start']:
-                gcfg['entry-nodes'].append(idx(state))
-            if abb in circuit['end']:
-                gcfg['exit-nodes'].append(idx(state))
+
+        # After having an index for every state, we can mark states as
+        # entry or exit nodes.
+        for state in start_states.values():
+            gcfg['entry-nodes'].append(idx(state))
+        for state in end_states.values():
+            gcfg['exit-nodes'].append(idx(state))
 
 
         # Add all state records to the gcfg
-        for state in circuit['states']:
+        for state in all_states.values():
             successors = [x for x in state.get_outgoing_nodes(SavedStateTransition)
-                          if id(x) in circuit['state-ids']]
+                          if id(x) in all_states]
             local_successors = [idx(x) for x in successors if x.current_subtask == state.current_subtask]
             global_successors = [idx(x) for x in successors if x.current_subtask != state.current_subtask]
 
-            successors = [idx(x) for x in successors if id(x) in circuit['state-ids']]
+            successors = [idx(x) for x in successors if id(x) in all_states]
             data = {'index': idx(state),
                     'local-successors': local_successors,
                     'global-successors': global_successors}
+            # Annotate that states are part of a loop
+            if id(state) in state_to_loops:
+                data['loops'] = state_to_loops[id(state)]
+
             if state.current_abb in abb_to_idx:
                 data['abb'] = abb_to_idx[state.current_abb]
                 data['abb-name'] = str(state.current_abb)
@@ -215,17 +277,12 @@ class GeneratePML(Analysis):
             gcfg['nodes'].append(data)
 
         irq_entry_variables = defaultdict(list)
-        #print (circuit['states'])
 
         # Transform IRQ Regions
         ast_requests = set()
-        for region in self.find_irq_regions(circuit):
+        for region in self.find_irq_regions(all_states):
             isr_entry = region['entry']
             isr_entry_data = gcfg['nodes'][idx(isr_entry)]
-            # Sometimes an IRQ entry can be
-            if region['entry'].current_abb in circuit['start']:
-                gcfg['entry-nodes'].append(idx(isr_entry))
-
 
             # Give the isr_entry block a flow variable. This flow
             # variable, will capture how often an interrupt is
@@ -240,7 +297,6 @@ class GeneratePML(Analysis):
             isr_entry_data['abb'] = isr_entry_abb_idx
             isr_entry_data['abb-name'] = 'isr_entry'
 
-
             for isr_exit in region['exits']:
                 if id(isr_exit) not in state_to_idx:
                     continue
@@ -253,8 +309,7 @@ class GeneratePML(Analysis):
             # flow. This is necessary, since they are embedded into
             # the isr_entry state
             for state in region['states']:
-                if id(state) not in state_to_idx:
-                    continue
+                assert id(state) in state_to_idx
                 state_data = gcfg['nodes'][idx(state)]
                 state_data['abb-name'] = str(state.current_abb)
                 if state != isr_entry:
@@ -331,9 +386,28 @@ class GeneratePML(Analysis):
             'lhs': lhs
 ,        })
 
-        logging.info("     %s: %d states", gcfg['name'], len(gcfg['nodes']))
+        logging.info("     %s: %d states, %d loops", gcfg['name'],
+                     len(gcfg['nodes']),
+                     len(loops))
+        return state_to_idx
 
+    def find_loops(self, all_states):
+        # Create Directed Graph
+        G = networkx.DiGraph()
+
+        # Add a list of nodes:
+        G.add_nodes_from(all_states.keys())
+
+        for a in all_states.values():
+            for b in a.get_outgoing_nodes(SavedStateTransition):
+                if id(b) in all_states:
+                    G.add_edge(id(a), id(b))
 
+        loops = {}
+        for loop in networkx.simple_cycles(G):
+            loop_id = len(loops)
+            loops[loop_id] = [all_states[state_id] for state_id in loop]
+        return loops
 
     def do(self):
         self.flow_frequency_variable_counter = 0
@@ -348,37 +422,54 @@ class GeneratePML(Analysis):
 
         # We search for all circuits, the user has annotated with
         # function calls in his application
-        circuits = defaultdict(lambda : {'start': [], 'end': []})
+        circuits = defaultdict(lambda : {'start': {}, 'end': {}})
         for abb in self.system_graph.abbs:
-            for (bb, func, circuit) in abb.call_sites:
+            for (bb, func, args) in abb.call_sites:
+                if func in ('timing_start', 'timing_end'):
+                    circuit = args[0] & 0xff
+                    options = args[0] >> 8
                 if func == "timing_start":
                     if abb.subtask.conf.is_isr:
-                        logging.info("  timing-%d starts with IRQ activation", circuit[0])
+                        logging.info("  timing-%d starts with IRQ activation", circuit)
                         before = abb.definite_before(E.function_level)
                         assert before.isA(S.kickoff), "Can start analysis only at beginning of ISR"
-                        circuits[circuit[0]]["start"].append(before)
+                        circuits[circuit]["start"][before] = options
                     else:
-                        circuits[circuit[0]]["start"].append(abb)
+                        circuits[circuit]["start"][abb] = options
                 if func == "timing_end":
-                    circuits[circuit[0]]["end"].append(abb)
+                    circuits[circuit]["end"][abb] = options
         # Finds all states in between
-        for circuit, data in circuits.items():
-            states = self.get_circuit_states(data["start"], data["end"])
-
-            data['states'] = states
-            data['circuit'] = circuit # Copy ID into dict
-            data['state-ids'] = {id(x) for x in data['states']}
-            self.add_cfg_edges(data)
-
-
-        self.circuits = circuits.values()
-
         pml = {'format': 'pml-0.1',
                'triple': 'patmos-unknown-unknown-elf',
                'global-cfgs': []
         }
-        for circuit in self.circuits:
-            self.add_circuit_to_pml(pml, circuit)
+
+        self.circuits = []
+        for circuit_number, data in circuits.items():
+            start_states, all_states, end_states = self.get_circuit_states(data["start"], data["end"])
+
+            #print([x.current_abb for x in start_states.values()])
+            #print([x.current_abb for x in all_states.values()])
+            #print([x.current_abb for x in end_states.values()])
+            assert all([id_ in all_states for id_ in start_states]), \
+                "All start states must be part of the all_state set"
+            assert all([id_ in all_states for id_ in end_states]), \
+                "All end states must be part of the all_state set"
+
+            # Copy circuit into graph (combined ABB->ABB edges)
+            self.add_cfg_edges(circuit_number, all_states)
+
+            # Add Circuit to PML
+            loops = self.find_loops(all_states)
+            state_to_idx = self.add_circuit_to_pml(pml, circuit_number,
+                                                   start_states, all_states, end_states,
+                                                   loops)
+
+            self.circuits.append({"index": circuit_number,
+                                  'state_to_idx': state_to_idx,
+                                  'start_states': start_states,
+                                  'all_states': all_states,
+                                  'end_states': end_states})
 
 
         fn = os.path.join(os.path.dirname(self.system_graph.basefilename),
-- 
GitLab