def insert_allocations(func, env): b = Builder(func) # IR positions and list of ops positions = dict((op, idx) for idx, op in enumerate(func.ops)) oplist = list(func.ops) for op in func.ops: if op.opcode == 'ckernel': ckernel, args = op.args alloc = Op('alloc', op.type, args=[]) # TODO: Insert alloc in args list of ckernel # Replace uses of ckernel with temporary allocation op.replace_uses(alloc) op.set_args([ckernel, [alloc] + args]) # Emit allocation before first use b.position_before(op) b.emit(alloc) # Emit deallocation after last use, unless we are returning # the result idx = max(positions[u] for u in func.uses[alloc]) last_op = oplist[idx] if not last_op.opcode == 'ret': b.position_after(last_op) dealloc = Op('dealloc', types.Void, [alloc]) b.emit(dealloc) return func, env
def explicit_coercions(func, env=None): """ Turn implicit coercions into explicit conversion operations. """ conversions = {} b = Builder(func) for op in func.ops: if op.opcode != 'kernel': continue overload = op.metadata['overload'] signature = overload.resolved_sig parameters = signature.parameters[:-1] assert len(op.args) - 1 == len(parameters) # ------------------------------------------------- # Identify conversion points replacements = {} # { arg : replacement_conversion } for arg, param_type in zip(op.args[1:], parameters): if arg.type != param_type: conversion = conversions.get((arg, param_type)) if not conversion: conversion = Op('convert', param_type, [arg]) b.position_after(arg) b.emit(conversion) conversions[arg, param_type] = conversion replacements[arg] = conversion # ------------------------------------------------- op.replace_args(replacements)
def split_critical_edges(func, cfg, phis): """ Split critical edges to correctly handle cycles in phis. See 2) above. """ b = Builder(func) for block in cfg.node: successors = cfg.neighbors(block) if len(successors) > 1: # More than one successor, we need to split # (Alternatively, we could move our copies into the successor block # if we were the only predecessor, but this seems simpler) # Split successors with phis new_succs = {} # old_successor -> new_successor for succ in successors: if phis[succ]: label = func.temp("split_critical") new_succ = func.new_block(label, after=block) new_succs[succ] = new_succ b.position_at_end(new_succ) b.jump(succ) # Patch our basic-block terminator to point to new blocks if new_succs: terminator = block.terminator assert terminator.opcode == 'cbranch', terminator test, truebb, falsebb = terminator.args terminator.set_args([test, new_succs.get(truebb, truebb), new_succs.get(falsebb, falsebb)])
def initialize(self): """Initialize pykit untypes structures""" # Setup Function sig = types.Function(types.Opaque, [types.Opaque] * len(self.argnames), False) self.dst = Function(func_name(self.func), self.argnames, sig) # Setup Builder self.builder = Builder(self.dst) # Setup Blocks for offset in self.bytecode.labels: name = blockname(self.func, offset) block = self.dst.new_block(name) self.blocks[offset] = block self.stacks[block] = [] # Setup Variables self.builder.position_at_beginning(self.dst.startblock) for varname in self.varnames: stackvar = self.builder.alloca(types.Pointer(types.Opaque), result=self.dst.temp(varname)) self.allocas[varname] = stackvar # Initialize function arguments if varname in self.argnames: self.builder.store(self.dst.get_arg(varname), stackvar)
def rewrite_setattr(func, env): """ Resolve missing attributes through __setattr__ """ context = env['flypy.typing.context'] b = Builder(func) for op in func.ops: if op.opcode == 'setfield': obj, attr, value = op.args obj_type = context[obj] attr_type = types.String[()] if attr not in obj_type.fields and attr not in obj_type.layout: assert SETATTR in obj_type.fields, attr b.position_after(op) # Construct attribute string attr_string = OConst(attr) # call(getfield(obj, '__setattr__'), ['attr', value]) method_type = make_method(obj_type, SETATTR) method = b.getfield(ptypes.Opaque, obj, SETATTR) call = b.call(ptypes.Opaque, method, [attr_string, value]) op.delete() # Update context del context[op] context[method] = method_type context[call] = types.Void[()] context[attr_string] = attr_type
def insert_phis(func, cfg, allocas): """Insert φs in the function given the set of promotable stack variables""" builder = Builder(func) phis = {} # phi -> alloca for block in func.blocks: if len(cfg.predecessors(block)) > 1: with builder.at_front(block): for alloca in allocas: phi = builder.phi(alloca.type.base, [], []) phis[phi] = alloca return phis
def rewrite_lowlevel_constants(func, env): """ Rewrite constant pointers. """ b = Builder(func) b.position_at_beginning(func.startblock) for op in func.ops: constants = collect_constants(op) new_constants = [] for c in constants: new_constants.append(allocate_pointer_const(b, c)) substitute_args(op, constants, new_constants)
def test_exc_rewrite(self): func = Function("foo", [], types.Function(types.Void, (), False)) entry = func.new_block("entry") catch_block = func.new_block("catch") b = Builder(func) with b.at_front(entry): b.exc_setup([catch_block]) b.exc_throw(Const(StopIteration, types.Exception)) with b.at_front(catch_block): b.exc_catch([Const(Exception, types.Exception)]) local_exceptions.run(func, {}) self.assertNotIn('exc_throw', opcodes(func))
def visit_FuncDef(self, node): assert not node.param_decls self.enter_func() name = node.decl.name type = self.visit(node.decl.type) argnames = [p.name for p in node.decl.type.args.params] self.func = Function(name, argnames, type) self.func.add_block('entry') self.builder = Builder(self.func) self.builder.position_at_end(self.func.blocks[0]) self.generic_visit(node.body) self.leave_func()
def move_generator(func, consumer, empty_body): gen = consumer.generator gen.unlink() b = Builder(func) b.position_at_end(empty_body) b.emit(gen) with b.at_end(empty_body): loop_exit = determine_loop_exit(consumer.loop) b.jump(loop_exit)
def inline(func, call): """ Inline the call instruction into func. :return: { old_op : new_op } """ callee = call.args[0] callblock = call.block # assert_inlinable(func, call, callee, uses) builder = Builder(func) builder.position_before(call) inline_header, inline_exit = builder.splitblock() new_callee, valuemap = copy_function(callee, temper=func.temp) result = rewrite_return(new_callee) # Fix up arguments for funcarg, arg in zip(new_callee.args, call.args[1]): funcarg.replace_uses(arg) # Copy blocks new_blocks = list(new_callee.blocks) after = inline_header for block in new_blocks: block.parent = None func.add_block(block, after=after) after = block # Fix up wiring builder.jump(new_callee.startblock) with builder.at_end(new_callee.exitblock): builder.jump(inline_exit) stretch_exception_block(builder, callblock, new_blocks) # Fix up final result of call if result is not None: # non-void return result.unlink() result.result = call.result call.replace(result) else: call.delete() func.reset_uses() #verify(func) return valuemap
def detach_loop(func, consumer): loop, iter = consumer.loop, consumer.iter for block in loop.blocks: func.del_block(block) func.reset_uses() b = Builder(func) jump = iter.block.terminator assert jump.opcode == 'jump' and jump.args[0] == loop.head jump.delete() b.position_at_end(iter.block) _, newblock = b.splitblock(terminate=True) return newblock
class RuntimeLowering(object): """ Lower assembly Operations into runtime calls, e.g. Op("thread_start") -> Op("call", "thread_start", []) """ def __init__(self, func): self.mod = func.module self.builder = Builder(func) self.func = func def lower_ops_into_runtime(self, names, insert_decl=False): """Lower all ops listed in `names` into runtime calls""" names = set(names) for op in self.func.ops: if op.opcode in names: self.lower_into_runtime(op, insert_decl) def lower_into_runtime(self, op, insert_decl=False): """ Lower op into a runtime call. :param decl: Whether to insert an external declaration if not present """ _verify_args(op) if insert_decl and not self.mod.get_global(op.opcode): signature = types.Function(op.type, [arg.type for arg in op.args], False) self.mod.add_global( GlobalValue(op.opcode, signature, external=True)) call = self.builder.gen_call_external(op.opcode, op.args, op.result) op.replace(call)
class TestIR(unittest.TestCase): def setUp(self): self.m = from_c(source) self.f = self.m.get_function('testfunc') self.b = Builder(self.f) def test_replace(self): entry = self.f.get_block('entry') for op in entry: if op.opcode == ops.convert: r, = op.args t = self.b.add(types.Int32, [r, r]) c = self.b.convert(types.Float32, [t], result=op.result) op.replace([t, c]) break cfa.run(self.f) self.assertEqual(opcodes(self.f), ['mul', 'add', 'convert', 'ret'])
class TestInterp(unittest.TestCase): def setUp(self): self.f = from_assembly(testfunc) self.b = Builder(self.f) def test_replace(self): entry = self.f.get_block('entry') for op in entry: if op.opcode == ops.convert: r, = op.args t = self.b.add(types.Int32, [r, r], result="temp") c = self.b.convert(types.Float32, [t], result=op.result) op.replace([t, c]) break result = [(op.opcode, op.result) for op in entry] assert result == [('alloca', '0'), ('mul', 'r'), ('add', 'temp'), ('convert', '1'), ('store', '2'), ('load', '3'), ('ret', '4')]
def rewrite_raise_exc_type(func, env): """ Rewrite 'raise Exception' to 'raise Exception()' """ context = env['flypy.typing.context'] b = Builder(func) for op in func.ops: if op.opcode == 'exc_throw': [exc_type] = op.args if isinstance(exc_type, Const): ty = context[exc_type] if ty.impl == Type: # Type[Exception[]] # Generate constructor application b.position_before(op) exc_obj = b.call(ptypes.Opaque, exc_type, []) op.set_args([exc_obj]) type = ty.parameters[0] context[exc_obj] = type
def visit_FuncDef(self, node): assert not node.param_decls self.enter_func() name = node.decl.name type = self.visit(node.decl.type) if node.decl.type.args: argnames = [p.name or "" for p in node.decl.type.args.params] else: argnames = [] self.func = Function(name, argnames, type) self.func.new_block('entry') self.builder = Builder(self.func) self.builder.position_at_end(self.func.startblock) # Store arguments in stack variables for argname in argnames: self.assignvar(argname, self.func.get_arg(argname)) self.generic_visit(node.body) self.leave_func()
def split_critical_edges(func, cfg, phis): """ Split critical edges to correctly handle cycles in phis. See 2) above. """ b = Builder(func) for block in cfg.node: successors = cfg.neighbors(block) if len(successors) > 1: # More than one successor, we need to split # (Alternatively, we could move our copies into the successor block # if we were the only predecessor, but this seems simpler) # Split successors with phis new_succs = {} # old_successor -> new_successor for succ in successors: if phis[succ]: label = func.temp("split_critical") new_succ = func.new_block(label, after=block) new_succs[succ] = new_succ b.position_at_end(new_succ) b.jump(succ) # Patch our basic-block terminator to point to new blocks if new_succs: terminator = block.terminator assert terminator.opcode == 'cbranch', terminator test, truebb, falsebb = terminator.args terminator.set_args([ test, new_succs.get(truebb, truebb), new_succs.get(falsebb, falsebb) ])
def move_allocas(func, allocas): """Move all allocas to the start block""" builder = Builder(func) builder.position_at_beginning(func.startblock) for alloca in allocas: if alloca.block != func.startblock: alloca.unlink() builder.emit(alloca)
def simplify_exceptions(func, env=None): """ Rewrite exceptions emitted by the front-end: exc_end -> split block """ b = Builder(func) for op in func.ops: if op.opcode == 'exc_end': b.position_after(op) b.splitblock(terminate=True, preserve_exc=False) op.delete()
def lower_fields(func, env): b = Builder(func) opbuilder = OpBuilder() for op in func.ops: if op.opcode not in ("getfield", "setfield"): continue if op.args[0].type.is_pointer: b.position_before(op) # Load the pointer and update the argument p = op.args[0] load = b.load(p) args = [load] + op.args[1:] op.set_args(args) if op.opcode == "setfield": # Write back result b.position_after(op) op.type = load.type b.store(op, p) if op.opcode == "getfield": struct, attr = op.args newop = opbuilder.extractfield(op.type, struct, attr, result=op.result) else: struct, attr, value = op.args newop = opbuilder.insertfield(op.type, struct, attr, value, result=op.result) op.replace(newop)
def preserve_exceptions(oldblock, newblock): """ Preserve exc_setup instructions for block splits. """ from pykit.ir import Builder func = oldblock.parent b = Builder(func) b.position_at_beginning(newblock) for op in oldblock.leaders: if op.opcode == 'exc_setup': b.exc_setup(op.args[0], **op.metadata)
def from_expr(graph, expr_context, env): """ Map a Blaze expression graph to blaze AIR Parameters ---------- graph: blaze.expr.Op Expression graph expr_context: ExprContext Context of the expression ctx: ExecutionContext """ inputs = expr_context.params # ------------------------------------------------- # Types argtypes = [operand.dshape for operand in inputs] signature = types.Function(graph.dshape, argtypes, varargs=False) # ------------------------------------------------- # Setup function name = "expr" argnames = ["e%d" % i for i in range(len(inputs))] f = Function(name, argnames, signature) builder = Builder(f) builder.position_at_beginning(f.new_block('entry')) # ------------------------------------------------- # Generate function valuemap = dict( (expr, f.get_arg("e%d" % i)) for i, expr in enumerate(inputs)) _from_expr(graph, f, builder, valuemap) retval = valuemap[graph] builder.ret(retval) # Update environment with runtime arguments runtime_args = [expr_context.terms[input] for input in inputs] env['runtime.args'] = dict(zip(f.args, runtime_args)) return f
def rewrite_varargs(func, env): """ Rewrite function application with arguments that go in the varargs: def f(x, *args): ... call(f, [x, y, z]) -> call(f, [x, (y, z)]) """ b = Builder(func) caller = Caller(b, env['flypy.typing.context'], env) caller = Caller(b, env['flypy.typing.context'], env) call_flags = env['flypy.state.call_flags'] def f(context, py_func, f_env, op): f, args = op.args flags = call_flags.get(op, {}) if flags.get('varargs'): return # Retrieve any remaining arguments meant for *args flattened = flatargs(py_func, args, {}) if flattened.have_varargs: b.position_before(op) #print(py_func, flattened.varargs) # -- Build the tuple -- # result = caller.apply_constructor(EmptyTuple) for item in flattened.varargs: result = caller.apply_constructor(StaticTuple, args=[item, result]) # -- Patch callsite -- # args = list(flattened.positional) + [result] op.set_args([f, args]) jitcallmap(f, func, env)
def splitblock(block, trailing, name=None, terminate=False, preserve_exc=True): """Split the current block, returning (old_block, new_block)""" func = block.parent if block.is_terminated(): successors = deduce_successors(block) else: successors = [] # ------------------------------------------------- # Sanity check # Allow splitting only after leaders and before terminator # TODO: error check # ------------------------------------------------- # Split blockname = name or func.temp('Block') newblock = func.new_block(blockname, after=block) # ------------------------------------------------- # Move ops after the split to new block for op in trailing: op.unlink() newblock.extend(trailing) if terminate and not block.is_terminated(): # Terminate b = Builder(func) b.position_at_end(block) b.jump(newblock) # Update phis and preserve exception blocks patch_phis(block, newblock, successors) if preserve_exc: preserve_exceptions(block, newblock) return block, newblock
def rewrite_getattr(func, env): """ Resolve missing attributes through __getattr__ """ context = env['flypy.typing.context'] b = OpBuilder() builder = Builder(func) for op in func.ops: if op.opcode == 'getfield': value, attr = op.args obj_type = context[value] attr_type = types.String[()] if attr not in obj_type.fields and attr not in obj_type.layout: assert '__getattr__' in obj_type.fields op.set_args([value, '__getattr__']) # Construct attribute string attr_string = OConst(attr) # Retrieve __getattr__ function and type getattr_func, func_type, restype = infer_getattr( obj_type, op, env) # call(getfield(obj, '__getattr__'), ['attr']) call = b.call(op.type, op, [attr_string]) op.replace_uses(call) builder.position_after(op) builder.emit(call) # Update context context[op] = func_type context[attr_string] = attr_type context[call] = restype
def __init__(self, func): self.mod = func.module self.builder = Builder(func) self.func = func
def run(func, env=None, return_block=None): """ Rewrite 'ret' operations into jumps to a return block and assignments to a return variable. """ b = Builder(func) return_block = return_block or func.new_block("pykit.return") # Allocate return variable if not func.type.restype.is_void: with b.at_front(func.startblock): return_var = b.alloca(types.Pointer(func.type.restype), []) b.store(Undef(func.type.restype), return_var) else: return_var = None # Repace 'ret' instructions with jumps and assignments for op in func.ops: if op.opcode == "ret": b.position_after(op) if return_var: b.store(op.args[0], return_var) b.jump(return_block) op.delete() with b.at_end(return_block): if return_var: result = b.load(return_var.type.base, [return_var]) else: result = None b.ret(result)
def consume_yields(func, consumer, generator_func, valuemap): b = Builder(func) copier = lambda x: x loop = consumer.loop inlined_values = set(valuemap.values()) for block in func.blocks: if block in inlined_values: for op in block.ops: if op.opcode == 'yield': # -- Replace 'yield' by the loop body -- # b.position_after(op) _, resume = b.splitblock() # Copy blocks blocks = [copier(block) for block in loop.blocks] # Insert blocks prev = op.block for block in blocks: func.add_block(block, after=prev) prev = block # Fix wiring b.jump(blocks[0]) b.position_at_end(blocks[-1]) b.jump(resume) # We just introduced a bunch of copied blocks func.reset_uses() # Update phis with new predecessor b.replace_predecessor(loop.tail, op.block, loop.head) b.replace_predecessor(loop.tail, op.block, loop.head) # Replace next() by value produced by yield value = op.args[0] consumer.next.replace_uses(value) op.delete() # We don't need these anymore consumer.next.delete()
def setUp(self): self.m = from_c(source) self.f = self.m.get_function('testfunc') self.b = Builder(self.f)
class PykitIRVisitor(c_ast.NodeVisitor): """ Map pykit IR in the form of polymorphic C to in-memory pykit IR. int function(float x) { int i = 0; /* I am a comment */ while (i < 10) { /*: { "unroll": true } :*/ x = call_external("sqrt", x * x); } return (int) x; } Attributes: """ in_function = False def __init__(self, type_env=None): self.mod = Module() self.type_env = type_env or {} self.func = None self.builder = None self.local_vars = None self.allocas = None self.global_vars = {} self.functions = {} # ______________________________________________________________________ @property def vars(self): if self.in_function: return self.local_vars else: return self.global_vars def enter_func(self): self.in_function = True self.local_vars = {} self.allocas = {} def leave_func(self): self.in_function = False self.mod.add_function(self.func) self.local_vars = None self.allocas = None self.func = None def visit(self, node, type=None): """ Visit a node. :type: Whether we have a type for this opcode, which is an LHS type or a cast. E.g.: (Int) call(...) // cast result = call(...) // assmnt, assuming 'result' is declared result = call(..., call(...)) // second 'call' isn't typed """ self.type = type method = 'visit_' + node.__class__.__name__ visitor = getattr(self, method, self.generic_visit) # if visitor is None: # raise SyntaxError( # "Node %s not supported in %s:%s" % (node, node.coord.file, # node.coord.line)) return visitor(node) def visitif(self, node): if node: return self.visit(node) def visits(self, node): return list(map(self.visit, node)) # ______________________________________________________________________ def alloca(self, varname): if varname not in self.allocas: # Allocate variable with alloca with self.builder.at_front(self.func.startblock): type = types.Pointer(self.local_vars[varname]) result = self.func.temp(varname) self.allocas[varname] = self.builder.alloca(type, [], result) return self.allocas[varname] def assignvar(self, varname, rhs): self.builder.store(rhs, self.alloca(varname)) def assign(self, varname, rhs): if self.in_function: # Local variable type = self.local_vars[varname] self.assignvar(varname, self.visit(rhs, type=type)) else: # Global variable type = self.global_vars[varname] self.mod.add_global( GlobalValue(varname, type=self.type, value=self.visit(rhs, type=type))) # ______________________________________________________________________ def visit_Decl(self, decl): if decl.name in self.vars: error(decl, "Var '%s' already declared!" % (decl.name, )) type = self.visit(decl.type) self.vars[decl.name] = type if decl.init: self.assign(decl.name, decl.init) elif not self.in_function: extern = decl.storage == 'external' self.mod.add_global(GlobalValue(decl.name, type, external=extern)) return type def visit_TypeDecl(self, decl): return self.visit(decl.type) visit_Typename = visit_TypeDecl def visit_PtrDecl(self, decl): return types.Pointer(self.visit(decl.type.type)) def visit_FuncDecl(self, decl): if decl.args: params = self.visits(decl.args.params) else: params = [] return types.Function(self.visit(decl.type), params) def visit_IdentifierType(self, node): name, = node.names return self.type_env[name] def visit_Typedef(self, node): if node.name in ("Type", "_list"): type = self.type_env[node.name] else: type = self.visit(node.type) if type == types.Type: type = getattr(types, node.name) self.type_env[node.name] = type return type def visit_Template(self, node): left = self.visit(node.left) subtypes = self.visits(node.right) if left is list: return list(subtypes) else: assert issubclass(left, types.Type) subtypes = self.visits(node.right) return left(*subtypes) # ______________________________________________________________________ def visit_FuncDef(self, node): assert not node.param_decls self.enter_func() name = node.decl.name type = self.visit(node.decl.type) if node.decl.type.args: argnames = [p.name or "" for p in node.decl.type.args.params] else: argnames = [] self.func = Function(name, argnames, type) self.func.new_block('entry') self.builder = Builder(self.func) self.builder.position_at_end(self.func.startblock) # Store arguments in stack variables for argname in argnames: self.assignvar(argname, self.func.get_arg(argname)) self.generic_visit(node.body) self.leave_func() # ______________________________________________________________________ def visit_FuncCall(self, node): type = self.type opcode = node.name.name args = self.visits(node.args.exprs) if node.args else [] if opcode == "list": return args elif not type and not ops.is_void(opcode): error( node, "Expected a type for sub-expression " "(add a cast or assignment)") elif not hasattr(self.builder, opcode): if opcode in self.mod.functions: return self.builder.call(type, [self.mod.get_function(opcode), args]) error(node, "No opcode %s" % (opcode, )) buildop = getattr(self.builder, opcode) if ops.is_void(opcode): return buildop(*args) else: return buildop(type or "Unset", args) def visit_ID(self, node): if self.in_function: if node.name in self.local_vars: result = self.alloca(node.name) return self.builder.load(result.type.base, [result]) global_val = (self.mod.get_function(node.name) or self.mod.get_global(node.name)) if not global_val: error(node, "Not a local or global: %r" % node.name) return global_val def visit_Cast(self, node): type = self.visit(node.to_type) if isinstance(node.expr, c_ast.FuncCall): op = self.visit(node.expr, type=type) op.type = type return op else: result = self.visit(node.expr) if result.type == type: return result return self.builder.convert(type, [result]) def visit_Assignment(self, node): if node.op != '=': error(node, "Only assignment with '=' is supported") if not isinstance(node.lvalue, c_ast.ID): error(node, "Canot only assign to a name") self.assign(node.lvalue.name, node.rvalue) def visit_Constant(self, node): type = self.type_env[node.type] const = types.convert(node.value, types.resolve_typedef(type)) if isinstance(const, basestring): const = const[1:-1] # slice away quotes return Const(const) def visit_UnaryOp(self, node): op = defs.unary_defs[node.op] buildop = getattr(self.builder, op) arg = self.visit(node.expr) type = self.type or arg.type return buildop(type, [arg]) def visit_BinaryOp(self, node): op = binary_defs[node.op] buildop = getattr(self.builder, op) left, right = self.visits([node.left, node.right]) type = self.type if not type: l, r = map(types.resolve_typedef, [left.type, right.type]) assert l == r, (l, r) if node.op in defs.compare_defs: type = types.Bool return buildop(type or left.type, [left, right]) def visit_If(self, node): cond = self.visit(node.cond) ifpos, elsepos, exit_block = self.builder.ifelse(cond) with ifpos: self.visit(node.iftrue) self.builder.jump(exit_block) with elsepos: if node.iffalse: self.visit(node.iffalse) self.builder.jump(exit_block) self.builder.position_at_end(exit_block) def _loop(self, init, cond, next, body): _, exit_block = self.builder.splitblock(self.func.temp("exit")) _, body_block = self.builder.splitblock(self.func.temp("body")) _, cond_block = self.builder.splitblock(self.func.temp("cond")) self.visitif(init) self.builder.jump(cond_block) with self.builder.at_front(cond_block): cond = self.visit(cond, type=types.Bool) self.builder.cbranch(cond, body_block, exit_block) with self.builder.at_front(body_block): self.visit(body) self.visitif(next) bb = self.builder.basic_block if not bb.tail or not ops.is_terminator(bb.tail.opcode): self.builder.jump(cond_block) self.builder.position_at_end(exit_block) def visit_While(self, node): self._loop(None, node.cond, None, node.stmt) def visit_For(self, node): # avoid silly 2to3 rewrite to 'node.__next__' next = getattr(node, 'next') self._loop(node.init, node.cond, next, node.stmt) def visit_Return(self, node): b = self.builder value = self.visit(node.expr) t = self.func.temp b.ret(b.convert(self.func.type.restype, [value]))