def _parse_and_analyze(self, test_fn): node, source = parser.parse_entity(test_fn, future_features=()) entity_info = transformer.EntityInfo( source_code=source, source_file=None, future_features=(), namespace={}) node = qual_names.resolve(node) ctx = transformer.Context(entity_info) node = activity.resolve(node, ctx) graphs = cfg.build(node) liveness.resolve(node, ctx, graphs) return node
def _parse_and_analyze(self, test_fn): node, source = parser.parse_entity(test_fn) entity_info = transformer.EntityInfo(source_code=source, source_file=None, namespace={}, arg_values=None, arg_types=None, owner_type=None) node = qual_names.resolve(node) node = activity.resolve(node, entity_info) graphs = cfg.build(node) liveness.resolve(node, entity_info, graphs) return node
def _parse_and_analyze(self, test_fn): node, source = parser.parse_entity(test_fn) entity_info = transformer.EntityInfo( source_code=source, source_file=None, namespace={}, arg_values=None, arg_types=None, owner_type=None) node = qual_names.resolve(node) node = activity.resolve(node, entity_info) graphs = cfg.build(node) liveness.resolve(node, entity_info, graphs) return node
def _parse_and_analyze(self, test_fn): # TODO(mdan): Use a custom FunctionTransformer here. node, source = parser.parse_entity(test_fn, future_features=()) entity_info = transformer.EntityInfo(name=test_fn.__name__, source_code=source, source_file=None, future_features=(), namespace={}) node = qual_names.resolve(node) namer = naming.Namer({}) ctx = transformer.Context(entity_info, namer, None) node = activity.resolve(node, ctx) graphs = cfg.build(node) liveness.resolve(node, ctx, graphs) return node
def standard_analysis(node, context, is_initial=False): """Performs a complete static analysis of the given code. Args: node: ast.AST context: converter.EntityContext is_initial: bool, whether this is the initial analysis done on the input source code Returns: ast.AST, same as node, with the static analysis annotations added """ # TODO(mdan): Clear static analysis here. # TODO(mdan): Consider not running all analyses every time. # TODO(mdan): Don't return a node because it's modified by reference. graphs = cfg.build(node) node = qual_names.resolve(node) node = activity.resolve(node, context, None) node = reaching_definitions.resolve(node, context, graphs, AnnotatedDef) node = liveness.resolve(node, context, graphs) node = live_values.resolve(node, context, config.PYTHON_LITERALS) node = type_info.resolve(node, context) # This second call allows resolving first-order class attributes. node = live_values.resolve(node, context, config.PYTHON_LITERALS) if is_initial: anno.dup( node, { anno.Static.DEFINITIONS: anno.Static.ORIG_DEFINITIONS, }, ) return node
def standard_analysis(node, context, is_initial=False): """Performs a complete static analysis of the given code. Args: node: ast.AST context: converter.EntityContext is_initial: bool, whether this is the initial analysis done on the input source code Returns: ast.AST, same as node, with the static analysis annotations added """ # TODO(mdan): Clear static analysis here. # TODO(mdan): Consider not running all analyses every time. # TODO(mdan): Don't return a node because it's modified by reference. graphs = cfg.build(node) node = qual_names.resolve(node) node = activity.resolve(node, context.info, None) node = reaching_definitions.resolve(node, context.info, graphs, AnnotatedDef) node = liveness.resolve(node, context.info, graphs) node = live_values.resolve(node, context.info, config.PYTHON_LITERALS) node = type_info.resolve(node, context.info) # This second call allows resolving first-order class attributes. node = live_values.resolve(node, context.info, config.PYTHON_LITERALS) if is_initial: anno.dup( node, { anno.Static.DEFINITIONS: anno.Static.ORIG_DEFINITIONS, }, ) return node
def transform(node, ctx): graphs = cfg.build(node) node = qual_names.resolve(node) node = activity.resolve(node, ctx, None) node = reaching_definitions.resolve(node, ctx, graphs, AnnotatedDef) node = liveness.resolve(node, ctx, graphs) node = ControlFlowTransformer(ctx).visit(node) return node
def convert4(): node, ctx = get_node_and_ctx(f4) node = qual_names.resolve(node) cfgs = cfg.build(node) node = activity.resolve(node, ctx) node = reaching_definitions.resolve(node, ctx, cfgs) node = reaching_fndefs.resolve(node, ctx, cfgs) node = liveness.resolve(node, ctx, cfgs) print('live into `b = a + 1`:', anno.getanno(node.body[0], anno.Static.LIVE_VARS_IN)) print('live into `return b`:', anno.getanno(node.body[1], anno.Static.LIVE_VARS_IN))
def mlir_gen_internal(node, entity_info): """Returns mlir module for unprocessed node `node`.""" namer = naming.Namer({}) graphs = cfg.build(node) ctx = transformer.Context(entity_info, namer, None) node = qual_names.resolve(node) node = activity.resolve(node, ctx) node = reaching_definitions.resolve(node, ctx, graphs) node = reaching_fndefs.resolve(node, ctx, graphs) node = liveness.resolve(node, ctx, graphs) mlir_generator = MLIRGen(ctx) mlir_generator.visit(node) return mlir_generator.prog
def _live_tensors(f, attr_name="inputs"): """Returns the indices of the used inputs. Note: This currently only handles direct index accesses e.g. op.inputs[1]. If the function has slicing or list comprehension on attr_name then returns _ALL. This ensure that this is correct even if inefficient. Args: f: A grad function, taking the op as first argument. attr_name: op attr to track. "inputs" or "outputs". Returns: Either one of: * set of integers representing individual indices of inputs used * the value _ALL, if indices are used but cannot be determined which * empty set, if no inputs are used """ node, _ = parser.parse_entity(f, ()) entity_info = transformer.EntityInfo( name=f.__name__, source_code=None, source_file=None, future_features=(), namespace=sys.modules[f.__module__].__dict__) ctx = transformer.Context(entity_info, None, None) graphs = cfg.build(node) node = qual_names.resolve(node) node = activity.resolve(node, ctx, None) node = reaching_fndefs.resolve(node, ctx, graphs) node = liveness.resolve(node, ctx, graphs) op_arg_name = anno.getanno(node.args.args[0], anno.Basic.QN) op_inputs_outputs_name = qual_names.QN(op_arg_name, attr=attr_name) special_tracker = _SubscriptUseTracker(ctx, (op_inputs_outputs_name, )) node = special_tracker.visit(node) live_vars_in = anno.getanno(node.body[0], anno.Static.LIVE_VARS_IN) inputs_outputs_used_qns = set() for v in special_tracker.complex_reads: # Complicated patterns like op.inputs[:3]. Could be smarter about them # if they matter much. if v == op_inputs_outputs_name: return _ALL for v in live_vars_in: if v in special_tracker.reads: if (v.has_subscript() and v.parent == op_inputs_outputs_name): inputs_outputs_used_qns.add(v) elif v == op_inputs_outputs_name: # When op.{attr_name} is used directly, assume all tensors are # used for now. In that case, no point digging further. # TODO(mdan): We can descend into tuple expansions. return _ALL function_calls_tracker = _FunctionCallsTracker(ctx, op_arg_name) node = function_calls_tracker.visit(node) input_output_indices = set() for called_f in function_calls_tracker.calls: child_indices = _live_tensors(called_f, attr_name=attr_name) if child_indices is _ALL: return _ALL input_output_indices |= child_indices for v in inputs_outputs_used_qns: assert v.has_subscript() _, subscript = v.qn if not subscript.is_simple(): # Not a number, assuming it can be anything. return _ALL subscript_val, = subscript.qn if (not isinstance(subscript_val, qual_names.Literal) and not isinstance(subscript_val.value, int)): # Not a number, assuming it can be anything. return _ALL input_output_indices.add(subscript_val.value) return input_output_indices