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