def visit_If(self, node): self.generic_visit(node) try: if ast.literal_eval(node.test): if not metadata.get(node, OMPDirective): self.update = True return node.body else: if not metadata.get(node, OMPDirective): self.update = True return node.orelse except ValueError: # not a constant expression pass have_body = any(not isinstance(x, ast.Pass) for x in node.body) have_else = any(not isinstance(x, ast.Pass) for x in node.orelse) # If the "body" is empty but "else content" is useful, switch branches # and remove else content if not have_body and have_else: test = ast.UnaryOp(op=ast.Not(), operand=node.test) self.update = True return ast.If(test=test, body=node.orelse, orelse=list()) # if neither "if" and "else" are useful, keep test if it is not pure elif not have_body: self.update = True if node.test in self.pure_expressions: return ast.Pass() else: node = ast.Expr(value=node.test) self.generic_visit(node) return node
def visit(self, node): """ Add OMPDirective from the old node to the new one. """ old_omp = metadata.get(node, OMPDirective) node = super(DeadCodeElimination, self).visit(node) if not metadata.get(node, OMPDirective): for omp_directive in old_omp: metadata.add(node, omp_directive) return node
def can_use_c_for(self, node): """ Check if a for loop can use classic C syntax. To use C syntax: - target should not be assign in the loop - xrange should be use as iterator - order have to be known at compile time or OpenMP should not be use """ assert isinstance(node.target, ast.Name) pattern = ast.Call(func=ast.Attribute(value=ast.Name(id='__builtin__', ctx=ast.Load()), attr='xrange', ctx=ast.Load()), args=AST_any(), keywords=[], starargs=None, kwargs=None) is_assigned = {node.target.id: False} [is_assigned.update(self.passmanager.gather(IsAssigned, stmt)) for stmt in node.body] if (node.iter not in ASTMatcher(pattern).search(node.iter) or is_assigned[node.target.id]): return False args = node.iter.args if (len(args) > 2 and (not isinstance(args[2], ast.Num) and not (isinstance(args[1], ast.Num) and isinstance(args[0], ast.Num))) and metadata.get(node, OMPDirective)): return False return True
def visit_For(self, node): # first unroll children if needed or possible self.generic_visit(node) # if the user added some OpenMP directive, trust him and no unroll has_omp = metadata.get(node, OMPDirective) # a break or continue in the loop prevents unrolling too has_break = any(self.passmanager.gather(HasBreak, n, self.ctx) for n in node.body) has_cont = any(self.passmanager.gather(HasContinue, n, self.ctx) for n in node.body) # do not unroll too much to prevent code growth node_count = self.passmanager.gather(NodeCount, node, self.ctx) if type(node.iter) is ast.List: isvalid = not(has_omp or has_break or has_cont) total_count = node_count * len(node.iter.elts) issmall = total_count < LoopFullUnrolling.MAX_NODE_COUNT if isvalid and issmall: def unroll(elt): return ([ast.Assign([deepcopy(node.target)], elt)] + deepcopy(node.body)) self.update = True return reduce(list.__add__, map(unroll, node.iter.elts)) return node
def attach_data(self, node): '''Generic method called for visit_XXXX() with XXXX in GatherOMPData.statements list ''' if self.current: for curr in self.current: md = OMPDirective(curr) metadata.add(node, md) self.current = list() # add a Pass to hold some directives for field_name, field in ast.iter_fields(node): if field_name in GatherOMPData.statement_lists: if(field and isinstance(field[-1], ast.Expr) and self.isompdirective(field[-1].value)): field.append(ast.Pass()) self.generic_visit(node) # add an If to hold scoping OpenMP directives directives = metadata.get(node, OMPDirective) field_names = {n for n, _ in ast.iter_fields(node)} has_no_scope = field_names.isdisjoint(GatherOMPData.statement_lists) if directives and has_no_scope: # some directives create a scope, but the holding stmt may not # artificially create one here if needed sdirective = ''.join(d.s for d in directives) scoping = ('parallel', 'task', 'section') if any(s in sdirective for s in scoping): node = ast.If(ast.Num(1), [node], []) return node
def visit_For(self, node): # if the user added some OpenMP directive, trust him and no unroll if metadata.get(node, OMPDirective): return node # don't visit children because of collapse # first unroll children if needed or possible self.generic_visit(node) # a break or continue in the loop prevents unrolling too has_break = any(self.gather(HasBreak, n) for n in node.body) has_cont = any(self.gather(HasContinue, n) for n in node.body) if has_break or has_cont: return node # do not unroll too much to prevent code growth node_count = self.gather(NodeCount, node) def unroll(elt, body): return [ast.Assign([deepcopy(node.target)], elt)] + body def dc(body, i, n): if i == n - 1: return body else: return deepcopy(body) def getrange(n): return getattr(getattr(n, 'func', None), 'attr', None) if isinstance(node.iter, (ast.Tuple, ast.List)): elts_count = len(node.iter.elts) total_count = node_count * elts_count issmall = total_count < LoopFullUnrolling.MAX_NODE_COUNT if issmall: self.update = True return sum([unroll(elt, dc(node.body, i, elts_count)) for i, elt in enumerate(node.iter.elts)], []) code = compile(ast.gast_to_ast(ast.Expression(node.iter)), '<loop unrolling>', 'eval') try: values = list(eval(code, {'builtins': __import__('builtins')})) except Exception: return node values_count = len(values) total_count = node_count * values_count issmall = total_count < LoopFullUnrolling.MAX_NODE_COUNT if issmall: try: new_node = sum([unroll(to_ast(elt), dc(node.body, i, values_count)) for i, elt in enumerate(values)], []) self.update = True return new_node except Exception: return node return node
def visit_FunctionDef(self, node): if metadata.get(node, metadata.Local): if not self.def_use_chains.chains[node].users(): self.update = True return None return node
def attach_data(self, node): '''Generic method called for visit_XXXX() with XXXX in GatherOMPData.statements list ''' if self.current: for curr in self.current: md = OMPDirective(curr) metadata.add(node, md) self.current = list() # add a Pass to hold some directives for field_name, field in ast.iter_fields(node): if field_name in GatherOMPData.statement_lists: if(field and isinstance(field[-1], ast.Expr) and self.isompdirective(field[-1].value)): field.append(ast.Pass()) self.generic_visit(node) # add an If to hold scoping OpenMP directives directives = metadata.get(node, OMPDirective) field_names = {n for n, _ in ast.iter_fields(node)} has_no_scope = field_names.isdisjoint(GatherOMPData.statement_lists) if directives and has_no_scope: # some directives create a scope, but the holding stmt may not # artificially create one here if needed sdirective = ''.join(d.s for d in directives) scoping = ('parallel', 'task', 'section') if any(s in sdirective for s in scoping): metadata.clear(node, OMPDirective) node = ast.If(ast.Num(1), [node], []) for directive in directives: metadata.add(node, directive) return node
def visit_For(self, node): # first unroll children if needed or possible self.generic_visit(node) # if the user added some OpenMP directive, trust him and no unroll has_omp = metadata.get(node, OMPDirective) # a break or continue in the loop prevents unrolling too has_break = any( self.passmanager.gather(HasBreak, n, self.ctx) for n in node.body) has_cont = any( self.passmanager.gather(HasContinue, n, self.ctx) for n in node.body) # do not unroll too much to prevent code growth node_count = self.passmanager.gather(NodeCount, node, self.ctx) if type(node.iter) is ast.List: isvalid = not (has_omp or has_break or has_cont) total_count = node_count * len(node.iter.elts) issmall = total_count < LoopFullUnrolling.MAX_NODE_COUNT if isvalid and issmall: def unroll(elt): return ([ast.Assign([deepcopy(node.target)], elt)] + deepcopy(node.body)) return reduce(list.__add__, map(unroll, node.iter.elts)) return node
def handle_omp_for(self, node, local_iter): """ Fix OpenMP directives on For loops. Add the target as private variable as a new variable may have been introduce to handle cxx iterator. Also, add the iterator as shared variable as all 'parallel for chunck' have to use the same iterator. """ for directive in metadata.get(node, OMPDirective): if any(key in directive.s for key in (' parallel ', ' task ')): # Eventually add local_iter in a shared clause as iterable is # shared in the for loop (for every clause with datasharing) directive.s += ' shared({})' directive.deps.append(ast.Name(local_iter, ast.Load())) target = node.target assert isinstance(target, ast.Name) hasfor = 'for' in directive.s nodefault = 'default' not in directive.s noindexref = all(isinstance(x, ast.Name) and x.id != target.id for x in directive.deps) if (hasfor and nodefault and noindexref and target.id not in self.scope[node]): # Target is private by default in omp but iterator use may # introduce an extra variable directive.s += ' private({})' directive.deps.append(ast.Name(target.id, ast.Load()))
def handle_omp_for(self, node, local_iter): """ Fix OpenMP directives on For loops. Add the target as private variable as a new variable may have been introduce to handle cxx iterator. Also, add the iterator as shared variable as all 'parallel for chunck' have to use the same iterator. """ for directive in metadata.get(node, OMPDirective): if any(key in directive.s for key in (' parallel ', ' task ')): # Eventually add local_iter in a shared clause as iterable is # shared in the for loop (for every clause with datasharing) directive.s += ' shared({})' directive.deps.append(ast.Name(local_iter, ast.Load(), None)) target = node.target assert isinstance(target, ast.Name) hasfor = 'for' in directive.s nodefault = 'default' not in directive.s noindexref = all( isinstance(x, ast.Name) and x.id != target.id for x in directive.deps) if (hasfor and nodefault and noindexref and target.id not in self.scope[node]): # Target is private by default in omp but iterator use may # introduce an extra variable directive.s += ' private({})' directive.deps.append(ast.Name(target.id, ast.Load(), None))
def is_global_constant(node): if isinstance(node, ConstantIntr): return True if not isinstance(node, ast.FunctionDef): return False return metadata.get(node.body[0], metadata.StaticReturn)
def visit_Pass(self, node): ancestor = self.ancestors[node][-1] if getattr(ancestor, 'body', ()) == [node]: return node if getattr(ancestor, 'orelse', ()) == [node]: return node if metadata.get(node, OMPDirective): return node return None
def visit(self, node): old_omp = self.in_omp omp_nodes = md.get(node, openmp.OMPDirective) if omp_nodes: self.in_omp = set(self.name_count.keys()) super(LazynessAnalysis, self).visit(node) if omp_nodes: new_nodes = set(self.name_count).difference(self.in_omp) self.dead.update(new_nodes) self.in_omp = old_omp
def is_in_collapse(self, loop, node): for ancestor in reversed(self.ancestors[loop]): if not isinstance(ancestor, ast.For): return False for directive in metadata.get(ancestor, OMPDirective): if 'collapse' in directive.s: # FIXME: check loop depth and range canonicalization if node not in self.pure_expressions: raise PythranSyntaxError( "not pure expression used as loop target inside a " "collapse clause", loop) return True assert False, "unreachable state"
def visit_For(self, node): # if the user added some OpenMP directive, trust him and no unroll if metadata.get(node, OMPDirective): return node # don't visit children because of collapse # first unroll children if needed or possible self.generic_visit(node) # a break or continue in the loop prevents unrolling too has_break = any(self.gather(HasBreak, n) for n in node.body) has_cont = any(self.gather(HasContinue, n) for n in node.body) if has_break or has_cont: return node # do not unroll too much to prevent code growth node_count = self.gather(NodeCount, node) def unroll(elt): return ([ast.Assign([deepcopy(node.target)], elt)] + deepcopy(node.body)) def getrange(n): return getattr(getattr(n, 'func', None), 'attr', None) if isinstance(node.iter, (ast.Tuple, ast.List)): total_count = node_count * len(node.iter.elts) issmall = total_count < LoopFullUnrolling.MAX_NODE_COUNT if issmall: self.update = True return sum([unroll(elt) for elt in node.iter.elts], []) code = compile(ast.gast_to_ast(ast.Expression(node.iter)), '<loop unrolling>', 'eval') try: values = list(eval(code, {'__builtin__': __import__('__builtin__')})) except Exception: return node total_count = node_count * len(values) issmall = total_count < LoopFullUnrolling.MAX_NODE_COUNT if issmall: try: new_node = sum([unroll(to_ast(elt)) for elt in values], []) self.update = True return new_node except Exception: return node return node
def visit(self, node): old_omp = self.in_omp omp_nodes = md.get(node, openmp.OMPDirective) if omp_nodes: self.in_omp = set(self.name_count.keys()) super(LazynessAnalysis, self).visit(node) if omp_nodes: new_nodes = set(self.name_count).difference(self.in_omp) for omp_node in omp_nodes: for n in omp_node.deps: if isinstance(n, ast.Name): self.result[n.id] = LazynessAnalysis.INF self.dead.update(new_nodes) self.in_omp = old_omp
def visit_Return(self, node): value = self.visit(node.value) if metadata.get(node, metadata.StaticReturn): # don't rely on auto because we want to make sure there's no # conversion each time we return # this happens for variant because the variant param # order may differ from the init order (because of the way we # do type inference rtype = "typename {}::type::result_type".format(self.fname) stmt = Block([Assign("static %s tmp_global" % rtype, value), ReturnStatement("tmp_global")]) else: stmt = ReturnStatement(value) return self.process_omp_attachements(node, stmt)
def visit_For(self, node): # first unroll children if needed or possible self.generic_visit(node) # if the user added some OpenMP directive, trust him and no unroll has_omp = metadata.get(node, OMPDirective) # a break or continue in the loop prevents unrolling too has_break = any( self.passmanager.gather(HasBreak, n, self.ctx) for n in node.body) has_cont = any( self.passmanager.gather(HasContinue, n, self.ctx) for n in node.body) if has_omp or has_break or has_cont: return node # do not unroll too much to prevent code growth node_count = self.passmanager.gather(NodeCount, node, self.ctx) def unroll(elt): return ([ast.Assign([deepcopy(node.target)], elt)] + deepcopy(node.body)) def getrange(n): return getattr(getattr(n, 'func', None), 'attr', None) if isinstance(node.iter, (ast.Tuple, ast.List)): total_count = node_count * len(node.iter.elts) issmall = total_count < LoopFullUnrolling.MAX_NODE_COUNT if issmall: self.update = True return sum([unroll(elt) for elt in node.iter.elts], []) code = compile(ast.gast_to_ast(ast.Expression(node.iter)), '<loop unrolling>', 'eval') try: values = list( eval(code, {'__builtin__': __import__('__builtin__')})) except Exception as e: return node total_count = node_count * len(values) issmall = total_count < LoopFullUnrolling.MAX_NODE_COUNT if issmall: try: new_node = sum([unroll(to_ast(elt)) for elt in values], []) self.update = True return new_node except Exception as e: return node return node
def visit_Return(self, node): if self.yields: return Block( [ Statement("{0} = -1".format(Cxx.generator_state_holder)), Statement("goto {0}".format(Cxx.final_statement)), ] ) else: value = self.visit(node.value) if metadata.get(node, metadata.StaticReturn): stmt = Block([Assign("static auto tmp_global", value), ReturnStatement("tmp_global")]) else: stmt = ReturnStatement(value) return self.process_omp_attachements(node, stmt)
def check_global(self, node, arg): if not isinstance(arg, ast.Call): return try: aliases = self.strict_aliases[arg.func] except KeyError: return for alias in aliases: if not isinstance(alias, ast.FunctionDef): continue if metadata.get(alias.body[0], metadata.StaticReturn): raise PythranSyntaxError( ("Cannot modify '{}': global variables are constant " "in pythran.").format(alias.name), node)
def can_use_autofor(self, node): """ Check if given for Node can use autoFor syntax. To use auto_for: - iterator should have local scope - yield should not be use - OpenMP pragma should not be use TODO : Yield should block only if it is use in the for loop, not in the whole function. """ auto_for = (isinstance(node.target, ast.Name) and node.target.id in self.scope[node]) auto_for &= not metadata.get(node, OMPDirective) return auto_for
def can_use_autofor(self, node): """ Check if given for Node can use autoFor syntax. To use auto_for: - iterator should have local scope - yield should not be use - OpenMP pragma should not be use TODO : Yield should block only if it is use in the for loop, not in the whole function. """ auto_for = isinstance(node.target, ast.Name) and node.target.id in self.scope[node] auto_for &= not self.yields auto_for &= not metadata.get(node, OMPDirective) return auto_for
def visit_Return(self, node): if self.yields: return Block([ Statement("{0} = -1".format(Cxx.generator_state_holder)), Statement("goto {0}".format(Cxx.final_statement)) ]) else: value = self.visit(node.value) if metadata.get(node, metadata.StaticReturn): stmt = Block([ Assign("static auto tmp_global", value), ReturnStatement("tmp_global") ]) else: stmt = ReturnStatement(value) return self.process_omp_attachements(node, stmt)
def visit_FunctionDef(self, node): """ Determine this function definition can be inlined. """ if len(node.body) != 1: return sbody = node.body[0] if not isinstance(sbody, (ast.Call, ast.Return)): return # only consider static return if they are pure if metadata.get(sbody, metadata.StaticReturn): if sbody not in self.pure_expressions: return ids = self.gather(Identifiers, sbody) # FIXME : It mark "not inlinable" def foo(foo): return foo if node.name not in ids: self.result[node.name] = copy.deepcopy(node)
def process_omp_attachements(self, node, stmt, index=None): """ Add OpenMP pragma on the correct stmt in the correct order. stmt may be a list. On this case, index have to be specify to add OpenMP on the correct statement. """ omp_directives = metadata.get(node, OMPDirective) if omp_directives: directives = list() for directive in reversed(omp_directives): directive.deps = map(self.visit, directive.deps) directives.append(directive) if index is None: stmt = AnnotatedStatement(stmt, directives) else: stmt[index] = AnnotatedStatement(stmt[index], directives) return stmt
def process_omp_attachements(self, node, stmt, index=None): """ Add OpenMP pragma on the correct stmt in the correct order. stmt may be a list. On this case, index have to be specify to add OpenMP on the correct statement. """ omp_directives = metadata.get(node, OMPDirective) if omp_directives: directives = list() for directive in omp_directives: directive.deps = [self.visit(dep) for dep in directive.deps] directives.append(directive) if index is None: stmt = AnnotatedStatement(stmt, directives) else: stmt[index] = AnnotatedStatement(stmt[index], directives) return stmt
def dispatch(self, tree): """Dispatcher function, dispatching tree type T to method _T.""" # display omp directive in python dump for omp in metadata.get(tree, openmp.OMPDirective): deps = list() for dep in omp.deps: old_file = self.f self.f = io.StringIO() self.dispatch(dep) deps.append(self.f.getvalue()) self.f = old_file directive = omp.s.format(*deps) self._Expr(ast.Expr(ast.Str(s=directive))) if isinstance(tree, list): for t in tree: self.dispatch(t) return meth = getattr(self, "_" + tree.__class__.__name__) meth(tree)
def dispatch(self, tree): """Dispatcher function, dispatching tree type T to method _T.""" # display omp directive in python dump for omp in metadata.get(tree, openmp.OMPDirective): deps = list() for dep in omp.deps: old_file = self.f self.f = io.StringIO() self.dispatch(dep) deps.append(self.f.getvalue()) self.f = old_file directive = omp.s.format(*deps) self._Expr(ast.Expr(ast.Constant(directive, None))) if isinstance(tree, list): for t in tree: self.dispatch(t) return meth = getattr(self, "_" + tree.__class__.__name__) meth(tree)
def visit_For(self, node): """ Create For representation for Cxx generation. Examples -------- >> for i in xrange(10): >> ... work ... Becomes >> typename returnable<decltype(__builtin__.xrange(10))>::type __iterX = __builtin__.xrange(10); >> ... possible container size reservation ... >> for (auto&& i: __iterX) >> ... the work ... This function also handle assignment for local variables. We can notice that three kind of loop are possible: - Normal for loop on iterator - Autofor loop. - Normal for loop using integer variable iteration Kind of loop used depend on OpenMP, yield use and variable scope. """ if not isinstance(node.target, ast.Name): raise PythranSyntaxError( "Using something other than an identifier as loop target", node.target) target = self.visit(node.target) # Handle the body of the for loop loop_body = Block(map(self.visit, node.body)) # Declare local variables at the top of the loop body loop_body = self.process_locals(node, loop_body, node.target.id) iterable = self.visit(node.iter) if self.can_use_c_for(node): header, loop = self.gen_c_for(node, target, loop_body) else: if self.can_use_autofor(node): header = [] self.ldecls = {d for d in self.ldecls if d.id != node.target.id} autofor = AutoFor(target, iterable, loop_body) loop = [self.process_omp_attachements(node, autofor)] else: # Iterator declaration local_iter = "__iter{0}".format(len(self.break_handlers)) local_iter_decl = Assignable(DeclType(iterable)) self.handle_omp_for(node, local_iter) # For yield function, iterable is globals. if self.yields: self.extra_declarations.append( (local_iter, local_iter_decl,)) local_iter_decl = "" # Assign iterable # For C loop, it avoids issues # if the upper bound is assigned in the loop header = [Statement("{0} {1} = {2}".format(local_iter_decl, local_iter, iterable))] loop = self.gen_for(node, target, local_iter, loop_body) # For xxxComprehension, it is replaced by a for loop. In this case, # pre-allocate size of container. for comp in metadata.get(node, metadata.Comprehension): header.append(Statement("pythonic::utils::reserve({0},{1})".format( comp.target, iterable))) return Block(header + loop)
def visit_For(self, node): """ Create For representation for Cxx generation. Examples -------- >> for i in xrange(10): >> ... work ... Becomes >> typename returnable<decltype(__builtin__.xrange(10))>::type __iterX = __builtin__.xrange(10); >> ... possible container size reservation ... >> for (typename decltype(__iterX)::iterator::reference i: __iterX) >> ... the work ... This function also handle assignment for local variables. We can notice that three kind of loop are possible: - Normal for loop on iterator - Autofor loop. - Normal for loop using integer variable iteration Kind of loop used depend on OpenMP, yield use and variable scope. """ if not isinstance(node.target, ast.Name): raise PythranSyntaxError( "Using something other than an identifier as loop target", node.target) target = self.visit(node.target) # Handle the body of the for loop loop_body = Block(map(self.visit, node.body)) # Declare local variables at the top of the loop body loop_body = self.process_locals(node, loop_body, node.target.id) iterable = self.visit(node.iter) if self.can_use_c_for(node): header, loop = self.gen_c_for(node, target, loop_body) else: # Iterator declaration local_iter = "__iter{0}".format(len(self.break_handlers)) local_iter_decl = Assignable(DeclType(iterable)) self.handle_omp_for(node, local_iter) # For yield function, iterable is globals. if self.yields: self.extra_declarations.append(( local_iter, local_iter_decl, )) local_iter_decl = "" # Assign iterable # For C loop, it avoid issue if upper bound is reassign in the loop header = [ Statement("{0} {1} = {2}".format(local_iter_decl, local_iter, iterable)) ] if self.can_use_autofor(node): self.ldecls = { d for d in self.ldecls if d.id != node.target.id } autofor = AutoFor(target, local_iter, loop_body) loop = [self.process_omp_attachements(node, autofor)] else: loop = self.gen_for(node, target, local_iter, local_iter_decl, loop_body) # For xxxComprehension, it is replaced by a for loop. In this case, # pre-allocate size of container. for comp in metadata.get(node, metadata.Comprehension): header.append( Statement("pythonic::utils::reserve({0},{1})".format( comp.target, iterable))) return Block(header + loop)
def visit_For(self, node): """ Create For representation for Cxx generation. Examples -------- >> for i in xrange(10): >> ... work ... Becomes >> typename returnable<decltype(__builtin__.xrange(10))>::type __iterX = __builtin__.xrange(10); >> ... possible container size reservation ... >> for (auto&& i: __iterX) >> ... the work ... This function also handle assignment for local variables. We can notice that three kind of loop are possible: - Normal for loop on iterator - Autofor loop. - Normal for loop using integer variable iteration Kind of loop used depend on OpenMP, yield use and variable scope. """ if not isinstance(node.target, ast.Name): raise PythranSyntaxError( "Using something other than an identifier as loop target", node.target) target = self.visit(node.target) # Handle the body of the for loop loop_body = Block([self.visit(stmt) for stmt in node.body]) # Declare local variables at the top of the loop body loop_body = self.process_locals(node, loop_body, node.target.id) iterable = self.visit(node.iter) if self.can_use_c_for(node): header, loop = self.gen_c_for(node, target, loop_body) else: if self.can_use_autofor(node): header = [] self.ldecls.remove(node.target.id) autofor = AutoFor(target, iterable, loop_body) loop = [self.process_omp_attachements(node, autofor)] else: # Iterator declaration local_iter = "__iter{0}".format(id(node)) local_iter_decl = self.types.builder.Assignable( self.types[node.iter]) self.handle_omp_for(node, local_iter) # Assign iterable # For C loop, it avoids issues # if the upper bound is assigned in the loop asgnt = self.make_assign(local_iter_decl, local_iter, iterable) header = [Statement(asgnt)] loop = self.gen_for(node, target, local_iter, local_iter_decl, loop_body) # For xxxComprehension, it is replaced by a for loop. In this case, # pre-allocate size of container. for comp in metadata.get(node, metadata.Comprehension): header.append( Statement("pythonic::utils::reserve({0},{1})".format( comp.target, iterable))) return Block(header + loop)