def op_kernel(self, op): if op not in self.jitted: return op uses = self.func.uses[op] if all(u in self.trees for u in uses): # All our consumers know about us and have us as an argument # in their tree! Delete this op, only the root will perform a # rewrite. self.delete_later.append(op) elif any(u in self.trees for u in uses): # Some consumers have us as a node, but others don't. This # forms a ckernel boundary, so we need to detach ourselves! raise NotImplementedError else: # No consumer has us as an internal node in the kernel tree, we # are a kerneltree root tree = self.trees[op] # out_rank = len(op.type.shape) # tree = tree.adapt(out_rank, llvm_array.C_CONTIGUOUS) ckernel_deferred = tree.make_ckernel_deferred(op.type) # Flatten the tree of args, removing duplicates just # as the kernel tree does. args = [] for arg in op.args[1:]: # Skip op.args[0], the kernel string name for ir_arg, kt_arg in self.arguments[arg]: if ir_arg not in args: args.append(ir_arg) new_op = Op('ckernel', op.type, [ckernel_deferred, args], op.result) new_op.add_metadata({'rank': 0, 'parallel': True}) return new_op
def _from_expr(expr, f, builder, values): if expr.opcode == 'array': result = values[expr] else: # ------------------------------------------------- # Construct args # This is purely for IR readability name = qualified_name(expr.metadata['overload'].func) args = [_from_expr(arg, f, builder, values) for arg in expr.args] args = [Const(name)] + args # ------------------------------------------------- # Construct Op result = Op("kernel", expr.dshape, args) # Copy metadata verbatim assert 'kernel' in expr.metadata assert 'overload' in expr.metadata result.add_metadata(expr.metadata) # ------------------------------------------------- # Emit Op in code stream builder.emit(result) values[expr] = result return result
def op_kernel(self, op): if self.strategies[op] != 'ckernel': return function = op.metadata['kernel'] overload = op.metadata['overload'] func = overload.func polysig = overload.sig monosig = overload.resolved_sig argtypes = datashape.coretypes.Tuple(monosig.argtypes) try: overload = function.best_match('ckernel', argtypes) except datashape.CoercionError: return op impl = overload.func assert monosig == overload.resolved_sig, (monosig, overload.resolved_sig) new_op = Op('ckernel', op.type, [impl, op.args[1:]], op.result) new_op.add_metadata({'rank': 0, 'parallel': True}) return new_op
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 op_kernel(self, op): if op not in self.trees: return op uses = self.func.uses[op] if all(u in self.trees for u in uses): # All our consumers know about us and have us as an argument # in their tree! Delete this op, only the root will perform a # rewrite. self.delete_later.append(op) elif any(u in self.trees for u in uses): # Some consumers have us as a node, but others don't. This # forms a ckernel boundary, so we need to detach ourselves! raise NotImplementedError else: # No consumer has us as an internal node in the kernel tree, we # are a kerneltree root tree = self.trees[op] # out_rank = len(op.type.shape) # tree = tree.adapt(out_rank, llvm_array.C_CONTIGUOUS) unbound_ckernel = tree.make_unbound_ckernel(strided=False) # Skip kernel string name, first arg to 'kernel' Operations args = [ir_arg for arg in op.args[1:] for ir_arg, kt_arg in self.arguments[arg]] new_op = Op('ckernel', op.type, [unbound_ckernel, args], op.result) new_op.add_metadata({'rank': 0, 'parallel': True}) return new_op
def _process(self, ty, args=None, result=None, **metadata): if args is None: args = [] assert ty is not None assert isinstance(args, list), args assert not any(arg is None for arg in flatten(args)), args result = Op(op, ty, args, result) if metadata: result.add_metadata(metadata) self._insert_op(result) return result
def op_kernel(self, op): function = op.metadata['kernel'] overload = op.metadata['overload'] func = overload.func polysig = overload.sig monosig = overload.resolved_sig impls = function.find_impls(func, polysig, 'ckernel') if impls: [impl] = impls new_op = Op('ckernel', op.type, [impl, op.args[1:]], op.result) new_op.add_metadata({'rank': 0, 'parallel': True}) return new_op return op
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: func = self.mod.get_function(opcode) return self.builder.call(type, func, args) # error(node, "No opcode %s" % (opcode,)) op = Op(opcode, type or types.Opaque, args) self.builder.emit(op) return op buildop = getattr(self.builder, opcode) if ops.is_void(opcode): return buildop(*args) else: return buildop(type or types.Opaque, *args)
def copy_function(func, temper=None, module=None): """Copy a Function. `temper` may be given to""" temper = temper or make_temper() f = Function(func.name, list(func.argnames), func.type, temper=temper) valuemap = {} lookup = partial(_lookup, module or func.module, f, valuemap) ### Construct new Blocks for block in func.blocks: new_block = Block(temper(block.name), f) valuemap[block] = new_block f.add_block(new_block) ### Construct new Operations for block in func.blocks: new_block = valuemap[block] for op in block.ops: new_op = Op(op.opcode, op.type, nestedmap(lookup, op.args), result=temper(op.result), parent=new_block) # assert new_op.result != op.result valuemap[op] = new_op new_block.append(new_op) return f
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 ret(self, obj0, **kwds): returnType = types.Void register = kwds.pop('result', None) op = Op('ret', returnType, [obj0], register, metadata=kwds) if config.op_verify: verify_op_syntax(op) self._insert_op(op) return op
def alloca(self, returnType, obj0, **kwds): assert returnType is not None register = kwds.pop('result', None) op = Op('alloca', returnType, [obj0], register, metadata=kwds) if config.op_verify: verify_op_syntax(op) self._insert_op(op) return op
def bitcast(self, returnType, value0, **kwds): assert isinstance(value0, Value) assert returnType is not None register = kwds.pop('result', None) op = Op('bitcast', returnType, [value0], register, metadata=kwds) if config.op_verify: verify_op_syntax(op) self._insert_op(op) return op
def exc_catch(self, lst0, **kwds): assert isinstance(lst0, list) returnType = types.Void register = kwds.pop('result', None) op = Op('exc_catch', returnType, [lst0], register, metadata=kwds) if config.op_verify: verify_op_syntax(op) self._insert_op(op) return op
def print(self, value0, **kwds): assert isinstance(value0, Value) returnType = types.Void register = kwds.pop('result', None) op = Op('print', returnType, [value0], register, metadata=kwds) if config.op_verify: verify_op_syntax(op) self._insert_op(op) return op
def call_math(self, returnType, obj0, lst0, **kwds): assert isinstance(lst0, list) assert returnType is not None register = kwds.pop('result', None) op = Op('call_math', returnType, [obj0, lst0], register, metadata=kwds) if config.op_verify: verify_op_syntax(op) self._insert_op(op) return op
def test_combinator(self): visitor = SampleVisitor() def op_blah(op): visitor.recorded.append(op.opcode) comb = combine(visitor, {'op_blah': op_blah}) with self.b.at_front(self.entry): self.b.emit(Op('blah', None, [])) visit(comb, self.f) self.eq(visitor.recorded, ['blah', 'mul'])
def phi(self, returnType, lst0, lst1, **kwds): assert isinstance(lst0, list) assert isinstance(lst1, list) assert returnType is not None register = kwds.pop('result', None) op = Op('phi', returnType, [lst0, lst1], register, metadata=kwds) if config.op_verify: verify_op_syntax(op) self._insert_op(op) return op
def setfield(self, value0, obj0, value1, **kwds): assert isinstance(value0, Value) assert isinstance(value1, Value) returnType = types.Void register = kwds.pop('result', None) op = Op('setfield', returnType, [value0, obj0, value1], register, metadata=kwds) if config.op_verify: verify_op_syntax(op) self._insert_op(op) return op
def op_kernel(self, op): function = op.metadata['kernel'] overload = op.metadata['overload'] func = overload.func polysig = overload.sig monosig = overload.resolved_sig argtypes = monosig.argtypes if function.matches('ckernel', argtypes): overload = function.best_match('ckernel', argtypes) impl = overload.func assert monosig == overload.resolved_sig, (monosig, overload.resolved_sig) new_op = Op('ckernel', op.type, [impl, op.args[1:]], op.result) new_op.add_metadata({'rank': 0, 'parallel': True}) return new_op return op
def shufflevector(self, returnType, value0, value1, value2, **kwds): assert isinstance(value0, Value) assert isinstance(value1, Value) assert isinstance(value2, Value) assert returnType is not None register = kwds.pop('result', None) op = Op('shufflevector', returnType, [value0, value1, value2], register, metadata=kwds) if config.op_verify: verify_op_syntax(op) self._insert_op(op) return op
def _build_op(type, args): if len(args) == 9: argnames = [] _, dest, _, _, type, _, opname, _, _ = args else: _, dest, _, _, type, _, opname, _, argnames, _ = args if not isinstance(argnames, list): argnames = [argnames] return Op(opname, type, argnames, dest)
def run(func, env): from flypy.runtime.special import debugprint for op in func.ops: if op.opcode == 'call': f, args = op.args if isinstance(f, Const) and f.const == debugprint: env["flypy.state.have_debugprint"] = True op.replace( Op("debugprint", ptypes.Void, args, result=op.result)) [expr] = args debugprint_expr(expr, "frontend")
def run(func, env): storage = env['storage'] for op in func.ops: if op.opcode == 'ckernel': # Build an executable chunked or in-memory pykernel if storage is None: pykernel = op_ckernel(op) else: pykernel = op_ckernel_chunked(op) newop = Op('pykernel', op.type, [pykernel, op.args[1]], op.result) op.replace(newop)
def op_kernel(self, op): if self.strategies[op] != 'ckernel': return function = op.metadata['kernel'] overload = op.metadata['overload'] func = overload.func polysig = overload.sig monosig = overload.resolved_sig argtypes = monosig.argtypes if function.matches('ckernel', argtypes): overload = function.best_match('ckernel', argtypes) impl = overload.func assert monosig == overload.resolved_sig, (monosig, overload.resolved_sig) new_op = Op('ckernel', op.type, [impl, op.args[1:]], op.result) new_op.add_metadata({'rank': 0, 'parallel': True}) return new_op return op
def copy_function(func, temper=None, module=None): """Copy a Function. `temper` may be given to""" temper = temper or make_temper() f = Function(func.name, list(func.argnames), func.type, temper=temper) valuemap = {} lookup = partial(_lookup, module or func.module, f, valuemap) ### Construct new Blocks for block in func.blocks: new_block = Block(temper(block.name), f) valuemap[block] = new_block f.add_block(new_block) ### Construct new Operations for block in func.blocks: new_block = valuemap[block] for op in block.ops: if op.opcode == 'phi': # Phi nodes may be circular, or may simply precede some of # their arguments args = [] else: args = nestedmap(lookup, op.args) new_op = Op(op.opcode, op.type, args, result=temper(op.result), parent=new_block) new_op.add_metadata(op.metadata) # assert new_op.result != op.result valuemap[op] = new_op new_block.append(new_op) for old_op in func.ops: if old_op.opcode == 'phi': new_op = valuemap[old_op] new_op.set_args(nestedmap(lookup, old_op.args)) return f
def assemble_kernels(func, env, pykernels, strategy): """ Transforms kernel ops to pykernel ops, for execution by a simple interpreter. Arguments ========= pykernels: { Op: py_func } python applyable kernels that accept blaze arrays strategy: str the strategy for which we are applying the transform """ strategies = env['strategies'] for op in func.ops: if op.opcode == 'kernel' and strategies[op] == strategy: pykernel = pykernels[op] op.replace( Op('pykernel', op.type, [pykernel, op.args[1:]], op.result))
def apply_result(func, cfg, deadblocks, cells): """ Apply the result of the SCCP analysis: - replace ops with constants - remove unreachable code blocks """ for op in func.ops: if isconst(cells[op]): op.replace_uses(cells[op]) op.delete() if op.opcode == 'cbranch' and isconst(cells[op.args[0]]): test = cells[op.args[0]].const blocks = {True: op.args[1], False: op.args[2] } target = blocks[test] other = blocks[not test] cfg.remove_edge(op.block, other) op.replace(Op("jump", types.Void, [target], op.result)) cfa.delete_blocks(func, cfg, deadblocks)
def copy_function(func, temper=None, module=None): """Copy a Function. `temper` may be given to""" temper = temper or make_temper() f = Function(func.name, list(func.argnames), func.type, temper=temper) valuemap = {} lookup = partial(_lookup, module or func.module, f, valuemap) ### Construct new Blocks for block in func.blocks: new_block = f.new_block(block.name) valuemap[block] = new_block ### Construct new Operations for block in func.blocks: new_block = valuemap[block] for op in block.ops: if op.opcode == 'phi': # Phi nodes may be circular, or may simply precede some of # their arguments args = [] else: args = nestedmap(lookup, op.args) new_op = Op(op.opcode, op.type, args, result=temper(op.result), parent=new_block) new_op.add_metadata(op.metadata) # assert new_op.result != op.result valuemap[op] = new_op new_block.append(new_op) for old_op in func.ops: if old_op.opcode == 'phi': new_op = valuemap[old_op] new_op.set_args(nestedmap(lookup, old_op.args)) return f, valuemap
def newop(opcode, args): return Op(opcode, types.Opaque, args)
def insert(self, opcode, *args): type = types.Void if ops.is_void(opcode) else types.Opaque op = Op(opcode, type, list(args)) op.add_metadata({'lineno': self.lineno}) self.builder.emit(op) return op
def rewrite_sql(func, env): """ Generate SQL queries for each SQL op and assemble them into one big query which we rewrite to python kernels. """ strategies = env['strategies'] # op -> strategy (e.g. 'sql') impls = env['kernel.overloads'] # (op, strategy) -> Overload roots = env['roots'] # Backend boundaries: { Op } args = env['runtime.args'] # FuncArg -> blaze.Array conns = env['sql.conns'] # Op -> SQL Connection rewrite = set() # ops to rewrite to sql kernels delete = set() # ops to delete queries = {} # op -> query (str) leafs = {} # op -> set of SQL leafs # Extract table names and insert in queries for arg in func.args: if strategies[arg] == 'sql': arr = args[arg] sql_ddesc = arr._data if isinstance(sql_ddesc, DyNDDataDescriptor): # Extract scalar value from blaze array assert not sql_ddesc.dshape.shape # Do something better here query = str(sql_ddesc.dynd_arr()) else: table = Table(sql_ddesc.col.table) query = Column(table, sql_ddesc.col.colname) queries[arg] = query leafs[arg] = [arg] # print(func) # print(strategies) # Generate SQL queries for each op for op in func.ops: if op.opcode == "kernel" and strategies[op] == 'sql': query_gen, signature = impls[op, 'sql'] args = op.args[1:] inputs = [queries[arg] for arg in args] query = query_gen(*inputs) queries[op] = query if args[0] in conns: conns[op] = conns[args[0]] leafs[op] = [leaf for arg in args for leaf in leafs[arg]] elif op.opcode == 'convert': uses = func.uses[op] if all(strategies[use] == 'sql' for use in uses): arg = op.args[0] query = queries[arg] queries[op] = query if arg in conns: conns[op] = conns[arg] leafs[op] = list(leafs[arg]) else: continue else: continue if op in roots: rewrite.add(op) else: delete.add(op) # Rewrite sql kernels to python kernels for op in rewrite: query = queries[op] pykernel = sql_to_pykernel(query, op, env) newop = Op('pykernel', op.type, [pykernel, leafs[op]], op.result) op.replace(newop) # Delete remaining unnecessary ops func.delete_all(delete)
def _process(self, ty, args, result=None): assert ty is not None # args = nestedmap(make_arg, args) result = Op(op, ty, args, result) self._insert_op(result) return result