def visit_Call(self, node: Call): if _is_operation_call(node): node.func = copy_location(_wrap_in_controlled(node.func), node) node.args.insert( 0, copy_location(Name(self._control_param_name, ctx=Load()), node.args[0])) return node if _is_variant_call(node): functor_application = node.func if _identify_signature(functor_application) == 'Id': node.func = copy_location(_wrap_in_controlled(node.func), node) node.args.insert( 0, copy_location(Name(self._control_param_name, ctx=Load()), node.args[0])) else: node.args[0] = copy_location( _extend_control_data(node.args[0], self._control_param_name), node.args[0]) self.generic_visit(node) return node
def visit_Call(self, node: ast.Call): orig_node_id = id(node) with self.attrsub_context(node): if isinstance(node.func, ast.Attribute): node.func = self.visit_Attribute(node.func, call_context=True) elif isinstance(node.func, ast.Subscript): node.func = self.visit_Subscript(node.func, call_context=True) else: node.func = self.visit(node.func) # TODO: need a way to rewrite ast of subscript args, # and to process these separately from outer rewrite node.args = self._get_replacement_args(node.args, False) node.keywords = self._get_replacement_args(node.keywords, True) # in order to ensure that the args are processed with appropriate active scope, # we need to make sure not to use the active namespace scope on args (in the case # of a function call on an ast.Attribute). # # We do so by emitting an "enter argument list", whose handler pushes the current active # scope while we process each argument. The "end argument list" event will then restore # the active scope. # # This effectively rewrites function calls as follows: # f(a, b, ..., c) -> trace(f, 'enter argument list')(a, b, ..., c) with fast.location_of(node.func): node.func = fast.Call( func=self._emitter_ast(), args=[TraceEvent.before_call.to_ast(), self._get_copy_id_ast(orig_node_id)], keywords=fast.kwargs( ret=node.func, call_node_id=self._get_copy_id_ast(orig_node_id), ), ) # f(a, b, ..., c) -> trace(f(a, b, ..., c), 'exit argument list') with fast.location_of(node): node = fast.Call( func=self._emitter_ast(), args=[TraceEvent.after_call.to_ast(), self._get_copy_id_ast(orig_node_id)], keywords=fast.kwargs( ret=node, call_node_id=self._get_copy_id_ast(orig_node_id), ), ) return self._maybe_wrap_symbol_in_before_after_tracing(node, call_context=True, orig_node_id=orig_node_id)
def visit_Call(self, node: ast.Call) -> Any: try: global_func = astutils.evalnode(node.func, self.globals) if self.resolve_functions: global_val = astutils.evalnode(node, self.globals) else: global_val = node except SyntaxError: return self.generic_visit(node) newnode = None if self.resolve_functions and global_val is not node: # Without this check, casts don't generate code if not isinstance(global_val, dtypes.typeclass): newnode = self.global_value_to_node( global_val, parent_node=node, qualname=astutils.unparse(node), recurse=True) if newnode is not None: return newnode elif not isinstance(global_func, dtypes.typeclass): callables = not self.do_not_detect_callables newnode = self.global_value_to_node( global_func, parent_node=node, qualname=astutils.unparse(node), recurse=True, detect_callables=callables) if newnode is not None: node.func = newnode return self.generic_visit(node) return self.generic_visit(node)
def visit_Call(self, node: ast.Call): # Only parse calls to parsed SDFGConvertibles if not isinstance(node.func, (ast.Num, ast.Constant)): return self.generic_visit(node) if isinstance(node.func, ast.Num): value = node.func.n else: value = node.func.value if not hasattr(value, '__sdfg__') or isinstance(value, SDFG): return self.generic_visit(node) constant_args = self._eval_args(node) # Resolve nested closure as necessary try: qualname = next(k for k, v in self.closure.closure_sdfgs.items() if v is value) if hasattr(value, 'closure_resolver'): self.closure.nested_closures.append( (qualname, value.closure_resolver(constant_args, self.closure))) else: self.closure.nested_closures.append((qualname, SDFGClosure())) except Exception as ex: # Parsing failed (anything can happen here) warnings.warn(f'Parsing SDFGConvertible {value} failed: {ex}') # Return old call AST instead node.func = node.func.oldnode.func return self.generic_visit(node)
def getAttributeFloatAst(call_node: ast.Call): r''' Return an attribute on one of the xAOD objects. ''' # Get the name of the moment out if len(call_node.args) != 1: raise BaseException( "Calling getMomentFloat - only one argument is allowed") if not isinstance(call_node.args[0], ast.Str): raise BaseException( "Calling getMomentFloat - only acceptable argument is a string") r = cpp_ast.CPPCodeValue() # We don't need include files for this - just quick access r.args = [ 'moment_name', ] r.replacement_instance_obj = ('obj_j', call_node.func.value.id) r.running_code += [ 'float result = obj_j->getAttribute<float>(moment_name);' ] r.result = 'result' r.result_rep = lambda sc: crep.cpp_variable( unique_name("jet_attrib"), scope=sc, cpp_type=ctyp.terminal('float')) # Replace it as the function that is going to get called. call_node.func = r return call_node
def getAttributeVectorFloatAst(call_node: ast.Call): r''' Return a cpp ast accessing a vector of doubles for an xAOD attribute ''' # Get the name of the moment out if len(call_node.args) != 1: raise BaseException( "Calling getMomentFloat - only one argument is allowed") if not isinstance(call_node.args[0], ast.Str): raise BaseException( "Calling getMomentFloat - only acceptable argument is a string") r = cpp_ast.CPPCodeValue() r.include_files += ['vector'] r.args = [ 'moment_name', ] r.replacement_instance_obj = ('obj_j', call_node.func.value.id) r.running_code += [ 'auto result = obj_j->getAttribute<std::vector<double>>(moment_name);' ] r.result = 'result' r.result_rep = lambda sc: crep.cpp_collection( unique_name("jet_vec_attrib_"), scope=sc, collection_type=ctyp.collection(ctyp.terminal('double'))) # Replace it as the function that is going to get called. call_node.func = r return call_node
def visit_Call(self, node: ast.Call): # Only parse calls to parsed SDFGConvertibles if not isinstance(node.func, (ast.Num, ast.Constant)): self.seen_calls.add(astutils.unparse(node.func)) return self.generic_visit(node) if hasattr(node.func, 'oldnode'): if isinstance(node.func.oldnode, ast.Call): self.seen_calls.add(astutils.unparse(node.func.oldnode.func)) else: self.seen_calls.add(astutils.rname(node.func.oldnode)) if isinstance(node.func, ast.Num): value = node.func.n else: value = node.func.value if not hasattr(value, '__sdfg__') or isinstance(value, SDFG): return self.generic_visit(node) constant_args = self._eval_args(node) # Resolve nested closure as necessary qualname = None try: if id(value) in self.closure.closure_sdfgs: qualname, _ = self.closure.closure_sdfgs[id(value)] elif hasattr(node.func, 'qualname'): qualname = node.func.qualname self.seen_calls.add(qualname) if hasattr(value, 'closure_resolver'): self.closure.nested_closures.append( (qualname, value.closure_resolver(constant_args, self.closure))) else: self.closure.nested_closures.append((qualname, SDFGClosure())) except DaceRecursionError: # Parsing failed in a nested context, raise raise except Exception as ex: # Parsing failed (anything can happen here) optional_qname = '' if qualname is not None: optional_qname = f' ("{qualname}")' warnings.warn( f'Preprocessing SDFGConvertible {value}{optional_qname} failed with {type(ex).__name__}: {ex}' ) if Config.get_bool('frontend', 'raise_nested_parsing_errors'): raise if id(value) in self.closure.closure_sdfgs: del self.closure.closure_sdfgs[id(value)] # Return old call AST instead if not hasattr(node.func, 'oldnode'): raise node.func = node.func.oldnode.func return self.generic_visit(node)
def visit_Call(self, node: ast.Call): is_attrsub = False if isinstance(node.func, (ast.Attribute, ast.Subscript)): is_attrsub = True with self.attrsub_load_context(): node.func = self.visit_Attribute_or_Subscript( node.func, call_context=True) # TODO: need a way to rewrite ast of attribute and subscript args, # and to process these separately from outer rewrite node.args = self._get_replacement_args(node.args, is_attrsub, False) node.keywords = self._get_replacement_args(node.keywords, is_attrsub, True) # in order to ensure that the args are processed with appropriate active scope, # we need to push current active scope before processing the args and pop after # (pop happens on function return as opposed to in tracer) node.func = ast.Call( func=ast.Name(self.scope_pusher, ast.Load()), args=[node.func], keywords=[], ) node = ast.Call(func=ast.Name(self.scope_popper, ast.Load()), args=[node, ast.NameConstant(is_attrsub)], keywords=[]) if self.inside_attrsub_load_chain or not is_attrsub: return node replacement_node = ast.Call(func=ast.Name(self.end_tracer, ast.Load()), args=[node, ast.NameConstant(True)], keywords=[]) ast.copy_location(replacement_node, node) return replacement_node
def _wrap_call_if_functional(cls, call: ast.Call, func_map: Dict[str, ast.FunctionDef]) -> None: """ Wrap the function call with a cache check if it's functional """ # TODO: Use better function identifier than name func_name = call.func.id # TODO: Parse the args and identify if all functional args, kws = call.args, call.keywords if Functional.function(func_map.get(func_name), func_map): _logger.debug(f'{func_name} is functional') # TODO: Possibly only cache if it takes longer than X seconds call.args.append(call.func) call.func = ast.Name(id='cached_func', ctx=ast.Load(), lineno=call.func.lineno, col_offset=call.func.col_offset) else: # TODO: Recursively cache non-functional methods that are called _logger.debug(f'{call.func.id} is not functional')
def visit_Call(self, node: ast.Call): # Struct initializer name = astutils.rname(node.func) if name not in self._structs: return self.generic_visit(node) # Parse name and fields struct = self._structs[name] name = struct.name fields = {astutils.rname(arg.arg): arg.value for arg in node.keywords} if tuple(sorted(fields.keys())) != tuple(sorted(struct.fields.keys())): raise SyntaxError('Mismatch in fields in struct definition') # Create custom node #new_node = astutils.StructInitializer(name, fields) #return ast.copy_location(new_node, node) node.func = ast.copy_location( ast.Name(id='__DACESTRUCT_' + name, ctx=ast.Load()), node.func) return node
def get_collection(self, md: EventCollectionSpecification, call_node: ast.Call): r''' Return a cpp ast for accessing the jet collection with the given arguments. ''' # Get the name jet collection to look at. if len(call_node.args) != 1: raise ValueError( f"Calling {md.name} - only one argument is allowed") if not isinstance(call_node.args[0], ast.Str): raise ValueError( f"Calling {md.name} - only acceptable argument is a string") # Fill in the CPP block next. r = cpp_ast.CPPCodeValue() r.args = [ 'collection_name', ] r.include_files += md.include_files r.link_libraries += md.libraries r.running_code += self.get_running_code(md.container_type) r.result = 'result' if issubclass(type(md.container_type), event_collection_collection_container): r.result_rep = lambda scope: crep.cpp_collection( unique_name(md.name.lower()), scope=scope, collection_type=md.container_type) # type: ignore else: r.result_rep = lambda scope: crep.cpp_variable( unique_name(md.name.lower()), scope=scope, cpp_type=md.container_type) # Replace it as the function that is going to get called. call_node.func = r # type: ignore return call_node
def update_callable_after_validation(self, call: ast.Call, callable_id: str, callable_target: Callable): # if the arguments are not generic, include the specified method in the symbol table if (isinstance(callable_target, IBuiltinMethod) and callable_target.identifier != callable_id and callable_target.raw_identifier == callable_id and callable_target.identifier not in self.symbols): self.symbols[callable_target.identifier] = callable_target call.func = ast.Name(lineno=call.func.lineno, col_offset=call.func.col_offset, ctx=ast.Load(), id=callable_target.identifier) # validates if metadata matches callable's requirements if hasattr(callable_target, 'requires_storage') and callable_target.requires_storage: if not self._metadata.has_storage: self._log_error( CompilerError.MetadataInformationMissing( line=call.func.lineno, col=call.func.col_offset, symbol_id=callable_target.identifier, metadata_attr_id='has_storage' ) ) else: self._current_method.set_storage()
def build_CPPCodeValue(spec: CPPCodeSpecification, call_node: ast.Call) -> ast.Call: ''' Given the specification for a C++ code block, invoked as a function in our AST, replace the call node with a cpp spec callback AST so the C++ code is properly inserted into the call stream. Args: spec (CPPCodeSpecification): The specification, including the code, that we should insert at this call node call_node (ast.Call): The call node (with arguments) that we are going to replace with this C++ code. Raises: ValueError: Raised if something is wrong with the call site Returns: [type]: The C++ ast that can easily be emitted as code ''' if len(call_node.args) != len(spec.arguments): raise ValueError( f"The call of {spec.name}({', '.join(spec.arguments)}) has insufficient arguments ({len(call_node.args)})." ) if isinstance(call_node.func, ast.Attribute) and spec.method_object is None: raise ValueError( f"The {spec.name} is a function, but it is invoked like a method.") if isinstance(call_node.func, ast.Name) and spec.method_object is not None: raise ValueError( f"The {spec.name} is a method, but it is invoked like a function.") # Create an AST to hold onto all of this. r = CPPCodeValue() # We need TVector2 included here r.include_files += spec.include_files # We need all four arguments pushed through. r.args = spec.arguments # The code is three steps r.running_code += spec.code r.result = spec.result if spec.cpp_return_is_collection: r.result_rep = lambda scope: cpp_collection( unique_name(spec.name), scope=scope, collection_type=ctyp.collection(ctyp.terminal(spec.cpp_return_type) )) # type: ignore else: r.result_rep = lambda scope: cpp_variable(unique_name(spec.name), scope=scope, cpp_type=ctyp.terminal( spec.cpp_return_type)) # If this is a mehtod, copy the info over to generate the obj reference. if spec.method_object is not None: r.replacement_instance_obj = (spec.method_object, call_node.func.value.id) # type: ignore call_node.func = r # type: ignore return call_node