def _datadesc(obj: Any): from dace import data if isinstance(obj, data.Data): return obj elif symbolic.issymbolic(obj): return data.Scalar(symbolic.symtype(obj)) elif isinstance(obj, dtypes.typeclass): return data.Scalar(obj) return data.Scalar(dtypes.typeclass(type(obj)))
def _create_datadescriptor(obj): """ Creates a data descriptor from various types of objects. @see: dace.data.Data """ if isinstance(obj, data.Data): return obj try: return obj.descriptor except AttributeError: if isinstance(obj, numpy.ndarray): return data.Array(dtype=types.typeclass(obj.dtype.type), shape=obj.shape) if symbolic.issymbolic(obj): return data.Scalar(symbolic.symtype(obj)) if isinstance(obj, types.typeclass): return data.Scalar(obj) return data.Scalar(types.typeclass(type(obj)))
def generate_code( self, sdfg: SDFG, schedule: Optional[dtypes.ScheduleType], sdfg_id: str = "" ) -> Tuple[str, str, Set[TargetCodeGenerator], Set[str]]: """ Generate frame code for a given SDFG, calling registered targets' code generation callbacks for them to generate their own code. :param sdfg: The SDFG to generate code for. :param schedule: The schedule the SDFG is currently located, or None if the SDFG is top-level. :param sdfg_id: An optional string id given to the SDFG label :return: A tuple of the generated global frame code, local frame code, and a set of targets that have been used in the generation of this SDFG. """ if len(sdfg_id) == 0 and sdfg.sdfg_id != 0: sdfg_id = '_%d' % sdfg.sdfg_id global_stream = CodeIOStream() callsite_stream = CodeIOStream() is_top_level = sdfg.parent is None # Analyze allocation lifetime of SDFG and all nested SDFGs if is_top_level: self.determine_allocation_lifetime(sdfg) # Generate code ########################### # Keep track of allocated variables allocated = set() # Add symbol mappings to allocated variables if sdfg.parent_nsdfg_node is not None: allocated |= sdfg.parent_nsdfg_node.symbol_mapping.keys() # Invoke all instrumentation providers for instr in self._dispatcher.instrumentation.values(): if instr is not None: instr.on_sdfg_begin(sdfg, callsite_stream, global_stream) # Allocate outer-level transients self.allocate_arrays_in_scope(sdfg, sdfg, global_stream, callsite_stream) # Allocate inter-state variables global_symbols = copy.deepcopy(sdfg.symbols) global_symbols.update( {aname: arr.dtype for aname, arr in sdfg.arrays.items()}) interstate_symbols = {} for e in sdfg.edges(): symbols = e.data.new_symbols(sdfg, global_symbols) # Inferred symbols only take precedence if global symbol not defined symbols = { k: v if k not in global_symbols else global_symbols[k] for k, v in symbols.items() } interstate_symbols.update(symbols) global_symbols.update(symbols) for isvarName, isvarType in interstate_symbols.items(): isvar = data.Scalar(isvarType) callsite_stream.write( '%s;\n' % (isvar.as_arg(with_types=True, name=isvarName)), sdfg) self.dispatcher.defined_vars.add(isvarName, disp.DefinedType.Scalar, isvarType.ctype) callsite_stream.write('\n', sdfg) ####################################################################### # Generate actual program body states_generated = self.generate_states(sdfg, global_stream, callsite_stream) ####################################################################### # Sanity check if len(states_generated) != len(sdfg.nodes()): raise RuntimeError( "Not all states were generated in SDFG {}!" "\n Generated: {}\n Missing: {}".format( sdfg.label, [s.label for s in states_generated], [s.label for s in (set(sdfg.nodes()) - states_generated)])) # Deallocate transients self.deallocate_arrays_in_scope(sdfg, sdfg, global_stream, callsite_stream) # Now that we have all the information about dependencies, generate # header and footer if is_top_level: # Let each target append code to frame code state before generating # header and footer for target in self._dispatcher.used_targets: target.on_target_used() header_stream = CodeIOStream() header_global_stream = CodeIOStream() footer_stream = CodeIOStream() footer_global_stream = CodeIOStream() # Get all environments used in the generated code, including # dependent environments import dace.library # Avoid import loops self.environments = dace.library.get_environments_and_dependencies( self._dispatcher.used_environments) self.generate_header(sdfg, header_global_stream, header_stream) # Open program function params = sdfg.signature() if params: params = ', ' + params function_signature = ( 'void __program_%s_internal(%s_t *__state%s)\n{\n' % (sdfg.name, sdfg.name, params)) self.generate_footer(sdfg, footer_global_stream, footer_stream) header_global_stream.write(global_stream.getvalue()) header_global_stream.write(footer_global_stream.getvalue()) generated_header = header_global_stream.getvalue() all_code = CodeIOStream() all_code.write(function_signature) all_code.write(header_stream.getvalue()) all_code.write(callsite_stream.getvalue()) all_code.write(footer_stream.getvalue()) generated_code = all_code.getvalue() else: generated_header = global_stream.getvalue() generated_code = callsite_stream.getvalue() # Clean up generated code gotos = re.findall(r'goto (.*?);', generated_code) clean_code = '' for line in generated_code.split('\n'): # Empty line with semicolon if re.match(r'^\s*;\s*', line): continue # Label that might be unused label = re.findall( r'^\s*([a-zA-Z_][a-zA-Z_0-9]*):\s*[;]?\s*////.*$', line) if len(label) > 0: if label[0] not in gotos: continue clean_code += line + '\n' # Return the generated global and local code strings return (generated_header, clean_code, self._dispatcher.used_targets, self._dispatcher.used_environments)
def generate_code( self, sdfg: SDFG, schedule: Optional[dtypes.ScheduleType], sdfg_id: str = "" ) -> Tuple[str, str, Set[TargetCodeGenerator], Set[str]]: """ Generate frame code for a given SDFG, calling registered targets' code generation callbacks for them to generate their own code. :param sdfg: The SDFG to generate code for. :param schedule: The schedule the SDFG is currently located, or None if the SDFG is top-level. :param sdfg_id: An optional string id given to the SDFG label :return: A tuple of the generated global frame code, local frame code, and a set of targets that have been used in the generation of this SDFG. """ if len(sdfg_id) == 0 and sdfg.sdfg_id != 0: sdfg_id = '_%d' % sdfg.sdfg_id sdfg_label = sdfg.name + sdfg_id global_stream = CodeIOStream() callsite_stream = CodeIOStream() # Set default storage/schedule types in SDFG set_default_schedule_and_storage_types(sdfg, schedule) is_top_level = sdfg.parent is None # Generate code ########################### # Invoke all instrumentation providers for instr in self._dispatcher.instrumentation.values(): if instr is not None: instr.on_sdfg_begin(sdfg, callsite_stream, global_stream) # Allocate outer-level transients shared_transients = sdfg.shared_transients() allocated = set() for state in sdfg.nodes(): for node in state.data_nodes(): if (node.data in shared_transients and node.data not in allocated): self._dispatcher.dispatch_allocate(sdfg, state, None, node, global_stream, callsite_stream) allocated.add(node.data) # Allocate inter-state variables global_symbols = copy.deepcopy(sdfg.symbols) interstate_symbols = {} for e in sdfg.edges(): symbols = e.data.new_symbols(global_symbols) interstate_symbols.update(symbols) global_symbols.update(symbols) for isvarName, isvarType in interstate_symbols.items(): # Skip symbols that have been declared as outer-level transients if isvarName in allocated: continue isvar = data.Scalar(isvarType) callsite_stream.write( '%s;\n' % (isvar.as_arg(with_types=True, name=isvarName)), sdfg) callsite_stream.write('\n', sdfg) states_topological = list(sdfg.topological_sort(sdfg.start_state)) # {edge: [dace.edges.ControlFlow]} control_flow = {e: [] for e in sdfg.edges()} if dace.config.Config.get_bool('optimizer', 'detect_control_flow'): #################################################################### # Loop detection procedure all_cycles = list(sdfg.find_cycles()) # Returns a list of lists # Order according to topological sort all_cycles = [ sorted(c, key=lambda x: states_topological.index(x)) for c in all_cycles ] # Group in terms of starting node starting_nodes = [c[0] for c in all_cycles] # Order cycles according to starting node in topological sort starting_nodes = sorted(starting_nodes, key=lambda x: states_topological.index(x)) cycles_by_node = [[c for c in all_cycles if c[0] == n] for n in starting_nodes] for cycles in cycles_by_node: # Use arbitrary cycle to find the first and last nodes first_node = cycles[0][0] last_node = cycles[0][-1] if not first_node.is_empty(): # The entry node should not contain any computations continue if not all([c[-1] == last_node for c in cycles]): # There are multiple back edges: not a for or while loop continue previous_edge = [ e for e in sdfg.in_edges(first_node) if e.src != last_node ] if len(previous_edge) != 1: # No single starting point: not a for or while continue previous_edge = previous_edge[0] back_edge = sdfg.edges_between(last_node, first_node) if len(back_edge) != 1: raise RuntimeError("Expected exactly one edge in cycle") back_edge = back_edge[0] # Build a set of all nodes in all cycles associated with this # set of start and end node internal_nodes = functools.reduce( lambda a, b: a | b, [set(c) for c in cycles]) - {first_node} exit_edge = [ e for e in sdfg.out_edges(first_node) if e.dst not in internal_nodes | {first_node} ] if len(exit_edge) != 1: # No single stopping condition: not a for or while # (we don't support continue or break) continue exit_edge = exit_edge[0] entry_edge = [ e for e in sdfg.out_edges(first_node) if e != exit_edge ] if len(entry_edge) != 1: # No single starting condition: not a for or while continue entry_edge = entry_edge[0] # Make sure this is not already annotated to be another construct if (len(control_flow[entry_edge]) != 0 or len(control_flow[back_edge]) != 0): continue # Nested loops case I - previous edge of internal loop is a # loop-entry of an external loop (first state in a loop is # another loop) if (len(control_flow[previous_edge]) == 1 and isinstance( control_flow[previous_edge][0], cflow.LoopEntry)): # Nested loop, mark parent scope loop_parent = control_flow[previous_edge][0].scope # Nested loops case II - exit edge of internal loop is a # back-edge of an external loop (last state in a loop is another # loop) elif (len(control_flow[exit_edge]) == 1 and isinstance( control_flow[exit_edge][0], cflow.LoopBack)): # Nested loop, mark parent scope loop_parent = control_flow[exit_edge][0].scope elif (len(control_flow[exit_edge]) == 0 or len(control_flow[previous_edge]) == 0): loop_parent = None else: continue if entry_edge == back_edge: # No entry check (we don't support do-loops) # TODO: do we want to add some support for self-loops? continue # Now we make sure that there is no other way to exit this # cycle, by checking that there's no reachable node *not* # included in any cycle between the first and last node. if any([len(set(c) - internal_nodes) > 1 for c in cycles]): continue # This is a loop! Generate the necessary annotation objects. loop_scope = cflow.LoopScope(internal_nodes) if ((len(previous_edge.data.assignments) > 0 or len(back_edge.data.assignments) > 0) and (len(control_flow[previous_edge]) == 0 or (len(control_flow[previous_edge]) == 1 and control_flow[previous_edge][0].scope == loop_parent))): # Generate assignment edge, if available control_flow[previous_edge].append( cflow.LoopAssignment(loop_scope, previous_edge)) # Assign remaining control flow constructs control_flow[entry_edge].append( cflow.LoopEntry(loop_scope, entry_edge)) control_flow[exit_edge].append( cflow.LoopExit(loop_scope, exit_edge)) control_flow[back_edge].append( cflow.LoopBack(loop_scope, back_edge)) ################################################################### # If/then/else detection procedure candidates = [ n for n in states_topological if sdfg.out_degree(n) == 2 ] for candidate in candidates: # A valid if occurs when then are no reachable nodes for either # path that does not pass through a common dominator. dominators = nx.dominance.dominance_frontiers( sdfg.nx, candidate) left_entry, right_entry = sdfg.out_edges(candidate) if (len(control_flow[left_entry]) > 0 or len(control_flow[right_entry]) > 0): # Already assigned to a control flow construct # TODO: carefully allow this in some cases continue left, right = left_entry.dst, right_entry.dst dominator = dominators[left] & dominators[right] if len(dominator) != 1: # There must be a single dominator across both branches, # unless one of the nodes _is_ the next dominator # if (len(dominator) == 0 and dominators[left] == {right} # or dominators[right] == {left}): # dominator = dominators[left] | dominators[right] # else: # continue continue dominator = next(iter(dominator)) # Exactly one dominator exit_edges = sdfg.in_edges(dominator) if len(exit_edges) != 2: # There must be a single entry and a single exit. This # could be relaxed in the future. continue left_exit, right_exit = exit_edges if (len(control_flow[left_exit]) > 0 or len(control_flow[right_exit]) > 0): # Already assigned to a control flow construct # TODO: carefully allow this in some cases continue # Now traverse from the source and verify that all possible paths # pass through the dominator left_nodes = sdfg.all_nodes_between(left, dominator) if left_nodes is None: # Not all paths lead to the next dominator continue right_nodes = sdfg.all_nodes_between(right, dominator) if right_nodes is None: # Not all paths lead to the next dominator continue all_nodes = left_nodes | right_nodes # Make sure there is no overlap between left and right nodes if len(left_nodes & right_nodes) > 0: continue # This is a valid if/then/else construct. Generate annotations if_then_else = cflow.IfThenElse(candidate, dominator) # Arbitrarily assign then/else to the two branches. If one edge # has no dominator but leads to the dominator, it means there's # only a then clause (and no else). has_else = False if len(dominators[left]) == 1: then_scope = cflow.IfThenScope(if_then_else, left_nodes) else_scope = cflow.IfElseScope(if_then_else, right_nodes) control_flow[left_entry].append( cflow.IfEntry(then_scope, left_entry)) control_flow[left_exit].append( cflow.IfExit(then_scope, left_exit)) control_flow[right_exit].append( cflow.IfExit(else_scope, right_exit)) if len(dominators[right]) == 1: control_flow[right_entry].append( cflow.IfEntry(else_scope, right_entry)) has_else = True else: then_scope = cflow.IfThenScope(if_then_else, right_nodes) else_scope = cflow.IfElseScope(if_then_else, left_nodes) control_flow[right_entry].append( cflow.IfEntry(then_scope, right_entry)) control_flow[right_exit].append( cflow.IfExit(then_scope, right_exit)) control_flow[left_exit].append( cflow.IfExit(else_scope, left_exit)) ####################################################################### # Generate actual program body states_generated = set() # For sanity check generated_edges = set() self.generate_states(sdfg, "sdfg", control_flow, global_stream, callsite_stream, set(states_topological), states_generated, generated_edges) ####################################################################### # Sanity check if len(states_generated) != len(sdfg.nodes()): raise RuntimeError( "Not all states were generated in SDFG {}!" "\n Generated: {}\n Missing: {}".format( sdfg.label, [s.label for s in states_generated], [s.label for s in (set(sdfg.nodes()) - states_generated)])) # Deallocate transients shared_transients = sdfg.shared_transients() deallocated = set() for state in sdfg.nodes(): for node in state.data_nodes(): if (node.data in shared_transients and node.data not in deallocated): self._dispatcher.dispatch_deallocate( sdfg, state, None, node, global_stream, callsite_stream) deallocated.add(node.data) # Now that we have all the information about dependencies, generate # header and footer if is_top_level: header_stream = CodeIOStream() header_global_stream = CodeIOStream() footer_stream = CodeIOStream() footer_global_stream = CodeIOStream() self.generate_header(sdfg, self._dispatcher.used_environments, header_global_stream, header_stream) # Open program function function_signature = 'void __program_%s_internal(%s)\n{\n' % ( sdfg.name, sdfg.signature()) self.generate_footer(sdfg, self._dispatcher.used_environments, footer_global_stream, footer_stream) header_global_stream.write(global_stream.getvalue()) header_global_stream.write(footer_global_stream.getvalue()) generated_header = header_global_stream.getvalue() all_code = CodeIOStream() all_code.write(function_signature) all_code.write(header_stream.getvalue()) all_code.write(callsite_stream.getvalue()) all_code.write(footer_stream.getvalue()) generated_code = all_code.getvalue() else: generated_header = global_stream.getvalue() generated_code = callsite_stream.getvalue() # Clean up generated code gotos = re.findall(r'goto (.*);', generated_code) clean_code = '' for line in generated_code.split('\n'): # Empty line with semicolon if re.match(r'^\s*;\s*', line): continue # Label that might be unused label = re.findall( r'^\s*([a-zA-Z_][a-zA-Z_0-9]*):\s*[;]?\s*////.*$', line) if len(label) > 0: if label[0] not in gotos: continue clean_code += line + '\n' # Return the generated global and local code strings return (generated_header, clean_code, self._dispatcher.used_targets, self._dispatcher.used_environments)