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 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 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 rewrite_obj_return(func, env): """ Handle returning stack-allocated objects. """ if should_skip(env): return context = env['flypy.typing.context'] restype = env['flypy.typing.restype'] envs = env['flypy.state.envs'] builder = Builder(func) stack_alloc = representation.byref(restype) if stack_alloc: out = func.add_arg(func.temp("out"), opaque_t) context[out] = Pointer[restype] func.type = types.Function(types.Void, func.type.argtypes, False) for arg in func.args: arg.type = opaque_t func.type = types.Function(func.type.restype, (opaque_t, ) * len(func.args), False) is_generator = env['flypy.state.generator'] for op in func.ops: if (op.opcode == 'ret' and op.args[0] is not None and stack_alloc and not is_generator): # ret val => # store (load val) out ; ret void [val] = op.args builder.position_before(op) newval = builder.load(val) builder.store(newval, out) op.set_args([None]) # Update context context[newval] = StackVar[context[val]] elif op.opcode == 'call' and op.type != types.Void: # result = call(f, ...) => # alloca result ; call(f, ..., &result) ty = context[op] if conversion.byref(ty): f, args = op.args if not is_flypy_cc(f) or should_skip(envs[f]): continue builder.position_before(op) retval = builder.alloca(opaque_t) builder.position_after(op) op.replace_uses(retval) newargs = args + [retval] op.set_args([f, newargs]) # Update context context[retval] = context[op] context[op] = void
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 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) else: result = None b.ret(result)
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 rewrite_obj_return(func, env): """ Handle returning stack-allocated objects. """ if should_skip(env): return context = env['flypy.typing.context'] restype = env['flypy.typing.restype'] envs = env['flypy.state.envs'] builder = Builder(func) stack_alloc = representation.byref(restype) if stack_alloc: out = func.add_arg(func.temp("out"), opaque_t) context[out] = Pointer[restype] func.type = types.Function(types.Void, func.type.argtypes, False) for arg in func.args: arg.type = opaque_t func.type = types.Function(func.type.restype, (opaque_t,) * len(func.args), False) is_generator = env['flypy.state.generator'] for op in func.ops: if (op.opcode == 'ret' and op.args[0] is not None and stack_alloc and not is_generator): # ret val => # store (load val) out ; ret void [val] = op.args builder.position_before(op) newval = builder.load(val) builder.store(newval, out) op.set_args([None]) # Update context context[newval] = StackVar[context[val]] elif op.opcode == 'call' and op.type != types.Void: # result = call(f, ...) => # alloca result ; call(f, ..., &result) ty = context[op] if conversion.byref(ty): f, args = op.args if not is_flypy_cc(f) or should_skip(envs[f]): continue builder.position_before(op) retval = builder.alloca(opaque_t) builder.position_after(op) op.replace_uses(retval) newargs = args + [retval] op.set_args([f, newargs]) # Update context context[retval] = context[op] context[op] = void
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()
class TestBuilder(unittest.TestCase): def setUp(self): self.f = Function("testfunc", ['a'], types.Function(types.Float32, [types.Int32])) self.b = Builder(self.f) self.b.position_at_end(self.f.add_block('entry')) self.a = self.f.get_arg('a') def test_basic_builder(self): v = self.b.alloca(types.Pointer(types.Float32), []) result = self.b.mul(types.Int32, [self.a, self.a], result='r') c = self.b.convert(types.Float32, [result]) self.b.store(c, v) val = self.b.load(types.Float32, [v]) self.b.ret(val) # print(string(self.f)) self.assertEqual(str(self.f).strip(), basic_expected) def test_splitblock(self): old, new = self.b.splitblock('newblock') with self.b.at_front(old): self.b.add(types.Int32, [self.a, self.a]) with self.b.at_end(new): self.b.div(types.Int32, [self.a, self.a]) # print(string(self.f)) self.assertEqual(split_expected, string(self.f)) def test_loop_builder(self): square = self.b.mul(types.Int32, [self.a, self.a]) c = self.b.convert(types.Float32, [square]) self.b.position_after(square) _, block = self.b.splitblock('start', terminate=True) self.b.position_at_end(block) const = partial(Const, type=types.Int32) cond, body, exit = self.b.gen_loop(const(5), const(10), const(2)) with self.b.at_front(body): self.b.print_(c) with self.b.at_end(exit): self.b.ret(c) # print(string(self.f)) # verify.verify(self.f) # self.assertEqual(loop_expected, string(self.f)) # TestBuilder('test_basic_builder').debug() # TestBuilder('test_splitblock').debug() # TestBuilder('test_loop_builder').debug() # unittest.main()
def lower_fields(func, env=None): b = Builder(func) for op in func.ops: if op.opcode in ("getfield", "setfield") and op.args[0].type.is_pointer: b.position_before(op) p = op.args[0] load = b.load(p.type.base, [p]) args = [load] + op.args[1:] op.set_args(args) if op.opcode == "setfield": b.position_after(op) b.store(op, p)
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 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()
class TestBuilder(unittest.TestCase): def setUp(self): self.f = Function("testfunc", ['a'], types.Function(types.Float32, [types.Int32])) self.b = Builder(self.f) self.b.position_at_end(self.f.new_block('entry')) self.a = self.f.get_arg('a') def test_basic_builder(self): v = self.b.alloca(types.Pointer(types.Float32), []) result = self.b.mul(types.Int32, [self.a, self.a], result='r') c = self.b.convert(types.Float32, [result]) self.b.store(c, v) val = self.b.load(types.Float32, [v]) self.b.ret(val) # print(string(self.f)) assert interp.run(self.f, args=[10]) == 100 def test_splitblock(self): old, new = self.b.splitblock('newblock') with self.b.at_front(old): self.b.add(types.Int32, [self.a, self.a]) with self.b.at_end(new): self.b.div(types.Int32, [self.a, self.a]) self.assertEqual(opcodes(self.f), ['add', 'div']) def test_loop_builder(self): square = self.b.mul(types.Int32, [self.a, self.a]) c = self.b.convert(types.Float32, [square]) self.b.position_after(square) _, block = self.b.splitblock('start', terminate=True) self.b.position_at_end(block) const = partial(Const, type=types.Int32) cond, body, exit = self.b.gen_loop(const(5), const(10), const(2)) with self.b.at_front(body): self.b.print(c) with self.b.at_end(exit): self.b.ret(c) self.assertEqual(interp.run(self.f, args=[10]), 100.0)
def generate_copies(func, phis): """ Emit stores to stack variables in predecessor blocks. """ builder = Builder(func) vars = {} loads = {} # Allocate a stack variable for each phi builder.position_at_beginning(func.startblock) for block in phis: for phi in phis[block]: vars[phi] = builder.alloca(types.Pointer(phi.type)) # Generate loads in blocks containing the phis for block in phis: leaders = list(block.leaders) last_leader = leaders[-1] if leaders else block.head builder.position_after(last_leader) for phi in phis[block]: loads[phi] = builder.load(vars[phi]) # Generate copies (store to stack variables) for block in phis: for phi in phis[block]: preds, args = phi.args var = vars[phi] phi_args = [loads.get(arg, arg) for arg in args] for pred, arg in zip(preds, phi_args): builder.position_before(pred.terminator) builder.store(arg, var) # Replace phis for block in phis: for phi in phis[block]: phi.replace_uses(loads[phi]) phi.delete() return vars, loads
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
class TestBuilder(unittest.TestCase): def setUp(self): self.f = Function("testfunc", ['a'], types.Function(types.Float32, [types.Int32], False)) self.b = Builder(self.f) self.b.position_at_end(self.f.new_block('entry')) self.a = self.f.get_arg('a') def test_basic_builder(self): v = self.b.alloca(types.Pointer(types.Float32)) result = self.b.mul(self.a, self.a, result='r') c = self.b.convert(types.Float32, result) self.b.store(c, v) val = self.b.load(v) self.b.ret(val) # print(string(self.f)) assert interp.run(self.f, args=[10]) == 100 def test_splitblock(self): old, new = self.b.splitblock('newblock') with self.b.at_front(old): self.b.add(self.a, self.a) with self.b.at_end(new): self.b.div(self.a, self.a) self.assertEqual(opcodes(self.f), ['add', 'div']) def test_loop_builder(self): square = self.b.mul(self.a, self.a) c = self.b.convert(types.Float32, square) self.b.position_after(square) _, block = self.b.splitblock('start', terminate=True) self.b.position_at_end(block) const = partial(Const, type=types.Int32) cond, body, exit = self.b.gen_loop(const(5), const(10), const(2)) with self.b.at_front(body): self.b.print(c) with self.b.at_end(exit): self.b.ret(c) self.assertEqual(interp.run(self.f, args=[10]), 100.0) def test_splitblock_preserve_phis(self): """ block1: %0 = mul a a jump(newblock) newblock: %1 = phi([block1], [%0]) ret %1 """ square = self.b.mul(self.a, self.a) old, new = self.b.splitblock('newblock', terminate=True) with self.b.at_front(new): phi = self.b.phi(types.Int32, [self.f.startblock], [square]) self.b.ret(phi) # Now split block1 self.b.position_after(square) block1, split = self.b.splitblock(terminate=True) phi, ret = new.ops blocks, values = phi.args self.assertEqual(blocks, [split])