From 066480fbb898ca63a60aacf830086b4d3d07f5d6 Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Wed, 1 Aug 2012 19:02:58 -0700 Subject: [PATCH 01/57] Add parallel-vectorize prototype. Add llvm_cbuilder. --- llvm_cbuilder/__init__.py | 2 + llvm_cbuilder/builder.py | 515 ++++++++++++++++++++++++++++++++++++ llvm_cbuilder/executor.py | 50 ++++ llvm_cbuilder/shortnames.py | 17 ++ tests/test_isprime.py | 120 +++++++++ tests/test_pthread.py | 79 ++++++ 6 files changed, 783 insertions(+) create mode 100644 llvm_cbuilder/__init__.py create mode 100644 llvm_cbuilder/builder.py create mode 100644 llvm_cbuilder/executor.py create mode 100644 llvm_cbuilder/shortnames.py create mode 100644 tests/test_isprime.py create mode 100644 tests/test_pthread.py diff --git a/llvm_cbuilder/__init__.py b/llvm_cbuilder/__init__.py new file mode 100644 index 0000000..e623129 --- /dev/null +++ b/llvm_cbuilder/__init__.py @@ -0,0 +1,2 @@ +from .builder import CBuilder +from .executor import CExecutor diff --git a/llvm_cbuilder/builder.py b/llvm_cbuilder/builder.py new file mode 100644 index 0000000..ee866df --- /dev/null +++ b/llvm_cbuilder/builder.py @@ -0,0 +1,515 @@ +# +# TODO: Add support for vector. +# + +import contextlib +import llvm.core as lc + + +def _is_int(ty): + return isinstance(ty, lc.IntegerType) + +def _is_real(ty): + tys = [ lc.Type.float(), + lc.Type.double(), + lc.Type.x86_fp80(), + lc.Type.fp128(), + lc.Type.ppc_fp128() ] + return any(ty == x for x in tys) + +def _is_pointer(ty): + return isinstance(ty, lc.PointerType) + +def _is_block_terminated(bb): + instrs = bb.instructions + return len(instrs) > 0 and instrs[-1].is_terminator + +@contextlib.contextmanager +def _change_block_temporarily(builder, bb): + origbb = builder.basic_block + builder.position_at_end(bb) + yield + builder.position_at_end(origbb) + +class _IfElse(object): + def __init__(self, parent, cond): + self.parent = parent + self.cond = cond + self._to_close = [] + + @contextlib.contextmanager + def then(self): + self._bbif = self.parent.function.append_basic_block('if.then') + self._bbelse = self.parent.function.append_basic_block('if.else') + + builder = self.parent.builder + builder.cbranch(self.cond.value, self._bbif, self._bbelse) + + builder.position_at_end(self._bbif) + + yield + + self._to_close.extend([self._bbif, self._bbelse]) + + @contextlib.contextmanager + def otherwise(self): + self.parent.builder.position_at_end(self._bbelse) + yield + + def close(self): + bbend = self.parent.function.append_basic_block('if.end') + builder = self.parent.builder + for bb in self._to_close: + if not _is_block_terminated(bb): + with _change_block_temporarily(builder, bb): + builder.branch(bbend) + builder.position_at_end(bbend) + +class _Loop(object): + def __init__(self, parent): + self.parent = parent + + @contextlib.contextmanager + def condition(self): + builder = self.parent.builder + self._bbcond = self.parent.function.append_basic_block('loop.cond') + self._bbbody = self.parent.function.append_basic_block('loop.body') + self._bbend = self.parent.function.append_basic_block('loop.end') + + builder.branch(self._bbcond) + + builder.position_at_end(self._bbcond) + + def setcond(cond): + builder.cbranch(cond.value, self._bbbody, self._bbend) + + yield setcond + + @contextlib.contextmanager + def body(self): + builder = self.parent.builder + builder.position_at_end(self._bbbody) + + yield self + + if not _is_block_terminated(builder.basic_block): + builder.branch(self._bbcond) + + def break_loop(self): + self.branch(self._bbend) + + def continue_loop(self): + self.branch(self._bbcond) + + def close(self): + self.parent.builder.position_at_end(self._bbend) + + +class CBuilder(object): + ''' + A wrapper class for features in llvm-py package + to allow user to use C-like high-level language contruct easily. + ''' + def __init__(self, function): + self.function = function + self.declare_block = self.function.append_basic_block('decl') + self.first_body_block = self.function.append_basic_block('body') + self.builder = lc.Builder.new(self.first_body_block) + + # prepare arguments + self.args = [] + for arg in function.args: + var = self.var(arg.type, arg, name=arg.name) + self.args.append(var) + + @staticmethod + def new_function(mod, name, ret, args): + functype = lc.Type.function(ret, args) + func = mod.add_function(functype, name=name) + return CBuilder(func) + + def var(self, ty, value=None, name=''): + ''' + Only allocate in the first block + ''' + with _change_block_temporarily(self.builder, self.declare_block): + ptr = self.builder.alloca(ty, name=name) + if value is not None: + if not isinstance(value, lc.Value): + value = self.constant(ty, value).value + self.builder.store(value, ptr) + return CVar(self, ptr) + + def array(self, ty, count, name=''): + with _change_block_temporarily(self.builder, self.declare_block): + if not isinstance(count, lc.Value): + count = self.constant(lc.Type.int(), count).value + ptr = self.builder.alloca_array(ty, count, name=name) + return CArray(self, ptr) + + def ret(self, val=None): + retty = self.function.type.pointee.return_type + if val is not None: + if val.type != retty: + errmsg = "Return type mismatch" + raise TypeError(errmsg) + self.builder.ret(val.value) + else: + if retty != lc.Type.void(): + errmsg = "Cannot return void" + raise TypeError(errmsg) + self.builder.ret_void() + + @contextlib.contextmanager + def ifelse(self, cond): + cb = _IfElse(self, cond) + yield cb + cb.close() + + @contextlib.contextmanager + def loop(self): + cb = _Loop(self) + yield cb + cb.close() + + def position_at_end(self, bb): + self.basic_block = bb + self.builder.position_at_end(bb) + + def close(self): + # Close declaration block + with _change_block_temporarily(self.builder, self.declare_block): + self.builder.branch(self.first_body_block) + + def constant(self, ty, val): + if isinstance(ty, lc.IntegerType): + res = lc.Constant.int(ty, val) + elif ty==lc.Type.float() or ty==lc.Type.double(): + res = lc.Constant.real(ty, val) + else: + raise TypeError("Cannot auto build constant " + "from %s and value %s" % (ty, val)) + return CTemp(self, res) + + def constant_null(self, ty): + res = lc.Constant.null(ty) + return CTemp(self, res) + + def get_intrinsic(self, intrinsic_id, tys): + lfunc = lc.Function.intrinsic(self.function.module, intrinsic_id, tys) + return CFunc(self, lfunc) + + def get_function_named(self, name): + m = self.function.module + func = m.get_function_named(name) + return CFunc(self, func) + + def is_terminated(self): + ''' + Is the current basic-block terminated? + ''' + return _is_block_terminated(self.builder.basic_block) + + +class CValue(object): + ''' + = Signess = + Since LLVM type does not provide signess attribute. This information + is provided in the CValue.unsigned attribute. The default value is + `None`, meaning that this attribute is not set. + + In casting operation, signess information is passed as an optional arg. + + In binary operation, signess of the left operand is used. + ''' + + # Attribute for for integer values. + unsigned = None + + _BINOP_MAP = { + # op-name : (signed int, unsigned int, real) + 'add' : (lc.Builder.add, lc.Builder.add, lc.Builder.fadd), + 'sub' : (lc.Builder.sub, lc.Builder.sub, lc.Builder.fsub), + 'mul' : (lc.Builder.mul, lc.Builder.mul, lc.Builder.fmul), + 'div' : (lc.Builder.sdiv, lc.Builder.udiv, lc.Builder.fdiv), + 'mod' : (lc.Builder.srem, lc.Builder.urem, lc.Builder.frem), + } + + _BITWISE_MAP = { + # op-name : (signed int, unsigned int) + 'lshift' : (lc.Builder.shl, lc.Builder.shl), + 'rshift' : (lc.Builder.lshr, lc.Builder.ashr), + 'and' : (lc.Builder.and_, lc.Builder.and_), + 'or' : (lc.Builder.or_, lc.Builder.or_), + 'xor' : (lc.Builder.xor, lc.Builder.xor), + } + + _CMP_MAP = { + # op-name : (signed int, unsigned int, real) + 'eq' : (lc.ICMP_EQ, lc.ICMP_EQ, lc.FCMP_OEQ), + 'ne' : (lc.ICMP_NE, lc.ICMP_NE, lc.FCMP_ONE), + 'lt' : (lc.ICMP_SLT, lc.ICMP_ULT, lc.FCMP_OLT), + 'le' : (lc.ICMP_SLE, lc.ICMP_ULE, lc.FCMP_OLE), + 'gt' : (lc.ICMP_SGT, lc.ICMP_UGT, lc.FCMP_OGT), + 'ge' : (lc.ICMP_SGE, lc.ICMP_UGE, lc.FCMP_OGE), + } + + def __init__(self, parent): + self.parent = parent + + def _use_binop(self, op): + def wrapped(rhs): + self._ensure_same_type(rhs) + binop = self._BINOP_MAP[op] + if self.is_int: + if not self.unsigned: + idx = 0 + else: + idx = 1 + elif self.is_real: + idx = 2 + else: + errmsg = "Binary operation %s does not support type %s" + raise TypeError(errmsg % (op, self.type)) + res = binop[idx](self.parent.builder, self.value, rhs.value) + return CTemp(self.parent, res) + return wrapped + + def _use_bitwise(self, op): + def wrapped(rhs): + self._ensure_same_type(rhs) + if not self.is_int: + errmsg = "Bitwise operation %s does not support type %s" + raise TypeError(op, self.type) + if not self.unsigned: + idx = 0 + else: + idx = 1 + res = self._BITWISE_MAP[idx](self.parent.builder, + self.value, rhs.value) + return CTemp(self.parent, res) + return wrapped + + def __add__(self, rhs): + return self._use_binop('add')(rhs) + + def __sub__(self, rhs): + return self._use_binop('sub')(rhs) + + def __mul__(self, rhs): + return self._use_binop('mul')(rhs) + + def __div__(self, rhs): + return self._use_binop('div')(rhs) + + def __mod__(self, rhs): + return self._use_binop('mod')(rhs) + + def __lshift__(self, rhs): + return self._use_bitwise('lshift')(rhs) + + def __rshift__(self, rhs): + return self._use_bitwise('rshift')(rhs) + + def __and__(self, rhs): + return self._use_bitwise('and')(rhs) + + def __or__(self, rhs): + return self._use_bitwise('or')(rhs) + + def __xor__(self, rhs): + return self._use_bitwise('xor')(rhs) + + def _ensure_same_type(self, val): + if self.type != val.type: + errmsg = "Type mismatch: %s != %s" + raise TypeError(errmsg % (self.type, val.type)) + + @property + def is_int(self): + return _is_int(self.type) + + @property + def is_real(self): + return _is_real(self.type) + + def cast(self, ty, unsigned=False): + make = lambda X: CTemp(self.parent, X) + if self.type == ty: + return self # pass thru + elif self.is_pointer and _is_pointer(ty): + builder = self.parent.builder + return make(builder.bitcast(self.value, ty)) + elif self.is_int: + if _is_int(ty): + if self.type.width > ty.width: + if not unsigned: + return make(self.parent.builder.sext(self.value, ty)) + else: + return make(self.parent.builder.zext(self.value, ty)) + else: + return make(self.parent.trunc(self.value, ty)) + elif _is_real(ty): + if not unsigned: + return make(self.parent.builder.sitofp(self.value, ty)) + else: + return make(self.parent.builder.uitofp(self.value, ty)) + elif self.is_real: + if not unsigned: + return make(self.parent.builder.fptosi(self.value, ty)) + else: + return make(self.parent.builder.fptoui(self.value, ty)) + + errmsg = "Cast from %s to %s is not possible." + raise TypeError(errmsg % (self.type, ty)) + + def _cmp_op(self, name): + def wrapped(rhs): + make = lambda X: CTemp(self.parent, X) + self._ensure_same_type(rhs) + + flag_bag = self._CMP_MAP[name] + if self.is_int: + comparator = self.parent.builder.icmp + if not self.unsigned: + flag = flag_bag[0] + else: + flag = flag_bag[1] + elif self.is_real: + comparator = self.parent.builder.fcmp + flag = flag_bag[2] + else: + errmsg = "Comparision between %s and %s is not supported." + raise TypeError(errmsg % (self.type, rhs.type)) + + return CTemp(self.parent, comparator(flag, self.value, rhs.value)) + return wrapped + + def __eq__(self, rhs): + return self._cmp_op('eq')(rhs) + + def __ne__(self, rhs): + return self._cmp_op('ne')(rhs) + + def __lt__(self, rhs): + return self._cmp_op('lt')(rhs) + + def __le__(self, rhs): + return self._cmp_op('le')(rhs) + + def __gt__(self, rhs): + return self._cmp_op('gt')(rhs) + + def __ge__(self, rhs): + return self._cmp_op('ge')(rhs) + + + @property + def is_pointer(self): + return _is_pointer(self.type) + + def _ensure_is_pointer(self): + if not self.is_pointer: + raise TypeError("Must be a pointer") + + +class CFunc(CValue): + def __init__(self, parent, func): + super(CFunc, self).__init__(parent) + self.function = func + + def __call__(self, *args): + arg_value = map(lambda x: x.value, args) + res = self.parent.builder.call(self.function, arg_value) + return CTemp(self.parent, res) + + @property + def value(self): + return self.function + + @property + def type(self): + return self.function.type + +class CTemp(CValue): + def __init__(self, parent, value): + super(CTemp, self).__init__(parent) + self.value = value + + @property + def type(self): + return self.value.type + +class CVar(CValue): + def __init__(self, parent, ptr): + super(CVar, self).__init__(parent) + self.ptr = ptr + + def _inplace_op(self, op): + def wrapped(rhs): + res = self._use_binop('add')(rhs) + self.assign(res) + return self + return wrapped + + def __iadd__(self, rhs): + return self._inplace_op('add')(rhs) + + def __isub__(self, rhs): + return self._inplace_op('sub')(rhs) + + def __imul__(self, rhs): + return self._inplace_op('mul')(rhs) + + def __idiv__(self, rhs): + return self._inplace_op('div')(rhs) + + def __imod__(self, rhs): + return self._inplace_op('mod')(rhs) + + @property + def value(self): + return self.parent.builder.load(self.ptr) + + def assign(self, val): + self.parent.builder.store(val.value, self.ptr) + + @property + def type(self): + return self.ptr.type.pointee + + def load(self): + self._ensure_is_pointer() + loaded = self.parent.builder.load(self.value) + return CTemp(self.parent, loaded) + + def store(self, val): + self._ensure_is_pointer() + self.parent.builder.store(val.value, self.value) + + def reference(self): + return CTemp(self.parent, self.ptr) + +class CArray(CValue): + def __init__(self, parent, base): + super(CArray, self).__init__(parent) + self.base_ptr = base + + @property + def value(self): + return self.base_ptr + + @property + def type(self): + return self.base_ptr.type + + def __getitem__(self, idx): + self._ensure_is_pointer() + builder = self.parent.builder + if isinstance(idx, CValue): + idx = idx.value + elif not isinstance(idx, lc.Value): + idx = self.parent.constant(lc.Type.int(), idx).value + ptr = builder.gep(self.value, [idx]) + return CVar(self.parent, ptr) + diff --git a/llvm_cbuilder/executor.py b/llvm_cbuilder/executor.py new file mode 100644 index 0000000..e5aa1c0 --- /dev/null +++ b/llvm_cbuilder/executor.py @@ -0,0 +1,50 @@ +from llvm.core import Type, Module +import llvm.ee as le +import ctypes as ct + +MAP_CTYPES = { +'void' : None, +'bool' : ct.c_bool, +'char' : ct.c_char, +'uchar' : ct.c_ubyte, +'short' : ct.c_short, +'ushort' : ct.c_ushort, +'int' : ct.c_int, +'uint' : ct.c_uint, +'long' : ct.c_long, +'ulong' : ct.c_ulong, + +'int8' : ct.c_int8, +'uint8' : ct.c_uint8, +'int16' : ct.c_int16, +'uint16' : ct.c_uint16, +'int32' : ct.c_int32, +'uint32' : ct.c_uint32, +'int64' : ct.c_int64, +'uint64' : ct.c_uint64, + +'float' : ct.c_float, +'double' : ct.c_double, +'longdouble' : ct.c_longdouble, +} + +class CExecutor(object): + def __init__(self, mod_or_engine): + if isinstance(mod_or_engine, Module): + self.engine = le.ExecutionEngine.new(mod_or_engine) + else: + self.engine = mod_or_engine + + def get_ctype_function(self, fn, typeinfo): + types = [ MAP_CTYPES[s.strip()] for s in typeinfo.split(',') ] + if not types: + retty = None + argtys = [] + else: + retty = types[0] + argtys = types[1:] + + prototype = ct.CFUNCTYPE(retty, *argtys) + fnptr = self.engine.get_pointer_to_function(fn) + return prototype(fnptr) + diff --git a/llvm_cbuilder/shortnames.py b/llvm_cbuilder/shortnames.py new file mode 100644 index 0000000..c0e65ca --- /dev/null +++ b/llvm_cbuilder/shortnames.py @@ -0,0 +1,17 @@ +from llvm.core import Type + +void = Type.void() +char = Type.int(8) +short = Type.int(16) +int = Type.int(32) +int16 = short +int32 = int +int64 = Type.int(64) +float = Type.float() +double = Type.double() + +# pointers + +pointer = Type.pointer + +void_p = pointer(char) diff --git a/tests/test_isprime.py b/tests/test_isprime.py new file mode 100644 index 0000000..5c6b78e --- /dev/null +++ b/tests/test_isprime.py @@ -0,0 +1,120 @@ +from llvm.core import * +from llvm.passes import * +from llvm.ee import * +from llvm_cbuilder import * +import llvm_cbuilder.shortnames as C +import unittest, logging + +def is_prime(x): + if x <= 2: + return True + if (x % 2) == 0: + return False + for y in range(2, int(1 + x**0.5)): + if (x % y) == 0: + return False + return True + +def gen_is_prime(mod): + functype = Type.function(C.int, [C.int]) + func = mod.add_function(functype, 'isprime') + + cb = CBuilder(func) + + arg = cb.args[0] + + two = cb.constant(C.int, 2) + true = one = cb.constant(C.int, 1) + false = zero = cb.constant(C.int, 0) + + with cb.ifelse( arg <= two ) as ifelse: + with ifelse.then(): + cb.ret(true) + + with cb.ifelse( (arg % two) == zero ) as ifelse: + with ifelse.then(): + cb.ret(false) + + idx = cb.var(C.int, 3, name='idx') + with cb.loop() as loop: + with loop.condition() as setcond: + setcond( idx < arg ) + + with loop.body(): + with cb.ifelse( (arg % idx) == zero ) as ifelse: + with ifelse.then(): + cb.ret(false) + # increment + idx += two + + cb.ret(true) + cb.close() + return func + + +def gen_is_prime_fast(mod): + functype = Type.function(C.int, [C.int]) + func = mod.add_function(functype, 'isprime') + + cb = CBuilder(func) + + arg = cb.args[0] + + two = cb.constant(C.int, 2) + true = one = cb.constant(C.int, 1) + false = zero = cb.constant(C.int, 0) + + with cb.ifelse( arg <= two ) as ifelse: + with ifelse.then(): + cb.ret(true) + + with cb.ifelse( (arg % two) == zero ) as ifelse: + with ifelse.then(): + cb.ret(false) + + idx = cb.var(C.int, 3, name='idx') + sqrt = cb.get_intrinsic(INTR_SQRT, [C.float]) + looplimit = one + sqrt(arg.cast(C.float)).cast(C.int) + with cb.loop() as loop: + with loop.condition() as setcond: + setcond( idx < looplimit ) + + with loop.body(): + with cb.ifelse( (arg % idx) == zero ) as ifelse: + with ifelse.then(): + cb.ret(false) + # increment + idx += two + + cb.ret(true) + cb.close() + return func + +class TestIsPrime(unittest.TestCase): + def test_isprime(self): + mod = Module.new(__name__) + lf_isprime = gen_is_prime(mod) + logging.debug(mod) + mod.verify() + + exe = CExecutor(mod) + func = exe.get_ctype_function(lf_isprime, 'bool, int') + for x in range(2, 1000): + msg = "Failed at x = %d" % x + self.assertEqual(func(x), is_prime(x), msg) + + def test_isprime_fast(self): + mod = Module.new(__name__) + lf_isprime = gen_is_prime_fast(mod) + logging.debug(mod) + mod.verify() + + exe = CExecutor(mod) + func = exe.get_ctype_function(lf_isprime, 'bool, int') + for x in range(2, 1000): + msg = "Failed at x = %d" % x + self.assertEqual(func(x), is_prime(x), msg) + +if __name__ == '__main__': + unittest.main() + diff --git a/tests/test_pthread.py b/tests/test_pthread.py new file mode 100644 index 0000000..e86dbdb --- /dev/null +++ b/tests/test_pthread.py @@ -0,0 +1,79 @@ +from llvm.core import * +from llvm.passes import * +from llvm.ee import * +from llvm_cbuilder import * +import llvm_cbuilder.shortnames as C +import unittest, logging + +logging.basicConfig(level=logging.DEBUG) + +NUM_OF_THREAD = 4 + +def gen_test_worker(mod): + cb = CBuilder.new_function(mod, 'worker', C.void, [C.pointer(C.int)]) + pval = cb.args[0] + val = pval.load() + one = cb.constant(val.type, 1) + pval.store(val + one) + cb.ret() + cb.close() + +def gen_test_pthread(mod): + cb = CBuilder.new_function(mod, 'manager', C.int, [C.int]) + arg = cb.args[0] + + worker_func = cb.get_function_named('worker') + pthread_create = cb.get_function_named('pthread_create') + pthread_join = cb.get_function_named('pthread_join') + + + NULL = cb.constant_null(C.void_p) + cast_to_null = lambda x: x.cast(C.void_p) + + threads = cb.array(C.void_p, NUM_OF_THREAD) + + for tid in range(NUM_OF_THREAD): + pthread_create_args = [threads[tid].reference(), + NULL, + worker_func, + arg.reference()] + pthread_create(*map(cast_to_null, pthread_create_args)) + + for tid in range(NUM_OF_THREAD): + pthread_join_args = threads[tid], NULL + pthread_join(*map(cast_to_null, pthread_join_args)) + + worker_func(arg.reference()) + + cb.ret(arg) + cb.close() + return cb.function + +class TestPThread(unittest.TestCase): + def test_pthread(self): + mod = Module.new(__name__) + # add pthread functions + + mod.add_function(Type.function(C.int, + [C.void_p, C.void_p, C.void_p, C.void_p]), + 'pthread_create') + + mod.add_function(Type.function(C.int, + [C.void_p, C.void_p]), + 'pthread_join') + + gen_test_worker(mod) + lf_test_pthread = gen_test_pthread(mod) + logging.debug(mod) + mod.verify() + + exe = CExecutor(mod) + exe.engine.get_pointer_to_function(mod.get_function_named('worker')) + func = exe.get_ctype_function(lf_test_pthread, 'int, int') + + inarg = 1234 + self.assertEqual(func(inarg), inarg+NUM_OF_THREAD+1) + +if __name__ == '__main__': + unittest.main() + From 6981206096679347d2db346e93b963844f11aac1 Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Wed, 1 Aug 2012 20:14:34 -0700 Subject: [PATCH 02/57] Add TODO and README files. --- TODO | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 TODO diff --git a/TODO b/TODO new file mode 100644 index 0000000..9a0e689 --- /dev/null +++ b/TODO @@ -0,0 +1,2 @@ +* add atomic operation to llvm-py +* add volatile load/store to llvm-py From 7238fecc7f9f5d03320b296fe29840c1b04970e5 Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Thu, 2 Aug 2012 14:20:02 -0700 Subject: [PATCH 03/57] add build & test instruction to README --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..cab0f28 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +A llvm-py Builder wrapper for writing in slightly higher-level constructs. +This is aiming for two usecases: +1. Emit LLVM code in a more human-readable way; +2. Writing low-level code that you can't do it properly/portably with C, e.g + template (generic), atomic operations, memory ordering... + + From 8381b25f6629572755ec3add65f7f4dbc06d26bd Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Fri, 3 Aug 2012 16:26:46 -0700 Subject: [PATCH 04/57] Add atomic ops --- TODO | 2 - llvm_cbuilder/builder.py | 108 ++++++++++++++++++++++++++++++++- llvm_cbuilder/executor.py | 2 +- tests/test_atomic_add.py | 115 +++++++++++++++++++++++++++++++++++ tests/test_atomic_ldst.py | 122 ++++++++++++++++++++++++++++++++++++++ tests/test_isprime.py | 5 ++ tests/test_pthread.py | 17 +++++- 7 files changed, 362 insertions(+), 9 deletions(-) delete mode 100644 TODO create mode 100644 tests/test_atomic_add.py create mode 100644 tests/test_atomic_ldst.py diff --git a/TODO b/TODO deleted file mode 100644 index 9a0e689..0000000 --- a/TODO +++ /dev/null @@ -1,2 +0,0 @@ -* add atomic operation to llvm-py -* add volatile load/store to llvm-py diff --git a/llvm_cbuilder/builder.py b/llvm_cbuilder/builder.py index ee866df..ce96222 100644 --- a/llvm_cbuilder/builder.py +++ b/llvm_cbuilder/builder.py @@ -4,6 +4,7 @@ import contextlib import llvm.core as lc +import llvm.ee as le def _is_int(ty): @@ -115,6 +116,7 @@ class CBuilder(object): self.declare_block = self.function.append_basic_block('decl') self.first_body_block = self.function.append_basic_block('body') self.builder = lc.Builder.new(self.first_body_block) + self.target_data = le.TargetData.new(self.function.module.data_layout) # prepare arguments self.args = [] @@ -210,6 +212,81 @@ class CBuilder(object): ''' return _is_block_terminated(self.builder.basic_block) + def atomic_cmpxchg(self, ptr, old, val, ordering, crossthread=True): + res = self.builder.atomic_cmpxchg(ptr.value, old.value, val.value, + ordering, crossthread) + return CTemp(self, res) + + def atomic_xchg(self, ptr, val, ordering, crossthread=True): + res = self.builder.atomic_xchg(ptr.value, val.value, + ordering, crossthread) + return CTemp(self, res) + + def atomic_add(self, ptr, val, ordering, crossthread=True): + res = self.builder.atomic_add(ptr.value, val.value, + ordering, crossthread) + return CTemp(self, res) + + def atomic_sub(self, ptr, val, ordering, crossthread=True): + res = self.builder.atomic_sub(ptr.value, val.value, + ordering, crossthread) + return CTemp(self, res) + + def atomic_and(self, ptr, val, ordering, crossthread=True): + res = self.builder.atomic_and(ptr.value, val.value, + ordering, crossthread) + return CTemp(self, res) + + def atomic_nand(self, ptr, val, ordering, crossthread=True): + res = self.builder.atomic_nand(ptr.value, val.value, + ordering, crossthread) + return CTemp(self, res) + + def atomic_or(self, ptr, val, ordering, crossthread=True): + res = self.builder.atomic_or(ptr.value, val.value, + ordering, crossthread) + return CTemp(self, res) + + def atomic_xor(self, ptr, val, ordering, crossthread=True): + res = self.builder.atomic_xor(ptr.value, val.value, + ordering, crossthread) + return CTemp(self, res) + + def atomic_max(self, ptr, val, ordering, crossthread=True): + res = self.builder.atomic_max(ptr.value, val.value, + ordering, crossthread) + return CTemp(self, res) + + def atomic_min(self, ptr, val, ordering, crossthread=True): + res = self.builder.atomic_min(ptr.value, val.value, + ordering, crossthread) + return CTemp(self, res) + + def atomic_umax(self, ptr, val, ordering, crossthread=True): + res = self.builder.atomic_umax(ptr.value, val.value, + ordering, crossthread) + return CTemp(self, res) + + def atomic_umin(self, ptr, val, ordering, crossthread=True): + res = self.builder.atomic_umin(ptr.value, val.value, + ordering, crossthread) + return CTemp(self, res) + + def atomic_load(self, ptr, ordering, align=1, crossthread=True): + res = self.builder.atomic_load(ptr.value, ordering, align, crossthread) + return CTemp(self, res) + + def atomic_store(self, val, ptr, ordering, align=1, crossthread=True): + res = self.builder.atomic_store(val.value, ptr.value, ordering, + align, crossthread) + return CTemp(self, res) + + def fence(self, ordering, crossthread=True): + res = self.builder.fence(ordering, crossthread) + return CTemp(self, res) + + def alignment(self, ty): + return self.target_data.abi_alignment(ty) class CValue(object): ''' @@ -419,7 +496,7 @@ class CFunc(CValue): self.function = func def __call__(self, *args): - arg_value = map(lambda x: x.value, args) + arg_value = list(map(lambda x: x.value, args)) res = self.parent.builder.call(self.function, arg_value) return CTemp(self.parent, res) @@ -441,6 +518,7 @@ class CTemp(CValue): return self.value.type class CVar(CValue): + def __init__(self, parent, ptr): super(CVar, self).__init__(parent) self.ptr = ptr @@ -478,15 +556,39 @@ class CVar(CValue): def type(self): return self.ptr.type.pointee - def load(self): + def load(self, volatile=False): self._ensure_is_pointer() - loaded = self.parent.builder.load(self.value) + loaded = self.parent.builder.load(self.value, volatile=volatile) return CTemp(self.parent, loaded) def store(self, val): self._ensure_is_pointer() self.parent.builder.store(val.value, self.value) + def atomic_load(self, ordering, align=None, crossthread=True, volatile=False): + self._ensure_is_pointer() + if align is None: + align = self.parent.alignment(self.type.pointee) + inst = self.parent.builder.atomic_load(self.value, ordering, align, + crossthread=crossthread, + volatile=volatile) + return CTemp(self.parent, inst) + + def atomic_store(self, value, ordering, align=None, crossthread=True, + volatile=False): + self._ensure_is_pointer() + if align is None: + align = self.parent.alignment(self.type.pointee) + self.parent.builder.atomic_store(value.value, self.value, ordering, + align=align, crossthread=crossthread) + + def atomic_cmpxchg(self, old, new, ordering, crossthread=True, volatile=False): + self._ensure_is_pointer() + inst = self.parent.builder.atomic_cmpxchg(self.value, old.value, + new.value, ordering, + crossthread=crossthread) + return CTemp(self.parent, inst) + def reference(self): return CTemp(self.parent, self.ptr) diff --git a/llvm_cbuilder/executor.py b/llvm_cbuilder/executor.py index e5aa1c0..03324e8 100644 --- a/llvm_cbuilder/executor.py +++ b/llvm_cbuilder/executor.py @@ -31,7 +31,7 @@ MAP_CTYPES = { class CExecutor(object): def __init__(self, mod_or_engine): if isinstance(mod_or_engine, Module): - self.engine = le.ExecutionEngine.new(mod_or_engine) + self.engine = le.EngineBuilder.new(mod_or_engine).create() else: self.engine = mod_or_engine diff --git a/tests/test_atomic_add.py b/tests/test_atomic_add.py new file mode 100644 index 0000000..eb63a44 --- /dev/null +++ b/tests/test_atomic_add.py @@ -0,0 +1,115 @@ +''' +Base on the test_pthread.py and extend to use atomic instructions +''' + +from llvm.core import * +from llvm.passes import * +from llvm.ee import * +from llvm_cbuilder import * +import llvm_cbuilder.shortnames as C +import unittest, logging + +logging.basicConfig(level=logging.DEBUG) + +NUM_OF_THREAD = 4 +REPEAT = 10000 + +def gen_test_worker(mod): + cb = CBuilder.new_function(mod, 'worker', C.void, [C.pointer(C.int)]) + pval = cb.args[0] + one = cb.constant(pval.type.pointee, 1) + + ct = cb.var(C.int, 0) + limit = cb.constant(C.int, REPEAT) + with cb.loop() as loop: + with loop.condition() as setcond: + setcond( ct < limit ) + + with loop.body(): + cb.atomic_add(pval, one, 'acq_rel') + ct += one + + cb.ret() + cb.close() + return cb.function + +def gen_test_pthread(mod): + cb = CBuilder.new_function(mod, 'manager', C.int, [C.int]) + arg = cb.args[0] + + worker_func = cb.get_function_named('worker') + pthread_create = cb.get_function_named('pthread_create') + pthread_join = cb.get_function_named('pthread_join') + + + NULL = cb.constant_null(C.void_p) + cast_to_null = lambda x: x.cast(C.void_p) + + threads = cb.array(C.void_p, NUM_OF_THREAD) + + for tid in range(NUM_OF_THREAD): + pthread_create_args = [threads[tid].reference(), + NULL, + worker_func, + arg.reference()] + pthread_create(*map(cast_to_null, pthread_create_args)) + + worker_func(arg.reference()) + + for tid in range(NUM_OF_THREAD): + pthread_join_args = threads[tid], NULL + pthread_join(*map(cast_to_null, pthread_join_args)) + + + cb.ret(arg) + cb.close() + return cb.function + +class TestPThread(unittest.TestCase): + def test_pthread(self): + mod = Module.new(__name__) + # add pthread functions + + mod.add_function(Type.function(C.int, + [C.void_p, C.void_p, C.void_p, C.void_p]), + 'pthread_create') + + mod.add_function(Type.function(C.int, + [C.void_p, C.void_p]), + 'pthread_join') + + lf_test_worker = gen_test_worker(mod) + lf_test_pthread = gen_test_pthread(mod) + logging.debug(mod) + mod.verify() + + # optimize + fpm = FunctionPassManager.new(mod) + mpm = PassManager.new() + pmb = PassManagerBuilder.new() + pmb.vectorize = True + pmb.opt_level = 3 + pmb.populate(fpm) + pmb.populate(mpm) + + fpm.run(lf_test_worker) + fpm.run(lf_test_pthread) + mpm.run(mod) + logging.debug(mod) + mod.verify() + + # run + exe = CExecutor(mod) + exe.engine.get_pointer_to_function(mod.get_function_named('worker')) + func = exe.get_ctype_function(lf_test_pthread, 'int, int') + + inarg = 1234 + gold = inarg + (NUM_OF_THREAD + 1) * REPEAT + + for _ in range(1000): # run many many times to catch race condition + self.assertEqual(func(inarg), gold, "Unexpected race condition") + + +if __name__ == '__main__': + unittest.main() + diff --git a/tests/test_atomic_ldst.py b/tests/test_atomic_ldst.py new file mode 100644 index 0000000..147ec59 --- /dev/null +++ b/tests/test_atomic_ldst.py @@ -0,0 +1,122 @@ +''' +Base on the test_pthread.py and extend to use atomic instructions +''' + +from llvm.core import * +from llvm.passes import * +from llvm.ee import * +from llvm_cbuilder import * +import llvm_cbuilder.shortnames as C +import unittest, logging + +logging.basicConfig(level=logging.DEBUG) + +NUM_OF_THREAD = 4 +REPEAT = 10000 + +def gen_test_worker(mod): + cb = CBuilder.new_function(mod, 'worker', C.void, [C.pointer(C.int)]) + pval = cb.args[0] + one = cb.constant(pval.type.pointee, 1) + + ct = cb.var(C.int, 0) + limit = cb.constant(C.int, REPEAT) + with cb.loop() as loop: + with loop.condition() as setcond: + setcond( ct < limit ) + + with loop.body(): + oldval = pval.atomic_load('acquire') + updated = oldval + one + castmp = pval.atomic_cmpxchg(oldval, updated, 'release') + + with cb.ifelse( castmp == oldval ) as ifelse: + with ifelse.then(): + ct += one + + cb.ret() + cb.close() + return cb.function + +def gen_test_pthread(mod): + cb = CBuilder.new_function(mod, 'manager', C.int, [C.int]) + arg = cb.args[0] + + worker_func = cb.get_function_named('worker') + pthread_create = cb.get_function_named('pthread_create') + pthread_join = cb.get_function_named('pthread_join') + + + NULL = cb.constant_null(C.void_p) + cast_to_null = lambda x: x.cast(C.void_p) + + threads = cb.array(C.void_p, NUM_OF_THREAD) + + for tid in range(NUM_OF_THREAD): + pthread_create_args = [threads[tid].reference(), + NULL, + worker_func, + arg.reference()] + pthread_create(*map(cast_to_null, pthread_create_args)) + + worker_func(arg.reference()) + + for tid in range(NUM_OF_THREAD): + pthread_join_args = threads[tid], NULL + pthread_join(*map(cast_to_null, pthread_join_args)) + + + cb.ret(arg) + cb.close() + return cb.function + +class TestPThread(unittest.TestCase): + def test_pthread(self): + mod = Module.new(__name__) + # add pthread functions + + mod.add_function(Type.function(C.int, + [C.void_p, C.void_p, C.void_p, C.void_p]), + 'pthread_create') + + mod.add_function(Type.function(C.int, + [C.void_p, C.void_p]), + 'pthread_join') + + lf_test_worker = gen_test_worker(mod) + lf_test_pthread = gen_test_pthread(mod) + logging.debug(mod) + mod.verify() + + # optimize + fpm = FunctionPassManager.new(mod) + mpm = PassManager.new() + pmb = PassManagerBuilder.new() + pmb.vectorize = True + pmb.opt_level = 3 + pmb.populate(fpm) + pmb.populate(mpm) + + fpm.run(lf_test_worker) + fpm.run(lf_test_pthread) + mpm.run(mod) + logging.debug(mod) + mod.verify() + + # run + exe = CExecutor(mod) + exe.engine.get_pointer_to_function(mod.get_function_named('worker')) + func = exe.get_ctype_function(lf_test_pthread, 'int, int') + + inarg = 1234 + gold = inarg + (NUM_OF_THREAD + 1) * REPEAT + + for _ in range(1000): # run many many times to catch race condition + res = func(inarg) + self.assertEqual(res, gold, + "Unexpected race condition: res = %d" % res) + + +if __name__ == '__main__': + unittest.main() + diff --git a/tests/test_isprime.py b/tests/test_isprime.py index 5c6b78e..c36c71b 100644 --- a/tests/test_isprime.py +++ b/tests/test_isprime.py @@ -73,8 +73,12 @@ def gen_is_prime_fast(mod): cb.ret(false) idx = cb.var(C.int, 3, name='idx') + sqrt = cb.get_intrinsic(INTR_SQRT, [C.float]) + looplimit = one + sqrt(arg.cast(C.float)).cast(C.int) + + with cb.loop() as loop: with loop.condition() as setcond: setcond( idx < looplimit ) @@ -86,6 +90,7 @@ def gen_is_prime_fast(mod): # increment idx += two + cb.ret(true) cb.close() return func diff --git a/tests/test_pthread.py b/tests/test_pthread.py index e86dbdb..d5212b0 100644 --- a/tests/test_pthread.py +++ b/tests/test_pthread.py @@ -39,12 +39,12 @@ def gen_test_pthread(mod): arg.reference()] pthread_create(*map(cast_to_null, pthread_create_args)) + worker_func(arg.reference()) + for tid in range(NUM_OF_THREAD): pthread_join_args = threads[tid], NULL pthread_join(*map(cast_to_null, pthread_join_args)) - worker_func(arg.reference()) - cb.ret(arg) cb.close() return cb.function @@ -72,7 +72,18 @@ class TestPThread(unittest.TestCase): func = exe.get_ctype_function(lf_test_pthread, 'int, int') inarg = 1234 - self.assertEqual(func(inarg), inarg+NUM_OF_THREAD+1) + gold = inarg + NUM_OF_THREAD + 1 + self.assertLessEqual(func(inarg), gold) + # Cannot determine the exact return value due to untamed race condition + + count_race = 0 + for _ in range(2**12): + if func(inarg) != gold: + count_race += 1 + + if count_race > 0: + logging.info("Race condition occured %d times.", count_race) + logging.info("Race condition is expected.") if __name__ == '__main__': unittest.main() From aebb9857ebf2781ff4f59d6740aa5383ccbe53c5 Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Fri, 3 Aug 2012 16:31:23 -0700 Subject: [PATCH 05/57] Drop volatile option in atomic ops. Add volatile option to store. --- llvm_cbuilder/builder.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/llvm_cbuilder/builder.py b/llvm_cbuilder/builder.py index ce96222..5bc31d1 100644 --- a/llvm_cbuilder/builder.py +++ b/llvm_cbuilder/builder.py @@ -561,28 +561,26 @@ class CVar(CValue): loaded = self.parent.builder.load(self.value, volatile=volatile) return CTemp(self.parent, loaded) - def store(self, val): + def store(self, val, volatile=False): self._ensure_is_pointer() - self.parent.builder.store(val.value, self.value) + self.parent.builder.store(val.value, self.value, volatile=volatile) - def atomic_load(self, ordering, align=None, crossthread=True, volatile=False): + def atomic_load(self, ordering, align=None, crossthread=True): self._ensure_is_pointer() if align is None: align = self.parent.alignment(self.type.pointee) inst = self.parent.builder.atomic_load(self.value, ordering, align, - crossthread=crossthread, - volatile=volatile) + crossthread=crossthread) return CTemp(self.parent, inst) - def atomic_store(self, value, ordering, align=None, crossthread=True, - volatile=False): + def atomic_store(self, value, ordering, align=None, crossthread=True): self._ensure_is_pointer() if align is None: align = self.parent.alignment(self.type.pointee) self.parent.builder.atomic_store(value.value, self.value, ordering, align=align, crossthread=crossthread) - def atomic_cmpxchg(self, old, new, ordering, crossthread=True, volatile=False): + def atomic_cmpxchg(self, old, new, ordering, crossthread=True): self._ensure_is_pointer() inst = self.parent.builder.atomic_cmpxchg(self.value, old.value, new.value, ordering, From f1e10e849f63210b491d1be4b0c548f00330182c Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Fri, 3 Aug 2012 17:02:01 -0700 Subject: [PATCH 06/57] Fix __div__ for py3. Add test for nested-loops. --- llvm_cbuilder/builder.py | 3 + tests/test_atomic_add.py | 4 +- ..._atomic_ldst.py => test_atomic_cmpxchg.py} | 4 +- tests/test_nestedloops.py | 128 ++++++++++++++++++ 4 files changed, 135 insertions(+), 4 deletions(-) rename tests/{test_atomic_ldst.py => test_atomic_cmpxchg.py} (97%) create mode 100644 tests/test_nestedloops.py diff --git a/llvm_cbuilder/builder.py b/llvm_cbuilder/builder.py index 5bc31d1..0582a25 100644 --- a/llvm_cbuilder/builder.py +++ b/llvm_cbuilder/builder.py @@ -379,6 +379,9 @@ class CValue(object): def __div__(self, rhs): return self._use_binop('div')(rhs) + def __truediv__(self, rhs): + return self.__div__(rhs) + def __mod__(self, rhs): return self._use_binop('mod')(rhs) diff --git a/tests/test_atomic_add.py b/tests/test_atomic_add.py index eb63a44..63b795a 100644 --- a/tests/test_atomic_add.py +++ b/tests/test_atomic_add.py @@ -65,8 +65,8 @@ def gen_test_pthread(mod): cb.close() return cb.function -class TestPThread(unittest.TestCase): - def test_pthread(self): +class TestAtomicAdd(unittest.TestCase): + def test_atomic_add(self): mod = Module.new(__name__) # add pthread functions diff --git a/tests/test_atomic_ldst.py b/tests/test_atomic_cmpxchg.py similarity index 97% rename from tests/test_atomic_ldst.py rename to tests/test_atomic_cmpxchg.py index 147ec59..eb2a45b 100644 --- a/tests/test_atomic_ldst.py +++ b/tests/test_atomic_cmpxchg.py @@ -70,8 +70,8 @@ def gen_test_pthread(mod): cb.close() return cb.function -class TestPThread(unittest.TestCase): - def test_pthread(self): +class TestAtomicCmpXchg(unittest.TestCase): + def test_atomic_cmpxchg(self): mod = Module.new(__name__) # add pthread functions diff --git a/tests/test_nestedloops.py b/tests/test_nestedloops.py new file mode 100644 index 0000000..823a54f --- /dev/null +++ b/tests/test_nestedloops.py @@ -0,0 +1,128 @@ +from llvm.core import * +from llvm.passes import * +from llvm.ee import * +from llvm_cbuilder import * +import llvm_cbuilder.shortnames as C +import unittest, logging + +def nestedloop1(d): + z = 0 + for x in range(100): + for y in range(100): + z += x * d + int(y / d) + return z + +def gen_nestedloop1(mod): + functype = Type.function(C.int, [C.int]) + func = mod.add_function(functype, 'nestedloop1') + + cb = CBuilder(func) + + d = cb.args[0] + x = cb.var(C.int) + y = cb.var(C.int) + z = cb.var(C.int) + + one = cb.constant(C.int, 1) + zero = cb.constant(C.int, 0) + limit = cb.constant(C.int, 100) + + z.assign(zero) + x.assign(zero) + with cb.loop() as outer: + with outer.condition() as setcond: + setcond( x < limit ) + + with outer.body(): + y.assign(zero) + with cb.loop() as inner: + with inner.condition() as setcond: + setcond( y < limit ) + + with inner.body(): + z += x * d + y / d + y += one + x += one + + cb.ret(z) + cb.close() + return func + + +def nestedloop2(d): + z = 0 + for x in range(1, 100): + for y in range(1, 100): + if x > y: + z += int(x / y) * d + else: + z += int(y / x) * d + return z + +def gen_nestedloop2(mod): + functype = Type.function(C.int, [C.int]) + func = mod.add_function(functype, 'nestedloop1') + + cb = CBuilder(func) + + d = cb.args[0] + x = cb.var(C.int) + y = cb.var(C.int) + z = cb.var(C.int) + + one = cb.constant(C.int, 1) + zero = cb.constant(C.int, 0) + limit = cb.constant(C.int, 100) + + z.assign(zero) + x.assign(one) + with cb.loop() as outer: + with outer.condition() as setcond: + setcond( x < limit ) + + with outer.body(): + y.assign(one) + with cb.loop() as inner: + with inner.condition() as setcond: + setcond( y < limit ) + + with inner.body(): + with cb.ifelse(x > y) as ifelse: + with ifelse.then(): + z += x / y * d + with ifelse.otherwise(): + z += y / x * d + y += one + x += one + + cb.ret(z) + cb.close() + return func + + +class TestNestedLoop(unittest.TestCase): + def test_nestedloop1(self): + mod = Module.new(__name__) + lfunc = gen_nestedloop1(mod) + logging.debug(mod) + mod.verify() + + exe = CExecutor(mod) + func = exe.get_ctype_function(lfunc, 'int, int') + for x in range(1, 100): + self.assertEqual(func(x), int(nestedloop1(x))) + + def test_nestedloop2(self): + mod = Module.new(__name__) + lfunc = gen_nestedloop2(mod) + logging.debug(mod) + mod.verify() + + exe = CExecutor(mod) + func = exe.get_ctype_function(lfunc, 'int, int') + for x in range(1, 100): + self.assertEqual(func(x), int(nestedloop2(x))) + +if __name__ == '__main__': + unittest.main() + From 184e4ea1a119b1e29b6fa3764ce75a2fc377867e Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Fri, 3 Aug 2012 22:30:54 -0700 Subject: [PATCH 07/57] Add API for structure --- llvm_cbuilder/__init__.py | 3 ++- llvm_cbuilder/builder.py | 36 ++++++++++++++++++++++--- llvm_cbuilder/executor.py | 18 ++++++++----- llvm_cbuilder/shortnames.py | 12 +++++++++ tests/test_struct.py | 52 +++++++++++++++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 12 deletions(-) create mode 100644 tests/test_struct.py diff --git a/llvm_cbuilder/__init__.py b/llvm_cbuilder/__init__.py index e623129..b32e754 100644 --- a/llvm_cbuilder/__init__.py +++ b/llvm_cbuilder/__init__.py @@ -1,2 +1,3 @@ -from .builder import CBuilder +from .builder import * from .executor import CExecutor + diff --git a/llvm_cbuilder/builder.py b/llvm_cbuilder/builder.py index 0582a25..7dc338b 100644 --- a/llvm_cbuilder/builder.py +++ b/llvm_cbuilder/builder.py @@ -25,6 +25,12 @@ def _is_block_terminated(bb): instrs = bb.instructions return len(instrs) > 0 and instrs[-1].is_terminator +def _is_cstruct(ty): + try: + return issubclass(ty, CStruct) + except TypeError: + return False + @contextlib.contextmanager def _change_block_temporarily(builder, bb): origbb = builder.basic_block @@ -135,11 +141,21 @@ class CBuilder(object): Only allocate in the first block ''' with _change_block_temporarily(self.builder, self.declare_block): + is_cstruct = _is_cstruct(ty) + if is_cstruct: + cstruct = ty + ty = ty.llvm_type() ptr = self.builder.alloca(ty, name=name) - if value is not None: - if not isinstance(value, lc.Value): - value = self.constant(ty, value).value - self.builder.store(value, ptr) + # back to the body + if value is not None: + if isinstance(value, CValue): + value = value.value + if not isinstance(value, lc.Value): + value = self.constant(ty, value).value + self.builder.store(value, ptr) + if is_cstruct: + return cstruct(self, ptr) + else: return CVar(self, ptr) def array(self, ty, count, name=''): @@ -616,3 +632,15 @@ class CArray(CValue): ptr = builder.gep(self.value, [idx]) return CVar(self.parent, ptr) +class CStruct(CValue): + @classmethod + def llvm_type(cls): + return lc.Type.struct([v for k, v in cls._fields_]) + + def __init__(self, parent, ptr): + super(CStruct, self).__init__(parent) + makeind = lambda x: self.parent.constant(lc.Type.int(), x).value + for i, (fd, _) in enumerate(self._fields_): + gep = self.parent.builder.gep(ptr, [makeind(0), makeind(i)]) + setattr(self, fd, CVar(self.parent, gep)) + diff --git a/llvm_cbuilder/executor.py b/llvm_cbuilder/executor.py index 03324e8..a2f558e 100644 --- a/llvm_cbuilder/executor.py +++ b/llvm_cbuilder/executor.py @@ -35,14 +35,18 @@ class CExecutor(object): else: self.engine = mod_or_engine - def get_ctype_function(self, fn, typeinfo): - types = [ MAP_CTYPES[s.strip()] for s in typeinfo.split(',') ] - if not types: - retty = None - argtys = [] + def get_ctype_function(self, fn, *typeinfo): + if len(typeinfo)==1 and isinstance(typeinfo[0], str): + types = [ MAP_CTYPES[s.strip()] for s in typeinfo[0].split(',') ] + if not types: + retty = None + argtys = [] + else: + retty = types[0] + argtys = types[1:] else: - retty = types[0] - argtys = types[1:] + retty = typeinfo[0] + argtys = typeinfo[1:] prototype = ct.CFUNCTYPE(retty, *argtys) fnptr = self.engine.get_pointer_to_function(fn) diff --git a/llvm_cbuilder/shortnames.py b/llvm_cbuilder/shortnames.py index c0e65ca..d3fc5af 100644 --- a/llvm_cbuilder/shortnames.py +++ b/llvm_cbuilder/shortnames.py @@ -7,6 +7,7 @@ int = Type.int(32) int16 = short int32 = int int64 = Type.int(64) + float = Type.float() double = Type.double() @@ -15,3 +16,14 @@ double = Type.double() pointer = Type.pointer void_p = pointer(char) + +# platform dependent + +def _determine_pointer_size(): + from ctypes import sizeof, c_void_p + return sizeof(c_void_p) * 8 + +pointer_size = _determine_pointer_size() + +intp = {32: int32, 64: int64}[pointer_size] + diff --git a/tests/test_struct.py b/tests/test_struct.py new file mode 100644 index 0000000..35048e6 --- /dev/null +++ b/tests/test_struct.py @@ -0,0 +1,52 @@ +from llvm.core import * +from llvm_cbuilder import * +import llvm_cbuilder.shortnames as C +import unittest, ctypes + +class Vector2D(CStruct): + _fields_ = [ + ('x', C.float), + ('y', C.float), + ] + +class Vector2DCtype(ctypes.Structure): + _fields_ = [ + ('x', ctypes.c_float), + ('y', ctypes.c_float), + ] + +def gen_vector2d_dist(mod): + functype = Type.function(C.float, [C.pointer(Vector2D.llvm_type())]) + func = mod.add_function(functype, 'vector2d_dist') + + cb = CBuilder(func) + vec = cb.var(Vector2D, cb.args[0].load()) + dist = vec.x * vec.x + vec.y * vec.y + + cb.ret(dist) + cb.close() + return func + + +class TestStruct(unittest.TestCase): + def test_vector2d_dist(self): + # prepare module + mod = Module.new('mod') + lfunc = gen_vector2d_dist(mod) + mod.verify() + # run + exe = CExecutor(mod) + func = exe.get_ctype_function(lfunc, ctypes.c_float, ctypes.POINTER(Vector2DCtype)) + + from random import random + pydist = lambda x, y: x * x + y * y + for _ in range(100): + x, y = random(), random() + vec = Vector2DCtype(x=x, y=y) + ans = func(ctypes.pointer(vec)) + gold = pydist(x, y) + + self.assertLess(abs(ans-gold)/gold, 1e-6) + +if __name__ == '__main__': + unittest.main() From b45c768cc5af27cce538d6a25baa92240a848835 Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Sat, 4 Aug 2012 15:14:09 -0700 Subject: [PATCH 08/57] Fix --- llvm_cbuilder/builder.py | 41 +++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/llvm_cbuilder/builder.py b/llvm_cbuilder/builder.py index 7dc338b..cb33214 100644 --- a/llvm_cbuilder/builder.py +++ b/llvm_cbuilder/builder.py @@ -103,10 +103,10 @@ class _Loop(object): builder.branch(self._bbcond) def break_loop(self): - self.branch(self._bbend) + self.parent.builder.branch(self._bbend) def continue_loop(self): - self.branch(self._bbcond) + self.parent.builder.branch(self._bbcond) def close(self): self.parent.builder.position_at_end(self._bbend) @@ -542,27 +542,50 @@ class CVar(CValue): super(CVar, self).__init__(parent) self.ptr = ptr - def _inplace_op(self, op): + def _inplace_binop(self, op): def wrapped(rhs): - res = self._use_binop('add')(rhs) + res = self._use_binop(op)(rhs) self.assign(res) return self return wrapped def __iadd__(self, rhs): - return self._inplace_op('add')(rhs) + return self._inplace_binop('add')(rhs) def __isub__(self, rhs): - return self._inplace_op('sub')(rhs) + return self._inplace_binop('sub')(rhs) def __imul__(self, rhs): - return self._inplace_op('mul')(rhs) + return self._inplace_binop('mul')(rhs) def __idiv__(self, rhs): - return self._inplace_op('div')(rhs) + return self._inplace_binop('div')(rhs) def __imod__(self, rhs): - return self._inplace_op('mod')(rhs) + return self._inplace_binop('mod')(rhs) + + + def _inplace_bitwise(self, op): + def wrapped(rhs): + res = self._use_bitwise(op)(rhs) + self.assign(res) + return self + return wrapped + + def __ilshift__(self, rhs): + return self._inplace_bitwise('lshift')(rhs) + + def __irshift__(self, rhs): + return self._inplace_bitwise('rshift')(rhs) + + def __iand__(self, rhs): + return self._inplace_bitwise('and')(rhs) + + def __ior__(self, rhs): + return self._inplace_bitwise('or')(rhs) + + def __ixor__(self, rhs): + return self._inplace_bitwise('xor')(rhs) @property def value(self): From 673ada6a4ea36a8a61a4b8ce0e9739a5716c2b2a Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Sat, 4 Aug 2012 15:18:41 -0700 Subject: [PATCH 09/57] Fix and add tests. --- tests/test_isprime.py | 2 +- tests/test_loopcontrol.py | 135 ++++++++++++++++++++++++++++++++++++++ tests/test_nestedloops.py | 2 +- 3 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 tests/test_loopcontrol.py diff --git a/tests/test_isprime.py b/tests/test_isprime.py index c36c71b..bcc0b9c 100644 --- a/tests/test_isprime.py +++ b/tests/test_isprime.py @@ -54,7 +54,7 @@ def gen_is_prime(mod): def gen_is_prime_fast(mod): functype = Type.function(C.int, [C.int]) - func = mod.add_function(functype, 'isprime') + func = mod.add_function(functype, 'isprime_fast') cb = CBuilder(func) diff --git a/tests/test_loopcontrol.py b/tests/test_loopcontrol.py new file mode 100644 index 0000000..7e252bf --- /dev/null +++ b/tests/test_loopcontrol.py @@ -0,0 +1,135 @@ +from llvm.core import * +from llvm.passes import * +from llvm.ee import * +from llvm_cbuilder import * +import llvm_cbuilder.shortnames as C +import unittest, logging + +def loopbreak(d): + z = 0 + for x in range(100): + for y in range(100): + z += x + y + if z > 50: + break + z -= d + return z + +def gen_loopbreak(mod): + functype = Type.function(C.int, [C.int]) + func = mod.add_function(functype, 'loopbreak') + + cb = CBuilder(func) + + d = cb.args[0] + x = cb.var(C.int) + y = cb.var(C.int) + z = cb.var(C.int) + + one = cb.constant(C.int, 1) + zero = cb.constant(C.int, 0) + limit = cb.constant(C.int, 100) + fifty = cb.constant(C.int, 50) + + z.assign(zero) + x.assign(zero) + with cb.loop() as outer: + with outer.condition() as setcond: + setcond( x < limit ) + + with outer.body(): + y.assign(zero) + with cb.loop() as inner: + with inner.condition() as setcond: + setcond( y < limit ) + + with inner.body(): + z += x + y + with cb.ifelse( z > fifty ) as ifelse: + with ifelse.then(): + inner.break_loop() + y += one + z -= d + x += one + + cb.ret(z) + cb.close() + return func + +def loopcontinue(d): + z = 0 + for x in range(100): + for y in range(100): + z += x + y + if z > 50: + continue + z += d + return z + +def gen_loopcontinue(mod): + functype = Type.function(C.int, [C.int]) + func = mod.add_function(functype, 'loopcontinue') + + cb = CBuilder(func) + + d = cb.args[0] + x = cb.var(C.int) + y = cb.var(C.int) + z = cb.var(C.int) + + one = cb.constant(C.int, 1) + zero = cb.constant(C.int, 0) + limit = cb.constant(C.int, 100) + fifty = cb.constant(C.int, 50) + + z.assign(zero) + x.assign(zero) + with cb.loop() as outer: + with outer.condition() as setcond: + setcond( x < limit ) + + with outer.body(): + y.assign(zero) + with cb.loop() as inner: + with inner.condition() as setcond: + setcond( y < limit ) + + with inner.body(): + z += x + y + y += one + with cb.ifelse( z > fifty ) as ifelse: + with ifelse.then(): + inner.continue_loop() + z += d + x += one + + cb.ret(z) + cb.close() + return func + +class TestLoopControl(unittest.TestCase): + def test_loopbreak(self): + mod = Module.new(__name__) + lfunc = gen_loopbreak(mod) + logging.debug(mod) + mod.verify() + + exe = CExecutor(mod) + func = exe.get_ctype_function(lfunc, 'int, int') + for x in range(100): + self.assertEqual(func(x), loopbreak(x)) + + def test_loopcontinue(self): + mod = Module.new(__name__) + lfunc = gen_loopcontinue(mod) + logging.debug(mod) + mod.verify() + + exe = CExecutor(mod) + func = exe.get_ctype_function(lfunc, 'int, int') + for x in range(100): + self.assertEqual(func(x), loopcontinue(x)) + +if __name__ == '__main__': + unittest.main() + diff --git a/tests/test_nestedloops.py b/tests/test_nestedloops.py index 823a54f..0876bb2 100644 --- a/tests/test_nestedloops.py +++ b/tests/test_nestedloops.py @@ -61,7 +61,7 @@ def nestedloop2(d): def gen_nestedloop2(mod): functype = Type.function(C.int, [C.int]) - func = mod.add_function(functype, 'nestedloop1') + func = mod.add_function(functype, 'nestedloop2') cb = CBuilder(func) From 0654e00ab157ecc964c59fbadb22f4d1244d3fe0 Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Sat, 4 Aug 2012 19:13:35 -0700 Subject: [PATCH 10/57] Add printf() and friends. Disable logging in old tests. --- llvm_cbuilder/builder.py | 46 ++++++++++++++++++++++++--- tests/test_atomic_add.py | 2 +- tests/test_atomic_cmpxchg.py | 2 +- tests/test_print.py | 60 ++++++++++++++++++++++++++++++++++++ tests/test_pthread.py | 2 +- 5 files changed, 105 insertions(+), 7 deletions(-) create mode 100644 tests/test_print.py diff --git a/llvm_cbuilder/builder.py b/llvm_cbuilder/builder.py index cb33214..16f6daf 100644 --- a/llvm_cbuilder/builder.py +++ b/llvm_cbuilder/builder.py @@ -6,7 +6,6 @@ import contextlib import llvm.core as lc import llvm.ee as le - def _is_int(ty): return isinstance(ty, lc.IntegerType) @@ -31,6 +30,9 @@ def _is_cstruct(ty): except TypeError: return False +def _list_values(iterable): + return [i.value for i in iterable] + @contextlib.contextmanager def _change_block_temporarily(builder, bb): origbb = builder.basic_block @@ -111,7 +113,6 @@ class _Loop(object): def close(self): self.parent.builder.position_at_end(self._bbend) - class CBuilder(object): ''' A wrapper class for features in llvm-py package @@ -136,6 +137,32 @@ class CBuilder(object): func = mod.add_function(functype, name=name) return CBuilder(func) + def printf(self, fmt, *args): + mod = self.function.module + int_t = lc.Type.int() + char_ptr_t = lc.Type.pointer(lc.Type.int(8)) + functype = lc.Type.function(int_t, [char_ptr_t], True) + printf = mod.get_or_insert_function(functype, name='printf') + ret = self.builder.call(printf, [fmt.value]+_list_values(args)) + return CTemp(self, ret) + + def debug(self, *args): + type_mapper = { + 'i32': '%d', + 'double': '%e', + } + itemsfmt = [] + items = [] + for i in args: + if isinstance(i, str): + itemsfmt.append(i.replace('%', '%%')) + else: + ty = type_mapper[str(i.type)] + itemsfmt.append(ty) + items.append(i) + fmt = ' '.join(itemsfmt) + '\n' + return self.printf(self.constant_string(fmt), *items) + def var(self, ty, value=None, name=''): ''' Only allocate in the first block @@ -150,7 +177,7 @@ class CBuilder(object): if value is not None: if isinstance(value, CValue): value = value.value - if not isinstance(value, lc.Value): + elif not isinstance(value, lc.Value): value = self.constant(ty, value).value self.builder.store(value, ptr) if is_cstruct: @@ -213,6 +240,16 @@ class CBuilder(object): res = lc.Constant.null(ty) return CTemp(self, res) + def constant_string(self, string): + mod = self.function.module + name = '.conststr.%x' % hash(string) + content = lc.Constant.stringz(string) + globalstr = mod.add_global_variable(content.type, name=name) + globalstr.initializer = content + ptr = mod.add_global_variable(lc.Type.pointer(content.type.element), + name=name+".ptr") + return CTemp(self, globalstr.bitcast(lc.Type.pointer(content.type.element))) + def get_intrinsic(self, intrinsic_id, tys): lfunc = lc.Function.intrinsic(self.function.module, intrinsic_id, tys) return CFunc(self, lfunc) @@ -515,7 +552,7 @@ class CFunc(CValue): self.function = func def __call__(self, *args): - arg_value = list(map(lambda x: x.value, args)) + arg_value = _list_values(args) res = self.parent.builder.call(self.function, arg_value) return CTemp(self.parent, res) @@ -667,3 +704,4 @@ class CStruct(CValue): gep = self.parent.builder.gep(ptr, [makeind(0), makeind(i)]) setattr(self, fd, CVar(self.parent, gep)) + diff --git a/tests/test_atomic_add.py b/tests/test_atomic_add.py index 63b795a..27688e7 100644 --- a/tests/test_atomic_add.py +++ b/tests/test_atomic_add.py @@ -9,7 +9,7 @@ from llvm_cbuilder import * import llvm_cbuilder.shortnames as C import unittest, logging -logging.basicConfig(level=logging.DEBUG) +# logging.basicConfig(level=logging.DEBUG) NUM_OF_THREAD = 4 REPEAT = 10000 diff --git a/tests/test_atomic_cmpxchg.py b/tests/test_atomic_cmpxchg.py index eb2a45b..27fb763 100644 --- a/tests/test_atomic_cmpxchg.py +++ b/tests/test_atomic_cmpxchg.py @@ -9,7 +9,7 @@ from llvm_cbuilder import * import llvm_cbuilder.shortnames as C import unittest, logging -logging.basicConfig(level=logging.DEBUG) +# logging.basicConfig(level=logging.DEBUG) NUM_OF_THREAD = 4 REPEAT = 10000 diff --git a/tests/test_print.py b/tests/test_print.py new file mode 100644 index 0000000..d971e31 --- /dev/null +++ b/tests/test_print.py @@ -0,0 +1,60 @@ +from llvm.core import * +from llvm.passes import * +from llvm.ee import * +from llvm_cbuilder import * +import llvm_cbuilder.shortnames as C +import sys, unittest, logging +from subprocess import Popen, PIPE + +def gen_debugprint(mod): + functype = Type.function(C.void, []) + func = mod.add_function(functype, 'debugprint') + + cb = CBuilder(func) + fmt = cb.constant_string("Show %d %.3f %.3e\n") + + an_int = cb.constant(C.int, 123) + a_float = cb.constant(C.double, 1.234) + a_double = cb.constant(C.double, 1e-31) + cb.printf(fmt, an_int, a_float, a_double) + + cb.debug('an_int =', an_int, 'a_float =', a_float, 'a_double =', a_double) + + cb.ret() + cb.close() + return func + +def main_debugprint(): + # generate code + mod = Module.new(__name__) + lfunc = gen_debugprint(mod) + logging.debug(mod) + mod.verify() + # run + exe = CExecutor(mod) + func = exe.get_ctype_function(lfunc, 'void') + func() + +class TestPrint(unittest.TestCase): + def test_debugprint(self): + p = Popen(["python", "test_print.py", "-child"], stdout=PIPE) + p.wait() + + lines = p.stdout.read().decode().splitlines(False) + + expect = [ + 'Show 123 1.234 1.000e-31', + 'an_int = 123 a_float = 1.234000e+00 a_double = 1.000000e-31', + ] + self.assertEqual(expect, lines) + + p.stdout.close() + +if __name__ == '__main__': + try: + if sys.argv[1] == '-child': + main_debugprint() + except IndexError: + unittest.main() + + diff --git a/tests/test_pthread.py b/tests/test_pthread.py index d5212b0..d1d3a72 100644 --- a/tests/test_pthread.py +++ b/tests/test_pthread.py @@ -5,7 +5,7 @@ from llvm_cbuilder import * import llvm_cbuilder.shortnames as C import unittest, logging -logging.basicConfig(level=logging.DEBUG) +# logging.basicConfig(level=logging.DEBUG) NUM_OF_THREAD = 4 From 891daa1164d0941a71ebc1629c59429c091579ac Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Sun, 5 Aug 2012 21:35:02 -0700 Subject: [PATCH 11/57] Added test implementation of parallel-vectorize (incomplete). Various fix. --- llvm_cbuilder/builder.py | 213 ++++++++++++++++++------ llvm_cbuilder/executor.py | 2 +- llvm_cbuilder/shortnames.py | 1 + parallel_vectorize.py | 318 ++++++++++++++++++++++++++++++++++++ test_parallel_vectorize.py | 73 +++++++++ 5 files changed, 555 insertions(+), 52 deletions(-) create mode 100644 parallel_vectorize.py create mode 100644 test_parallel_vectorize.py diff --git a/llvm_cbuilder/builder.py b/llvm_cbuilder/builder.py index 16f6daf..ee308d6 100644 --- a/llvm_cbuilder/builder.py +++ b/llvm_cbuilder/builder.py @@ -40,6 +40,10 @@ def _change_block_temporarily(builder, bb): yield builder.position_at_end(origbb) +@contextlib.contextmanager +def _change_block_temporarily_dummy(*args): + yield + class _IfElse(object): def __init__(self, parent, cond): self.parent = parent @@ -58,14 +62,17 @@ class _IfElse(object): yield - self._to_close.extend([self._bbif, self._bbelse]) + self._to_close.extend([self._bbif, self._bbelse, builder.basic_block]) @contextlib.contextmanager def otherwise(self): - self.parent.builder.position_at_end(self._bbelse) + builder = self.parent.builder + builder.position_at_end(self._bbelse) yield + self._to_close.append(builder.basic_block) def close(self): + self._to_close.append(self.parent.builder.basic_block) bbend = self.parent.function.append_basic_block('if.end') builder = self.parent.builder for bb in self._to_close: @@ -111,7 +118,11 @@ class _Loop(object): self.parent.builder.branch(self._bbcond) def close(self): - self.parent.builder.position_at_end(self._bbend) + builder = self.parent.builder +# if not _is_block_terminated(builder.basic_block): +# with _change_block_temporarily(builder, builder.basic_block): +# builder.branch(self._bbend) + builder.position_at_end(self._bbend) class CBuilder(object): ''' @@ -148,7 +159,10 @@ class CBuilder(object): def debug(self, *args): type_mapper = { + 'i8' : '%c', + 'i16': '%hd', 'i32': '%d', + 'i64': '%ld', 'double': '%e', } itemsfmt = [] @@ -185,9 +199,23 @@ class CBuilder(object): else: return CVar(self, ptr) + def var_copy(self, val, name=''): + return self.var(val.type, val, name=name) + def array(self, ty, count, name=''): - with _change_block_temporarily(self.builder, self.declare_block): - if not isinstance(count, lc.Value): + if isinstance(count, int) or isinstance(count, lc.Constant): + contexthelper = _change_block_temporarily + else: + contexthelper = _change_block_temporarily_dummy + + with contexthelper(self.builder, self.declare_block): + is_cstruct = _is_cstruct(ty) + if is_cstruct: + cstruct = ty + ty = ty.llvm_type() + if isinstance(count, CValue): + count = count.value + elif not isinstance(count, lc.Value): count = self.constant(lc.Type.int(), count).value ptr = self.builder.alloca_array(ty, count, name=name) return CArray(self, ptr) @@ -341,6 +369,40 @@ class CBuilder(object): def alignment(self, ty): return self.target_data.abi_alignment(ty) + def unreachable(self): + self.builder.unreachable() + +class CDefinition(CBuilder): + ''' + Inherit this class to for defining functions + ''' + _name_ = 'unamed' # name of the function; should overide in subclass + _retty_ = lc.Type.void() # return type; can overide in subclass + _argtys_ = [] # a list of tuple(name, type); can overide in subclass + + @classmethod + def define(cls, module, **kws): + functype = lc.Type.function(cls._retty_, [v for k, v in cls._argtys_]) + func = module.get_or_insert_function(functype, name=cls._name_) + if not func.is_declaration: # already defined? + raise NameError('Function %s has already been defined' % cls.name) + + # Name all arguments + for i, (name, _) in enumerate(cls._argtys_): + func.args[i].name = name + + # Create builder and populate body + cbuilder = cls(func) + cbuilder.body(*cbuilder.args, **kws) + cbuilder.close() + return func + + def body(self): + ''' + Overide this function to define the body. + ''' + raise NotImplementedError + class CValue(object): ''' = Signess = @@ -475,13 +537,13 @@ class CValue(object): return make(builder.bitcast(self.value, ty)) elif self.is_int: if _is_int(ty): - if self.type.width > ty.width: + if self.type.width < ty.width: if not unsigned: return make(self.parent.builder.sext(self.value, ty)) else: return make(self.parent.builder.zext(self.value, ty)) else: - return make(self.parent.trunc(self.value, ty)) + return make(self.parent.builder.trunc(self.value, ty)) elif _is_real(ty): if not unsigned: return make(self.parent.builder.sitofp(self.value, ty)) @@ -543,7 +605,46 @@ class CValue(object): def _ensure_is_pointer(self): if not self.is_pointer: - raise TypeError("Must be a pointer") + raise TypeError("Must be a pointer; got %s" % self.type) + + def __getitem__(self, idx): + self._ensure_is_pointer() + if not isinstance(idx, CValue): + idx = self.parent.constant(lc.Type.int(), idx) + bldr = self.parent.builder + ptr = bldr.gep(self.value, [idx.value]) + return CVar(self.parent, ptr) + + def load(self, volatile=False): + self._ensure_is_pointer() + loaded = self.parent.builder.load(self.value, volatile=volatile) + return CTemp(self.parent, loaded) + + def store(self, val, volatile=False): + self._ensure_is_pointer() + self.parent.builder.store(val.value, self.value, volatile=volatile) + + def atomic_load(self, ordering, align=None, crossthread=True): + self._ensure_is_pointer() + if align is None: + align = self.parent.alignment(self.type.pointee) + inst = self.parent.builder.atomic_load(self.value, ordering, align, + crossthread=crossthread) + return CTemp(self.parent, inst) + + def atomic_store(self, value, ordering, align=None, crossthread=True): + self._ensure_is_pointer() + if align is None: + align = self.parent.alignment(self.type.pointee) + self.parent.builder.atomic_store(value.ptr, self.value, ordering, + align=align, crossthread=crossthread) + + def atomic_cmpxchg(self, old, new, ordering, crossthread=True): + self._ensure_is_pointer() + inst = self.parent.builder.atomic_cmpxchg(self.value, old.value, + new.value, ordering, + crossthread=crossthread) + return CTemp(self.parent, inst) class CFunc(CValue): @@ -552,8 +653,14 @@ class CFunc(CValue): self.function = func def __call__(self, *args): - arg_value = _list_values(args) - res = self.parent.builder.call(self.function, arg_value) + arg_values = _list_values(args) + ftype = self.function.type.pointee + for i, (exp, got) in enumerate(zip(ftype.args, arg_values)): + if exp != got.type: + raise TypeError("At call to %s, " + "argument %d mismatch: %s != %s" + % (self.function.name, i, exp, got.type)) + res = self.parent.builder.call(self.function, arg_values) return CTemp(self.parent, res) @property @@ -601,7 +708,6 @@ class CVar(CValue): def __imod__(self, rhs): return self._inplace_binop('mod')(rhs) - def _inplace_bitwise(self, op): def wrapped(rhs): res = self._use_bitwise(op)(rhs) @@ -629,46 +735,23 @@ class CVar(CValue): return self.parent.builder.load(self.ptr) def assign(self, val): + self._ensure_same_type(val) self.parent.builder.store(val.value, self.ptr) @property def type(self): return self.ptr.type.pointee - def load(self, volatile=False): - self._ensure_is_pointer() - loaded = self.parent.builder.load(self.value, volatile=volatile) - return CTemp(self.parent, loaded) - - def store(self, val, volatile=False): - self._ensure_is_pointer() - self.parent.builder.store(val.value, self.value, volatile=volatile) - - def atomic_load(self, ordering, align=None, crossthread=True): - self._ensure_is_pointer() - if align is None: - align = self.parent.alignment(self.type.pointee) - inst = self.parent.builder.atomic_load(self.value, ordering, align, - crossthread=crossthread) - return CTemp(self.parent, inst) - - def atomic_store(self, value, ordering, align=None, crossthread=True): - self._ensure_is_pointer() - if align is None: - align = self.parent.alignment(self.type.pointee) - self.parent.builder.atomic_store(value.value, self.value, ordering, - align=align, crossthread=crossthread) - - def atomic_cmpxchg(self, old, new, ordering, crossthread=True): - self._ensure_is_pointer() - inst = self.parent.builder.atomic_cmpxchg(self.value, old.value, - new.value, ordering, - crossthread=crossthread) - return CTemp(self.parent, inst) - def reference(self): return CTemp(self.parent, self.ptr) + def as_struct(self, cstruct_class, volatile=False): + if _is_pointer(self.type): + ptr = self.parent.builder.load(self.ptr, volatile=volatile) + return cstruct_class(self.parent, ptr) + else: + return cstruct_class(self.parent, self.ptr) + class CArray(CValue): def __init__(self, parent, base): super(CArray, self).__init__(parent) @@ -678,19 +761,23 @@ class CArray(CValue): def value(self): return self.base_ptr + def reference(self): + return CTemp(self.parent, self.base_ptr) + @property def type(self): return self.base_ptr.type - def __getitem__(self, idx): - self._ensure_is_pointer() - builder = self.parent.builder - if isinstance(idx, CValue): - idx = idx.value - elif not isinstance(idx, lc.Value): - idx = self.parent.constant(lc.Type.int(), idx).value - ptr = builder.gep(self.value, [idx]) - return CVar(self.parent, ptr) +## Moved to CValue +# def __getitem__(self, idx): +# self._ensure_is_pointer() +# builder = self.parent.builder +# if isinstance(idx, CValue): +# idx = idx.value +# elif not isinstance(idx, lc.Value): +# idx = self.parent.constant(lc.Type.int(), idx).value +# ptr = builder.gep(self.value, [idx]) +# return CVar(self.parent, ptr) class CStruct(CValue): @classmethod @@ -700,8 +787,32 @@ class CStruct(CValue): def __init__(self, parent, ptr): super(CStruct, self).__init__(parent) makeind = lambda x: self.parent.constant(lc.Type.int(), x).value + self.ptr = ptr for i, (fd, _) in enumerate(self._fields_): gep = self.parent.builder.gep(ptr, [makeind(0), makeind(i)]) + gep.name = "%s.%s" % (type(self).__name__, fd) setattr(self, fd, CVar(self.parent, gep)) + def reference(self): + return CTemp(self.parent, self.ptr) + +class CExternal(object): + def __init__(self, cbuilder): + is_func = lambda x: isinstance(x, lc.FunctionType) + non_magic = lambda s: not ( s.startswith('__') and s.endswith('__') ) + + to_declare = [] + for fname in filter(non_magic, vars(type(self))): + ftype = getattr(self, fname) + if is_func(ftype): + to_declare.append((fname, ftype)) + + mod = cbuilder.function.module + for fname, ftype in to_declare: + func = mod.get_or_insert_function(ftype, name=fname) + if func.type.pointee != ftype: + raise NameError("Function has already been declared " + "with a different type: %s != %s" + % (func.type, ftype) ) + setattr(self, fname, CFunc(cbuilder, func)) diff --git a/llvm_cbuilder/executor.py b/llvm_cbuilder/executor.py index a2f558e..bd7d78c 100644 --- a/llvm_cbuilder/executor.py +++ b/llvm_cbuilder/executor.py @@ -31,7 +31,7 @@ MAP_CTYPES = { class CExecutor(object): def __init__(self, mod_or_engine): if isinstance(mod_or_engine, Module): - self.engine = le.EngineBuilder.new(mod_or_engine).create() + self.engine = le.EngineBuilder.new(mod_or_engine).opt(3).create() else: self.engine = mod_or_engine diff --git a/llvm_cbuilder/shortnames.py b/llvm_cbuilder/shortnames.py index d3fc5af..beaaad3 100644 --- a/llvm_cbuilder/shortnames.py +++ b/llvm_cbuilder/shortnames.py @@ -16,6 +16,7 @@ double = Type.double() pointer = Type.pointer void_p = pointer(char) +char_p = pointer(char) # platform dependent diff --git a/parallel_vectorize.py b/parallel_vectorize.py new file mode 100644 index 0000000..1f83ad5 --- /dev/null +++ b/parallel_vectorize.py @@ -0,0 +1,318 @@ +from llvm.core import * +from llvm.passes import * + +from llvm_cbuilder import * +import llvm_cbuilder.shortnames as C + +class WorkQueue(CStruct): + _fields_ = [ + ('next', C.intp), + ('last', C.intp), + ('lock', C.int), + ] + + + def Lock(self): + with self.parent.loop() as loop: + with loop.condition() as setcond: + unlocked = self.parent.constant(self.lock.type, 0) + locked = self.parent.constant(self.lock.type, 1) + + res = self.lock.reference().atomic_cmpxchg(unlocked, locked, + ordering='acquire') + setcond( res != unlocked ) + + with loop.body(): + pass + + def Unlock(self): + unlocked = self.parent.constant(self.lock.type, 0) + locked = self.parent.constant(self.lock.type, 1) + + res = self.lock.reference().atomic_cmpxchg(locked, unlocked, + ordering='release') + + with self.parent.ifelse( res != locked ) as ifelse: + with ifelse.then(): + # This shall kill the program + self.parent.unreachable() + + +class ContextCommon(CStruct): + _fields_ = [ + # loop ufunc args + ('args', C.pointer(C.char_p)), + ('dimensions', C.pointer(C.intp)), + ('steps', C.pointer(C.intp)), + ('data', C.void_p), + # specifics for work queues + ('func', C.void_p), + ('num_thread', C.int), + ('workqueues', C.pointer(WorkQueue.llvm_type())), + ] + +class Context(CStruct): + _fields_ = [ + ('common', C.pointer(ContextCommon.llvm_type())), + ('id', C.int), + ('completed', C.intp), + ] + +class ParallelUFunc(CDefinition): + _name_ = 'parallel_ufunc' + _retty_ = C.void + _argtys_ = [ + ('func', C.void_p), + ('worker', C.void_p), + ('args', C.pointer(C.char_p)), + ('dimensions', C.pointer(C.intp)), + ('steps', C.pointer(C.intp)), + ('data', C.void_p), + ] + + def body(self, func, worker, args, dimensions, steps, data, ThreadCount=1): + + + common = self.var(ContextCommon, name='common') + workqueues = self.array(WorkQueue, ThreadCount, name='workqueues') + contexts = self.array(Context, ThreadCount, name='contexts') + + num_thread = self.var(C.int, ThreadCount, name='num_thread') + + common.args.assign(args) + common.dimensions.assign(dimensions) + common.steps.assign(steps) + common.data.assign(data) + common.func.assign(func) + common.num_thread.assign(num_thread.cast(C.int)) + common.workqueues.assign(workqueues.reference()) + + N = dimensions[0] + ChunkSize = self.var_copy(N / num_thread.cast(N.type)) + ChunkSize_NULL = self.constant_null(ChunkSize.type) + with self.ifelse(ChunkSize == ChunkSize_NULL) as ifelse: + with ifelse.then(): + ChunkSize.assign(self.constant(ChunkSize.type, 1)) + num_thread.assign(N.cast(num_thread.type)) + + self._populate_workqueues(workqueues, N, ChunkSize, num_thread) + self._populate_context(contexts, common, num_thread) + + self._dispatch_worker(worker, contexts, num_thread) + + # Check for race condition + total_completed = self.var(C.intp, 0, name='total_completed') + for t in range(ThreadCount): + cur_ctxt = contexts[t].as_struct(Context) + total_completed += cur_ctxt.completed + self.debug(cur_ctxt.id, 'completed', cur_ctxt.completed) + + with self.ifelse( total_completed == N ) as ifelse: + with ifelse.then(): + self.debug("All is well!") + with ifelse.otherwise(): + self.debug("ERROR: race occurred!") + + self.ret() + + def _populate_workqueues(self, workqueues, N, ChunkSize, num_thread): + i = self.var(num_thread.type, 0, name='i') + ONE = self.constant(i.type, 1) + with self.loop() as loop: + with loop.condition() as setcond: + setcond( i < num_thread ) + with loop.body(): + cur_wq = workqueues[i].as_struct(WorkQueue) + cur_wq.next.assign(i.cast(ChunkSize.type) * ChunkSize) + cur_wq.last.assign((i + ONE).cast(ChunkSize.type) * ChunkSize) + cur_wq.lock.assign(self.constant(C.int, 0)) + # increment + i += ONE + # end loop + last_wq = workqueues[num_thread - ONE].as_struct(WorkQueue) + last_wq.last.assign(N) + + def _populate_context(self, contexts, common, num_thread): + i = self.var(num_thread.type, 0, name='i') + ONE = self.constant(i.type, 1) + with self.loop() as loop: + with loop.condition() as setcond: + setcond( i < num_thread ) + with loop.body(): + cur_ctxt = contexts[i].as_struct(Context) + cur_ctxt.common.assign(common.reference()) + cur_ctxt.id.assign(i) + cur_ctxt.completed.assign(self.constant_null( + cur_ctxt.completed.type)) + # increment + i += ONE + + +class ParallelUFuncPosix(ParallelUFunc): + def _dispatch_worker(self, worker, contexts, num_thread): + api = PThreadAPI(self) + NULL = self.constant_null(C.void_p) + + threads = self.array(api.pthread_t, num_thread, name='threads') + + # self.debug("launch threads") + # TODO error handling + + i = self.var(num_thread.type, 0, name='i') + ONE = self.constant(i.type, 1) + with self.loop() as loop: + with loop.condition() as setcond: + setcond( i < num_thread ) + with loop.body(): + api.pthread_create(threads[i].reference(), NULL, worker, + contexts[i].reference().cast(C.void_p)) + i += ONE + + # self.debug("join threads") + i.assign(self.constant_null(i.type)) + with self.loop() as loop: + with loop.condition() as setcond: + setcond( i < num_thread ) + with loop.body(): + api.pthread_join(threads[i], NULL) + i += ONE + # self.debug("closing") + +class UFuncCore(CDefinition): + _name_ = 'ufunc_worker' + _argtys_ = [ + ('context', C.pointer(Context.llvm_type())), + ] + + def body(self, context): + context = context.as_struct(Context) + common = context.common.as_struct(ContextCommon) + tid = context.id + + # self.debug("start thread", tid, "/", common.num_thread) + workqueue = common.workqueues[tid].as_struct(WorkQueue) + + self._do_workqueue(common, workqueue, tid, context.completed) + self._do_work_stealing(common, tid, context.completed) # optional + + self.ret() + + def _do_workqueue(self, common, workqueue, tid, completed): + ''' + Process local workqueue. + ''' + ZERO = self.constant_null(C.int) + + with self.loop() as loop: + with loop.condition() as setcond: + setcond(ZERO == ZERO) # loop forever + + with loop.body(): + workqueue.Lock() + # Critical section + item = self.var_copy(workqueue.next, name='item') + workqueue.next += self.constant(item.type, 1) + last = self.var_copy(workqueue.last, name='last') + # Release + workqueue.Unlock() + + with self.ifelse( item >= last ) as ifelse: + with ifelse.then(): + loop.break_loop() + + self._do_work(common, item, tid, completed) + + def _do_work_stealing(self, common, tid, completed): + ''' + Steal work from other workqueues + ''' + # self.debug("start work stealing", tid) + incomplete_thread_ct = self.var(C.int, 1) + ITC_NULL = self.constant_null(incomplete_thread_ct.type) + with self.loop() as loop: + with loop.condition() as setcond: + setcond( incomplete_thread_ct != ITC_NULL ) + + with loop.body(): + incomplete_thread_ct.assign(ITC_NULL) + self._do_work_stealing_innerloop(common, incomplete_thread_ct, + tid, completed) + + def _do_work_stealing_innerloop(self, common, incomplete_thread_ct, tid, + completed): + i = self.var(C.int, 0) + with self.loop() as loop: + with loop.condition() as setcond: + setcond( i < common.num_thread ) + + with loop.body(): + with self.ifelse( i != tid ) as ifelse: + with ifelse.then(): + otherqueue = common.workqueues[i].as_struct(WorkQueue) + self._do_work_stealing_check(common, otherqueue, + incomplete_thread_ct, tid, + completed) + + # increment + i += self.constant(i.type, 1) + + def _do_work_stealing_check(self, common, otherqueue, + incomplete_thread_ct, tid, completed): + otherqueue.Lock() + ONE = self.constant(otherqueue.last.type, 1) + ITC_ONE = self.constant(incomplete_thread_ct.type, 1) + with self.ifelse(otherqueue.next < otherqueue.last) as ifelse: + with ifelse.then(): + otherqueue.last -= ONE + item = self.var_copy(otherqueue.last) + + otherqueue.Unlock() + # Released + + self._do_work(common, item, tid, completed) + + # Mark incomplete thread + incomplete_thread_ct.assign(ITC_ONE) + + with ifelse.otherwise(): + otherqueue.Unlock() + + def _do_work(self, common, item, tid, completed): + # self.debug(" tid", tid, "item", item) + completed += self.constant(completed.type, 1) + + +class PThreadAPI(CExternal): + pthread_t = C.void_p + + pthread_create = Type.function(C.int, + [C.pointer(pthread_t), # thread_t + C.void_p, # thread attr + C.void_p, # function + C.void_p]) # arg + + pthread_join = Type.function(C.int, [C.void_p, C.void_p]) + + +def _main(): + NUM_THREAD = 2 + module = Module.new(__name__) + + fpm = FunctionPassManager.new(module) + PassManagerBuilder.new().populate(fpm) + + f1 = ParallelUFuncPosix.define(module, ThreadCount=NUM_THREAD) + f2 = UFuncCore.define(module) + #print(module) +# print(f1) + print(f2) + module.verify() + + fpm.run(f1) + fpm.run(f2) + print('optimized'.center(80,'-')) + print(f2) + +if __name__ == '__main__': + _main() + diff --git a/test_parallel_vectorize.py b/test_parallel_vectorize.py new file mode 100644 index 0000000..b3ffb5c --- /dev/null +++ b/test_parallel_vectorize.py @@ -0,0 +1,73 @@ +from parallel_vectorize import * + +''' +void parallel_ufunc(void * func, void * worker, + char **args, npy_intp *dimensions, + npy_intp *steps, void *data) +''' +class Tester(CDefinition): + _name_ = 'tester' + + def body(self): + # depends + module = self.function.module + + ThreadCount = 2 + ArgCount = 2 + WorkCount = 1000000 + + parallel_ufunc = CFunc(self, ParallelUFuncPosix.define(module, + ThreadCount=ThreadCount)) + worker = CFunc(self, UFuncCore.define(module)) + + # real work + NULL = self.constant_null(C.void_p) + + args = self.array(C.char_p, 2, name='args') + + for t in range(ThreadCount): + args_for_thread = self.array(C.double, 4) + args[t].assign(args_for_thread.cast(C.char_p)) + + dims = self.array(C.intp, 1, name='dims') + dims[0].assign(self.constant(C.intp, WorkCount)) + + steps = self.array(C.intp, ArgCount, name='steps') + + for c in range(ArgCount): + steps[c].assign(self.constant(C.intp, 8)) + + parallel_ufunc(NULL, worker.cast(C.void_p), args, dims, steps, NULL) + + self.ret() + +def main(): + NUM_THREAD = 2 + module = Module.new(__name__) + + mpm = PassManager.new() + pmbuilder = PassManagerBuilder.new() + pmbuilder.opt_level = 3 + pmbuilder.populate(mpm) + + fntester = Tester.define(module) + +# print(module) + module.verify() + + mpm.run(module) + + print('optimized'.center(80,'-')) + print(module) + + # run + exe = CExecutor(module) + exe.engine.get_pointer_to_function(fntester) + func = exe.get_ctype_function(fntester, 'void') + + func() + + +if __name__ == '__main__': + main() + From 1594e5456a887caa29e3f2f8628d5bcb27c47590 Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Mon, 6 Aug 2012 11:35:03 -0700 Subject: [PATCH 12/57] Got test_parallel_vectorize working. --- llvm_cbuilder/builder.py | 56 +++++++- parallel_vectorize.py | 267 +++++++++++++++++++------------------ test_parallel_vectorize.py | 72 ++++++++-- 3 files changed, 253 insertions(+), 142 deletions(-) diff --git a/llvm_cbuilder/builder.py b/llvm_cbuilder/builder.py index ee308d6..eaa159e 100644 --- a/llvm_cbuilder/builder.py +++ b/llvm_cbuilder/builder.py @@ -5,6 +5,7 @@ import contextlib import llvm.core as lc import llvm.ee as le +from llvm import LLVMException def _is_int(ty): return isinstance(ty, lc.IntegerType) @@ -245,6 +246,44 @@ class CBuilder(object): yield cb cb.close() + @contextlib.contextmanager + def forever(self): + with self.loop() as loop: + with loop.condition() as setcond: + NULL = self.constant_null(lc.Type.int()) + setcond( NULL == NULL ) + with loop.body(): + yield loop + + @contextlib.contextmanager + def for_range(self, *args): + def check_arg(x): + if isinstance(x, int): + return self.constant(lc.Type.int(), x) + if not x.is_int: + raise TypeError(x, "All args must be of integer type.") + return x + + if len(args) == 3: + start, stop, step = map(check_arg, args) + elif len(args) == 2: + start, stop = map(check_arg, args) + step = self.constant(start.type, 1) + elif len(args) == 1: + stop = check_arg(args[0]) + start = self.constant(stop.type, 0) + step = self.constant(stop.type, 1) + else: + raise TypeError("Invalid # of arguments: 1, 2 or 3") + + idx = self.var_copy(start) + with self.loop() as loop: + with loop.condition() as setcond: + setcond( idx < stop ) + with loop.body(): + yield loop, idx + idx += step + def position_at_end(self, bb): self.basic_block = bb self.builder.position_at_end(bb) @@ -270,13 +309,18 @@ class CBuilder(object): def constant_string(self, string): mod = self.function.module - name = '.conststr.%x' % hash(string) + name = '.conststr.%x_%x' % (hash(string), len(string)) content = lc.Constant.stringz(string) - globalstr = mod.add_global_variable(content.type, name=name) - globalstr.initializer = content - ptr = mod.add_global_variable(lc.Type.pointer(content.type.element), - name=name+".ptr") - return CTemp(self, globalstr.bitcast(lc.Type.pointer(content.type.element))) + try: + globalstr = mod.get_global_variable_named(name) + except LLVMException: + globalstr = mod.add_global_variable(content.type, name=name) + globalstr.initializer = content +# ptr = mod.add_global_variable(lc.Type.pointer(content.type.element), +# name=name+".ptr") + return CTemp(self, globalstr.bitcast( + lc.Type.pointer(content.type.element))) + def get_intrinsic(self, intrinsic_id, tys): lfunc = lc.Function.intrinsic(self.function.module, intrinsic_id, tys) diff --git a/parallel_vectorize.py b/parallel_vectorize.py index 1f83ad5..25176b8 100644 --- a/parallel_vectorize.py +++ b/parallel_vectorize.py @@ -1,3 +1,15 @@ +''' +This file implements the code-generator for parallel-vectorize. + +ParallelUFunc is the platform independent base class for generating +the thread dispatcher. This thread dispatcher launches threads +that execute the generated function of UFuncCore. +UFuncCore is subclassed to specialize for the input/output types. +The actual workload is invoked inside the function generated by UFuncCore. +UFuncCore also defines a work-stealing mechanism that allows idle threads +to steal works from other threads. +''' + from llvm.core import * from llvm.passes import * @@ -5,14 +17,21 @@ from llvm_cbuilder import * import llvm_cbuilder.shortnames as C class WorkQueue(CStruct): + ''' + Structure for workqueue for parallel-ufunc. + ''' + _fields_ = [ - ('next', C.intp), - ('last', C.intp), - ('lock', C.int), + ('next', C.intp), # next index of work item + ('last', C.intp), # last index of work item (exlusive) + ('lock', C.int), # for locking the workqueue ] def Lock(self): + ''' + Inline the lock procedure. + ''' with self.parent.loop() as loop: with loop.condition() as setcond: unlocked = self.parent.constant(self.lock.type, 0) @@ -26,6 +45,9 @@ class WorkQueue(CStruct): pass def Unlock(self): + ''' + Inline the unlock procedure. + ''' unlocked = self.parent.constant(self.lock.type, 0) locked = self.parent.constant(self.lock.type, 1) @@ -39,6 +61,9 @@ class WorkQueue(CStruct): class ContextCommon(CStruct): + ''' + Structure for thread-shared context information in parallel-ufunc. + ''' _fields_ = [ # loop ufunc args ('args', C.pointer(C.char_p)), @@ -52,6 +77,9 @@ class ContextCommon(CStruct): ] class Context(CStruct): + ''' + Structure for thread-specific context information in parallel-ufunc. + ''' _fields_ = [ ('common', C.pointer(ContextCommon.llvm_type())), ('id', C.int), @@ -71,14 +99,14 @@ class ParallelUFunc(CDefinition): ] def body(self, func, worker, args, dimensions, steps, data, ThreadCount=1): - - + # Setup variables common = self.var(ContextCommon, name='common') workqueues = self.array(WorkQueue, ThreadCount, name='workqueues') contexts = self.array(Context, ThreadCount, name='contexts') num_thread = self.var(C.int, ThreadCount, name='num_thread') + # Initialize ContextCommon common.args.assign(args) common.dimensions.assign(dimensions) common.steps.assign(steps) @@ -87,6 +115,11 @@ class ParallelUFunc(CDefinition): common.num_thread.assign(num_thread.cast(C.int)) common.workqueues.assign(workqueues.reference()) + # Determine chunksize, initial count of work-items per thread. + # If total_work >= num_thread, equally divide the works. + # If total_work % num_thread != 0, the last thread does all remaining works. + # If total_work < num_thread, each thread does one work, + # and set num_thread to total_work N = dimensions[0] ChunkSize = self.var_copy(N / num_thread.cast(N.type)) ChunkSize_NULL = self.constant_null(ChunkSize.type) @@ -95,60 +128,70 @@ class ParallelUFunc(CDefinition): ChunkSize.assign(self.constant(ChunkSize.type, 1)) num_thread.assign(N.cast(num_thread.type)) + # Populate workqueue for all threads self._populate_workqueues(workqueues, N, ChunkSize, num_thread) + + # Populate contexts for all threads self._populate_context(contexts, common, num_thread) + # Dispatch worker threads self._dispatch_worker(worker, contexts, num_thread) + ## DEBUG ONLY ## # Check for race condition - total_completed = self.var(C.intp, 0, name='total_completed') - for t in range(ThreadCount): - cur_ctxt = contexts[t].as_struct(Context) - total_completed += cur_ctxt.completed - self.debug(cur_ctxt.id, 'completed', cur_ctxt.completed) + if True: + total_completed = self.var(C.intp, 0, name='total_completed') + for t in range(ThreadCount): + cur_ctxt = contexts[t].as_struct(Context) + total_completed += cur_ctxt.completed + self.debug(cur_ctxt.id, 'completed', cur_ctxt.completed) - with self.ifelse( total_completed == N ) as ifelse: - with ifelse.then(): - self.debug("All is well!") - with ifelse.otherwise(): - self.debug("ERROR: race occurred!") + with self.ifelse( total_completed == N ) as ifelse: + with ifelse.then(): + self.debug("All is well!") + with ifelse.otherwise(): + self.debug("ERROR: race occurred! Trigger segfault") + self.unreachable() + # Return self.ret() def _populate_workqueues(self, workqueues, N, ChunkSize, num_thread): - i = self.var(num_thread.type, 0, name='i') - ONE = self.constant(i.type, 1) - with self.loop() as loop: - with loop.condition() as setcond: - setcond( i < num_thread ) - with loop.body(): - cur_wq = workqueues[i].as_struct(WorkQueue) - cur_wq.next.assign(i.cast(ChunkSize.type) * ChunkSize) - cur_wq.last.assign((i + ONE).cast(ChunkSize.type) * ChunkSize) - cur_wq.lock.assign(self.constant(C.int, 0)) - # increment - i += ONE + ''' + Loop over all threads and populate the workqueue for each of them. + ''' + ONE = self.constant(num_thread.type, 1) + with self.for_range(num_thread) as (loop, i): + cur_wq = workqueues[i].as_struct(WorkQueue) + cur_wq.next.assign(i.cast(ChunkSize.type) * ChunkSize) + cur_wq.last.assign((i + ONE).cast(ChunkSize.type) * ChunkSize) + cur_wq.lock.assign(self.constant(C.int, 0)) # end loop last_wq = workqueues[num_thread - ONE].as_struct(WorkQueue) last_wq.last.assign(N) def _populate_context(self, contexts, common, num_thread): - i = self.var(num_thread.type, 0, name='i') - ONE = self.constant(i.type, 1) - with self.loop() as loop: - with loop.condition() as setcond: - setcond( i < num_thread ) - with loop.body(): - cur_ctxt = contexts[i].as_struct(Context) - cur_ctxt.common.assign(common.reference()) - cur_ctxt.id.assign(i) - cur_ctxt.completed.assign(self.constant_null( - cur_ctxt.completed.type)) - # increment - i += ONE + ''' + Loop over all threads and populate contexts for each of them. + ''' + ONE = self.constant(num_thread.type, 1) + with self.for_range(num_thread) as (loop, i): + cur_ctxt = contexts[i].as_struct(Context) + cur_ctxt.common.assign(common.reference()) + cur_ctxt.id.assign(i) + cur_ctxt.completed.assign( + self.constant_null(cur_ctxt.completed.type)) + def _dispatch_worker(self, worker, contexts, num_thread): + ''' + Dispatch worker threads. + ''' + raise NotImplementedError class ParallelUFuncPosix(ParallelUFunc): + ''' + Implements _dispatch_worker to use pthread. + ''' def _dispatch_worker(self, worker, contexts, num_thread): api = PThreadAPI(self) NULL = self.constant_null(C.void_p) @@ -158,27 +201,19 @@ class ParallelUFuncPosix(ParallelUFunc): # self.debug("launch threads") # TODO error handling - i = self.var(num_thread.type, 0, name='i') - ONE = self.constant(i.type, 1) - with self.loop() as loop: - with loop.condition() as setcond: - setcond( i < num_thread ) - with loop.body(): - api.pthread_create(threads[i].reference(), NULL, worker, - contexts[i].reference().cast(C.void_p)) - i += ONE + ONE = self.constant(num_thread.type, 1) + with self.for_range(num_thread) as (loop, i): + api.pthread_create(threads[i].reference(), NULL, worker, + contexts[i].reference().cast(C.void_p)) - # self.debug("join threads") - i.assign(self.constant_null(i.type)) - with self.loop() as loop: - with loop.condition() as setcond: - setcond( i < num_thread ) - with loop.body(): - api.pthread_join(threads[i], NULL) - i += ONE - # self.debug("closing") + with self.for_range(num_thread) as (loop, i): + api.pthread_join(threads[i], NULL) class UFuncCore(CDefinition): + ''' + Generates the workqueue handling and work stealing and invoke + the work function for each work item. + ''' _name_ = 'ufunc_worker' _argtys_ = [ ('context', C.pointer(Context.llvm_type())), @@ -203,64 +238,61 @@ class UFuncCore(CDefinition): ''' ZERO = self.constant_null(C.int) - with self.loop() as loop: - with loop.condition() as setcond: - setcond(ZERO == ZERO) # loop forever + with self.forever() as loop: + workqueue.Lock() + # Critical section + item = self.var_copy(workqueue.next, name='item') + workqueue.next += self.constant(item.type, 1) + last = self.var_copy(workqueue.last, name='last') + # Release + workqueue.Unlock() - with loop.body(): - workqueue.Lock() - # Critical section - item = self.var_copy(workqueue.next, name='item') - workqueue.next += self.constant(item.type, 1) - last = self.var_copy(workqueue.last, name='last') - # Release - workqueue.Unlock() + with self.ifelse( item >= last ) as ifelse: + with ifelse.then(): + loop.break_loop() - with self.ifelse( item >= last ) as ifelse: - with ifelse.then(): - loop.break_loop() - - self._do_work(common, item, tid, completed) + self._do_work(common, item, tid) + completed += self.constant(completed.type, 1) def _do_work_stealing(self, common, tid, completed): ''' - Steal work from other workqueues + Steal work from other workqueues. ''' # self.debug("start work stealing", tid) - incomplete_thread_ct = self.var(C.int, 1) - ITC_NULL = self.constant_null(incomplete_thread_ct.type) + steal_continue = self.var(C.int, 1) + STEAL_STOP = self.constant_null(steal_continue.type) + + # Loop until all workqueues are done. with self.loop() as loop: with loop.condition() as setcond: - setcond( incomplete_thread_ct != ITC_NULL ) + setcond( steal_continue != STEAL_STOP ) with loop.body(): - incomplete_thread_ct.assign(ITC_NULL) - self._do_work_stealing_innerloop(common, incomplete_thread_ct, - tid, completed) + steal_continue.assign(STEAL_STOP) + self._do_work_stealing_innerloop(common, steal_continue, tid, + completed) - def _do_work_stealing_innerloop(self, common, incomplete_thread_ct, tid, + def _do_work_stealing_innerloop(self, common, steal_continue, tid, completed): - i = self.var(C.int, 0) - with self.loop() as loop: - with loop.condition() as setcond: - setcond( i < common.num_thread ) + ''' + Loop over all other threads and try to steal work. + ''' + with self.for_range(common.num_thread) as (loop, i): + with self.ifelse( i != tid ) as ifelse: + with ifelse.then(): + otherqueue = common.workqueues[i].as_struct(WorkQueue) + self._do_work_stealing_check(common, otherqueue, + steal_continue, tid, + completed) - with loop.body(): - with self.ifelse( i != tid ) as ifelse: - with ifelse.then(): - otherqueue = common.workqueues[i].as_struct(WorkQueue) - self._do_work_stealing_check(common, otherqueue, - incomplete_thread_ct, tid, - completed) - - # increment - i += self.constant(i.type, 1) - - def _do_work_stealing_check(self, common, otherqueue, - incomplete_thread_ct, tid, completed): + def _do_work_stealing_check(self, common, otherqueue, steal_continue, tid, + completed): + ''' + Check the workqueue for any remaining work and steal it. + ''' otherqueue.Lock() ONE = self.constant(otherqueue.last.type, 1) - ITC_ONE = self.constant(incomplete_thread_ct.type, 1) + STEAL_CONTINUE = self.constant(steal_continue.type, 1) with self.ifelse(otherqueue.next < otherqueue.last) as ifelse: with ifelse.then(): otherqueue.last -= ONE @@ -269,17 +301,20 @@ class UFuncCore(CDefinition): otherqueue.Unlock() # Released - self._do_work(common, item, tid, completed) + self._do_work(common, item, tid) + completed += self.constant(completed.type, 1) # Mark incomplete thread - incomplete_thread_ct.assign(ITC_ONE) + steal_continue.assign(STEAL_CONTINUE) with ifelse.otherwise(): otherqueue.Unlock() - def _do_work(self, common, item, tid, completed): - # self.debug(" tid", tid, "item", item) - completed += self.constant(completed.type, 1) + def _do_work(self, common, item, tid): + ''' + Prepare to call the actual work function. + ''' + raise NotImplementedError class PThreadAPI(CExternal): @@ -294,25 +329,3 @@ class PThreadAPI(CExternal): pthread_join = Type.function(C.int, [C.void_p, C.void_p]) -def _main(): - NUM_THREAD = 2 - module = Module.new(__name__) - - fpm = FunctionPassManager.new(module) - PassManagerBuilder.new().populate(fpm) - - f1 = ParallelUFuncPosix.define(module, ThreadCount=NUM_THREAD) - f2 = UFuncCore.define(module) - #print(module) -# print(f1) - print(f2) - module.verify() - - fpm.run(f1) - fpm.run(f2) - print('optimized'.center(80,'-')) - print(f2) - -if __name__ == '__main__': - _main() - diff --git a/test_parallel_vectorize.py b/test_parallel_vectorize.py index b3ffb5c..fbcb133 100644 --- a/test_parallel_vectorize.py +++ b/test_parallel_vectorize.py @@ -1,11 +1,40 @@ from parallel_vectorize import * -''' -void parallel_ufunc(void * func, void * worker, - char **args, npy_intp *dimensions, - npy_intp *steps, void *data) -''' + +class Work_D_D(CDefinition): + _name_ = 'copy_d_d' + _retty_ = C.double + _argtys_ = [ + ('inval', C.double), + ] + def body(self, inval): + self.ret(inval / self.constant(inval.type, 2.345)) + +class UFuncCore_D_D(UFuncCore): + ''' + Specialize UFuncCore for double input, double output. + ''' + def _do_work(self, common, item, tid): + ufunc_type = Type.function(C.double, [C.double]) + ufunc_ptr = CFunc(self, common.func.cast(C.pointer(ufunc_type)).value) + + inbase = common.args[0] + outbase = common.args[1] + + instep = common.steps[0] + outstep = common.steps[0] + + indata = inbase[item * instep].reference().cast(C.pointer(C.double)) + outdata = outbase[item * outstep].reference().cast(C.pointer(C.double)) + + res = ufunc_ptr(indata.load()) + outdata.store(res) + + class Tester(CDefinition): + ''' + Generate test. + ''' _name_ = 'tester' def body(self): @@ -14,20 +43,23 @@ class Tester(CDefinition): ThreadCount = 2 ArgCount = 2 - WorkCount = 1000000 + WorkCount = 10000 parallel_ufunc = CFunc(self, ParallelUFuncPosix.define(module, ThreadCount=ThreadCount)) - worker = CFunc(self, UFuncCore.define(module)) + ufunc_core = CFunc(self, UFuncCore_D_D.define(module)) + worker = CFunc(self, Work_D_D.define(module)) # real work NULL = self.constant_null(C.void_p) args = self.array(C.char_p, 2, name='args') + args_double = [] for t in range(ThreadCount): - args_for_thread = self.array(C.double, 4) + args_for_thread = self.array(C.double, WorkCount) args[t].assign(args_for_thread.cast(C.char_p)) + args_double.append(args_for_thread) dims = self.array(C.intp, 1, name='dims') dims[0].assign(self.constant(C.intp, WorkCount)) @@ -37,7 +69,28 @@ class Tester(CDefinition): for c in range(ArgCount): steps[c].assign(self.constant(C.intp, 8)) - parallel_ufunc(NULL, worker.cast(C.void_p), args, dims, steps, NULL) + # populate data + inbase = args_double[0] + + i = self.var(C.intp, 0) + with self.loop() as loop: + with loop.condition() as setcond: + setcond( i < self.constant(C.intp, WorkCount) ) + with loop.body(): + inbase[i].assign(i.cast(C.double)) + i += self.constant(C.intp, 1) + + # call parallel ufunc + parallel_ufunc(worker.cast(C.void_p), ufunc_core.cast(C.void_p), args, + dims, steps, NULL) + + # check error + outbase = args_double[-1] + with self.for_range(self.constant(C.intp, WorkCount)) as (loop, i): + test = outbase[i] != (inbase[i] / self.constant(C.double, 2.345)) + with self.ifelse( test ) as ifelse: + with ifelse.then(): + self.debug("Invalid data at i =", i, outbase[i], inbase[i]) self.ret() @@ -61,6 +114,7 @@ def main(): print(module) # run + print('run') exe = CExecutor(module) exe.engine.get_pointer_to_function(fntester) func = exe.get_ctype_function(fntester, 'void') From c43b27356be5b4bcaa95babfc6185c1bb0c8be65 Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Mon, 6 Aug 2012 14:23:43 -0700 Subject: [PATCH 13/57] Fix --- llvm_cbuilder/builder.py | 2 +- test_parallel_vectorize.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/llvm_cbuilder/builder.py b/llvm_cbuilder/builder.py index eaa159e..661c2b1 100644 --- a/llvm_cbuilder/builder.py +++ b/llvm_cbuilder/builder.py @@ -429,7 +429,7 @@ class CDefinition(CBuilder): functype = lc.Type.function(cls._retty_, [v for k, v in cls._argtys_]) func = module.get_or_insert_function(functype, name=cls._name_) if not func.is_declaration: # already defined? - raise NameError('Function %s has already been defined' % cls.name) + raise NameError('Function %s has already been defined' % cls._name_) # Name all arguments for i, (name, _) in enumerate(cls._argtys_): diff --git a/test_parallel_vectorize.py b/test_parallel_vectorize.py index fbcb133..fd7cc52 100644 --- a/test_parallel_vectorize.py +++ b/test_parallel_vectorize.py @@ -95,7 +95,6 @@ class Tester(CDefinition): self.ret() def main(): - NUM_THREAD = 2 module = Module.new(__name__) mpm = PassManager.new() From fef9c2efbf50d194a9e715f152fd7627fe3870fa Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Mon, 6 Aug 2012 14:46:27 -0700 Subject: [PATCH 14/57] Add CBuilder.depends to automatically insert depending function. --- llvm_cbuilder/builder.py | 9 ++++++++- parallel_vectorize.py | 27 +++++++++++++++++++-------- test_parallel_vectorize.py | 10 ++++++---- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/llvm_cbuilder/builder.py b/llvm_cbuilder/builder.py index 661c2b1..3fce585 100644 --- a/llvm_cbuilder/builder.py +++ b/llvm_cbuilder/builder.py @@ -149,6 +149,13 @@ class CBuilder(object): func = mod.add_function(functype, name=name) return CBuilder(func) + def depends(self, cdef, **kwargs): + try: + func = cdef.define(self.function.module, **kwargs) + except NameError as e: + (func,) = e + return CFunc(self, func) + def printf(self, fmt, *args): mod = self.function.module int_t = lc.Type.int() @@ -429,7 +436,7 @@ class CDefinition(CBuilder): functype = lc.Type.function(cls._retty_, [v for k, v in cls._argtys_]) func = module.get_or_insert_function(functype, name=cls._name_) if not func.is_declaration: # already defined? - raise NameError('Function %s has already been defined' % cls._name_) + raise NameError(func) # Name all arguments for i, (name, _) in enumerate(cls._argtys_): diff --git a/parallel_vectorize.py b/parallel_vectorize.py index 25176b8..42aea26 100644 --- a/parallel_vectorize.py +++ b/parallel_vectorize.py @@ -87,8 +87,15 @@ class Context(CStruct): ] class ParallelUFunc(CDefinition): + ''' + Platform dependent threading function is implemented in + + def _dispatch_worker(self, worker, contexts, num_thread): + ... + + which should be implemented in subclass or mixin. + ''' _name_ = 'parallel_ufunc' - _retty_ = C.void _argtys_ = [ ('func', C.void_p), ('worker', C.void_p), @@ -182,13 +189,7 @@ class ParallelUFunc(CDefinition): cur_ctxt.completed.assign( self.constant_null(cur_ctxt.completed.type)) - def _dispatch_worker(self, worker, contexts, num_thread): - ''' - Dispatch worker threads. - ''' - raise NotImplementedError - -class ParallelUFuncPosix(ParallelUFunc): +class ParallelUFuncPosixMixin(object): ''' Implements _dispatch_worker to use pthread. ''' @@ -316,6 +317,16 @@ class UFuncCore(CDefinition): ''' raise NotImplementedError +class _SpecializedParallelUFunc(ParallelUFunc, ParallelUFuncPosixMixin): + _argtys_ = [ + ('args', C.pointer(C.char_p)), + ('dimensions', C.pointer(C.intp)), + ('steps', C.pointer(C.intp)), + ('data', C.void_p), + ] + + def body(self, args, dimensions, steps, data, ThreadCount=1): + pass class PThreadAPI(CExternal): pthread_t = C.void_p diff --git a/test_parallel_vectorize.py b/test_parallel_vectorize.py index fd7cc52..4fde372 100644 --- a/test_parallel_vectorize.py +++ b/test_parallel_vectorize.py @@ -30,6 +30,8 @@ class UFuncCore_D_D(UFuncCore): res = ufunc_ptr(indata.load()) outdata.store(res) +class ParallelUFuncPosix(ParallelUFunc, ParallelUFuncPosixMixin): + pass class Tester(CDefinition): ''' @@ -45,10 +47,10 @@ class Tester(CDefinition): ArgCount = 2 WorkCount = 10000 - parallel_ufunc = CFunc(self, ParallelUFuncPosix.define(module, - ThreadCount=ThreadCount)) - ufunc_core = CFunc(self, UFuncCore_D_D.define(module)) - worker = CFunc(self, Work_D_D.define(module)) + parallel_ufunc = self.depends(ParallelUFuncPosix, + ThreadCount=ThreadCount) + ufunc_core = self.depends(UFuncCore_D_D) + worker = self.depends(Work_D_D) # real work NULL = self.constant_null(C.void_p) From 150f497ed08be2d5ec82a607513a3bdbefe2dc02 Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Mon, 6 Aug 2012 15:33:36 -0700 Subject: [PATCH 15/57] Add specializable parallel ufunc. Update test. Some fixes. --- llvm_cbuilder/builder.py | 3 ++- parallel_vectorize.py | 16 ++++++++++++---- test_parallel_vectorize.py | 18 ++++++++++++------ 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/llvm_cbuilder/builder.py b/llvm_cbuilder/builder.py index 3fce585..88516df 100644 --- a/llvm_cbuilder/builder.py +++ b/llvm_cbuilder/builder.py @@ -434,7 +434,8 @@ class CDefinition(CBuilder): @classmethod def define(cls, module, **kws): functype = lc.Type.function(cls._retty_, [v for k, v in cls._argtys_]) - func = module.get_or_insert_function(functype, name=cls._name_) + name = cls._name_ % kws + func = module.get_or_insert_function(functype, name=name) if not func.is_declaration: # already defined? raise NameError(func) diff --git a/parallel_vectorize.py b/parallel_vectorize.py index 42aea26..2d9421f 100644 --- a/parallel_vectorize.py +++ b/parallel_vectorize.py @@ -95,7 +95,7 @@ class ParallelUFunc(CDefinition): which should be implemented in subclass or mixin. ''' - _name_ = 'parallel_ufunc' + _name_ = 'parallel_ufunc_%(ThreadCount)d' _argtys_ = [ ('func', C.void_p), ('worker', C.void_p), @@ -317,7 +317,9 @@ class UFuncCore(CDefinition): ''' raise NotImplementedError -class _SpecializedParallelUFunc(ParallelUFunc, ParallelUFuncPosixMixin): + +class SpecializedParallelUFunc(CDefinition): + _name_ = 'specialized_parallel_ufunc_%(ThreadCount)d_%(FuncName)s' _argtys_ = [ ('args', C.pointer(C.char_p)), ('dimensions', C.pointer(C.intp)), @@ -325,8 +327,14 @@ class _SpecializedParallelUFunc(ParallelUFunc, ParallelUFuncPosixMixin): ('data', C.void_p), ] - def body(self, args, dimensions, steps, data, ThreadCount=1): - pass + def body(self, args, dimensions, steps, data, + PUFuncDef, CoreDef, Func, FuncName, ThreadCount=1): + pufunc = self.depends(PUFuncDef, ThreadCount=ThreadCount) + core = self.depends(CoreDef) + func = self.depends(Func) + to_void_p = lambda x: x.cast(C.void_p) + pufunc(to_void_p(func), to_void_p(core), args, dimensions, steps, data) + self.ret() class PThreadAPI(CExternal): pthread_t = C.void_p diff --git a/test_parallel_vectorize.py b/test_parallel_vectorize.py index 4fde372..e16d206 100644 --- a/test_parallel_vectorize.py +++ b/test_parallel_vectorize.py @@ -47,10 +47,17 @@ class Tester(CDefinition): ArgCount = 2 WorkCount = 10000 - parallel_ufunc = self.depends(ParallelUFuncPosix, - ThreadCount=ThreadCount) - ufunc_core = self.depends(UFuncCore_D_D) - worker = self.depends(Work_D_D) +# parallel_ufunc = self.depends(ParallelUFuncPosix, +# ThreadCount=ThreadCount) +# ufunc_core = self.depends(UFuncCore_D_D) +# worker = self.depends(Work_D_D) + + sppufunc = self.depends(SpecializedParallelUFunc, + PUFuncDef = ParallelUFuncPosix, + CoreDef = UFuncCore_D_D, + Func = Work_D_D, + FuncName = Work_D_D._name_, + ThreadCount = 2) # real work NULL = self.constant_null(C.void_p) @@ -83,8 +90,7 @@ class Tester(CDefinition): i += self.constant(C.intp, 1) # call parallel ufunc - parallel_ufunc(worker.cast(C.void_p), ufunc_core.cast(C.void_p), args, - dims, steps, NULL) + sppufunc(args, dims, steps, NULL) # check error outbase = args_double[-1] From b5893d4ebb69fe920ae432db7e598aada5de8cda Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Mon, 6 Aug 2012 17:02:04 -0700 Subject: [PATCH 16/57] Add test to use numpy.fromfunc. Depends on numpy branch "ufunc-from-function-pointer" in git://github.com/jayvius/numpy.git --- parallel_vectorize.py | 5 ++-- test_parallel_vectorize.py | 11 +++----- test_parallel_vectorize_numpy.py | 46 ++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 test_parallel_vectorize_numpy.py diff --git a/parallel_vectorize.py b/parallel_vectorize.py index 2d9421f..bef04eb 100644 --- a/parallel_vectorize.py +++ b/parallel_vectorize.py @@ -151,11 +151,12 @@ class ParallelUFunc(CDefinition): for t in range(ThreadCount): cur_ctxt = contexts[t].as_struct(Context) total_completed += cur_ctxt.completed - self.debug(cur_ctxt.id, 'completed', cur_ctxt.completed) + # self.debug(cur_ctxt.id, 'completed', cur_ctxt.completed) with self.ifelse( total_completed == N ) as ifelse: with ifelse.then(): - self.debug("All is well!") + # self.debug("All is well!") + pass # keep quite if all is well with ifelse.otherwise(): self.debug("ERROR: race occurred! Trigger segfault") self.unreachable() diff --git a/test_parallel_vectorize.py b/test_parallel_vectorize.py index e16d206..ed2010d 100644 --- a/test_parallel_vectorize.py +++ b/test_parallel_vectorize.py @@ -2,7 +2,7 @@ from parallel_vectorize import * class Work_D_D(CDefinition): - _name_ = 'copy_d_d' + _name_ = 'work_d_d' _retty_ = C.double _argtys_ = [ ('inval', C.double), @@ -47,11 +47,6 @@ class Tester(CDefinition): ArgCount = 2 WorkCount = 10000 -# parallel_ufunc = self.depends(ParallelUFuncPosix, -# ThreadCount=ThreadCount) -# ufunc_core = self.depends(UFuncCore_D_D) -# worker = self.depends(Work_D_D) - sppufunc = self.depends(SpecializedParallelUFunc, PUFuncDef = ParallelUFuncPosix, CoreDef = UFuncCore_D_D, @@ -123,11 +118,11 @@ def main(): # run print('run') exe = CExecutor(module) - exe.engine.get_pointer_to_function(fntester) func = exe.get_ctype_function(fntester, 'void') func() - + # Will not reach here is race condition occurred + print('Good') if __name__ == '__main__': main() diff --git a/test_parallel_vectorize_numpy.py b/test_parallel_vectorize_numpy.py new file mode 100644 index 0000000..dc3557f --- /dev/null +++ b/test_parallel_vectorize_numpy.py @@ -0,0 +1,46 @@ +from test_parallel_vectorize import * + +import numpy as np + +def main(): + module = Module.new(__name__) + sppufunc = SpecializedParallelUFunc.define(module, + PUFuncDef = ParallelUFuncPosix, + CoreDef = UFuncCore_D_D, + Func = Work_D_D, + FuncName = Work_D_D._name_, + ThreadCount = 2) + module.verify() + + mpm = PassManager.new() + pmbuilder = PassManagerBuilder.new() + pmbuilder.opt_level = 3 + pmbuilder.populate(mpm) + + mpm.run(module) +# print module + + # run + + exe = CExecutor(module) + funcptr = exe.engine.get_pointer_to_function(sppufunc) + print("Function pointer: %x" % funcptr) + + ptr_t = long # py2 only + + typenum = np.dtype(np.double).num + ufunc = np.fromfunc([ptr_t(funcptr)], [[typenum, typenum]], 1, 1, [None]) + + x = np.linspace(0., 10., 1000) + x.dtype=np.double +# print x + ans = ufunc(x) +# print ans + + if not ( ans == x/2.345 ).all(): + raise ValueError('Computation failed') + else: + print('Good') + +if __name__ == '__main__': + main() From 01054c726fd9f0f193c8b2842f7c01531384e7ce Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Mon, 6 Aug 2012 20:25:36 -0700 Subject: [PATCH 17/57] Add specialize() for cleaner specialization of CDefinition. --- parallel_vectorize.py | 38 ++++++++++++++++++++++++-------- test_parallel_vectorize.py | 11 +++++---- test_parallel_vectorize_numpy.py | 11 +++++---- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/parallel_vectorize.py b/parallel_vectorize.py index bef04eb..b5a03f5 100644 --- a/parallel_vectorize.py +++ b/parallel_vectorize.py @@ -95,7 +95,7 @@ class ParallelUFunc(CDefinition): which should be implemented in subclass or mixin. ''' - _name_ = 'parallel_ufunc_%(ThreadCount)d' + #_name_ = 'parallel_ufunc_%(ThreadCount)d' _argtys_ = [ ('func', C.void_p), ('worker', C.void_p), @@ -105,8 +105,18 @@ class ParallelUFunc(CDefinition): ('data', C.void_p), ] - def body(self, func, worker, args, dimensions, steps, data, ThreadCount=1): + @classmethod + def specialize(cls, num_thread): + name = 'parallel_ufunc_%d' % num_thread + newcls = type(name, (cls,), { + '_name_' : name, + 'ThreadCount': num_thread, + }) + return newcls + + def body(self, func, worker, args, dimensions, steps, data): # Setup variables + ThreadCount = self.ThreadCount common = self.var(ContextCommon, name='common') workqueues = self.array(WorkQueue, ThreadCount, name='workqueues') contexts = self.array(Context, ThreadCount, name='contexts') @@ -318,9 +328,7 @@ class UFuncCore(CDefinition): ''' raise NotImplementedError - class SpecializedParallelUFunc(CDefinition): - _name_ = 'specialized_parallel_ufunc_%(ThreadCount)d_%(FuncName)s' _argtys_ = [ ('args', C.pointer(C.char_p)), ('dimensions', C.pointer(C.intp)), @@ -328,15 +336,27 @@ class SpecializedParallelUFunc(CDefinition): ('data', C.void_p), ] - def body(self, args, dimensions, steps, data, - PUFuncDef, CoreDef, Func, FuncName, ThreadCount=1): - pufunc = self.depends(PUFuncDef, ThreadCount=ThreadCount) - core = self.depends(CoreDef) - func = self.depends(Func) + def body(self, args, dimensions, steps, data,): + pufunc = self.depends(self.PUFuncDef) + core = self.depends(self.CoreDef) + func = self.depends(self.FuncDef) to_void_p = lambda x: x.cast(C.void_p) pufunc(to_void_p(func), to_void_p(core), args, dimensions, steps, data) self.ret() + @classmethod + def specialize(cls, pufunc_def, core_def, func_def): + name = 'specialized_%s_%s_%s'% (pufunc_def._name_, + core_def._name_, + func_def._name_) + newcls = type(name, (cls,), { + '_name_' : name, + 'PUFuncDef': pufunc_def, + 'CoreDef' : core_def, + 'FuncDef' : func_def, + }) + return newcls + class PThreadAPI(CExternal): pthread_t = C.void_p diff --git a/test_parallel_vectorize.py b/test_parallel_vectorize.py index ed2010d..ab28363 100644 --- a/test_parallel_vectorize.py +++ b/test_parallel_vectorize.py @@ -14,6 +14,7 @@ class UFuncCore_D_D(UFuncCore): ''' Specialize UFuncCore for double input, double output. ''' + _name_ = UFuncCore._name_ + '_d_d' def _do_work(self, common, item, tid): ufunc_type = Type.function(C.double, [C.double]) ufunc_ptr = CFunc(self, common.func.cast(C.pointer(ufunc_type)).value) @@ -47,12 +48,10 @@ class Tester(CDefinition): ArgCount = 2 WorkCount = 10000 - sppufunc = self.depends(SpecializedParallelUFunc, - PUFuncDef = ParallelUFuncPosix, - CoreDef = UFuncCore_D_D, - Func = Work_D_D, - FuncName = Work_D_D._name_, - ThreadCount = 2) + PUfuncDef = ParallelUFuncPosix.specialize(num_thread=2) + SPUF = SpecializedParallelUFunc.specialize( + PUfuncDef, UFuncCore_D_D, Work_D_D) + sppufunc = self.depends(SPUF) # real work NULL = self.constant_null(C.void_p) diff --git a/test_parallel_vectorize_numpy.py b/test_parallel_vectorize_numpy.py index dc3557f..7b1ca51 100644 --- a/test_parallel_vectorize_numpy.py +++ b/test_parallel_vectorize_numpy.py @@ -4,12 +4,11 @@ import numpy as np def main(): module = Module.new(__name__) - sppufunc = SpecializedParallelUFunc.define(module, - PUFuncDef = ParallelUFuncPosix, - CoreDef = UFuncCore_D_D, - Func = Work_D_D, - FuncName = Work_D_D._name_, - ThreadCount = 2) + PUfuncDef = ParallelUFuncPosix.specialize(num_thread=2) + SPUF = SpecializedParallelUFunc.specialize( + PUfuncDef, UFuncCore_D_D, Work_D_D) + sppufunc = SPUF.define(module) + module.verify() mpm = PassManager.new() From 2acc0709845cdaa571c110c13e9cbaabe229e90f Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Tue, 7 Aug 2012 16:55:33 -0700 Subject: [PATCH 18/57] Refactor. Modify the way CDefinition is specialized. --- llvm_cbuilder/builder.py | 92 +++++++++++++++++++++++--------- parallel_vectorize.py | 25 +++------ test_parallel_vectorize.py | 10 ++-- test_parallel_vectorize_numpy.py | 20 +++++-- 4 files changed, 96 insertions(+), 51 deletions(-) diff --git a/llvm_cbuilder/builder.py b/llvm_cbuilder/builder.py index 88516df..e591e9d 100644 --- a/llvm_cbuilder/builder.py +++ b/llvm_cbuilder/builder.py @@ -6,6 +6,7 @@ import contextlib import llvm.core as lc import llvm.ee as le from llvm import LLVMException +from . import shortnames as types def _is_int(ty): return isinstance(ty, lc.IntegerType) @@ -149,18 +150,12 @@ class CBuilder(object): func = mod.add_function(functype, name=name) return CBuilder(func) - def depends(self, cdef, **kwargs): - try: - func = cdef.define(self.function.module, **kwargs) - except NameError as e: - (func,) = e - return CFunc(self, func) + def depends(self, fndecl): + return CFunc(self, fndecl(self.function.module)) def printf(self, fmt, *args): mod = self.function.module - int_t = lc.Type.int() - char_ptr_t = lc.Type.pointer(lc.Type.int(8)) - functype = lc.Type.function(int_t, [char_ptr_t], True) + functype = lc.Type.function(types.int, [types.char_p], True) printf = mod.get_or_insert_function(functype, name='printf') ret = self.builder.call(printf, [fmt.value]+_list_values(args)) return CTemp(self, ret) @@ -224,7 +219,7 @@ class CBuilder(object): if isinstance(count, CValue): count = count.value elif not isinstance(count, lc.Value): - count = self.constant(lc.Type.int(), count).value + count = self.constant(types.int, count).value ptr = self.builder.alloca_array(ty, count, name=name) return CArray(self, ptr) @@ -236,7 +231,7 @@ class CBuilder(object): raise TypeError(errmsg) self.builder.ret(val.value) else: - if retty != lc.Type.void(): + if retty != types.void: errmsg = "Cannot return void" raise TypeError(errmsg) self.builder.ret_void() @@ -257,7 +252,7 @@ class CBuilder(object): def forever(self): with self.loop() as loop: with loop.condition() as setcond: - NULL = self.constant_null(lc.Type.int()) + NULL = self.constant_null(types.int) setcond( NULL == NULL ) with loop.body(): yield loop @@ -266,7 +261,7 @@ class CBuilder(object): def for_range(self, *args): def check_arg(x): if isinstance(x, int): - return self.constant(lc.Type.int(), x) + return self.constant(types.int, x) if not x.is_int: raise TypeError(x, "All args must be of integer type.") return x @@ -303,7 +298,7 @@ class CBuilder(object): def constant(self, ty, val): if isinstance(ty, lc.IntegerType): res = lc.Constant.int(ty, val) - elif ty==lc.Type.float() or ty==lc.Type.double(): + elif ty == types.float or ty == types.double: res = lc.Constant.real(ty, val) else: raise TypeError("Cannot auto build constant " @@ -316,6 +311,8 @@ class CBuilder(object): def constant_string(self, string): mod = self.function.module + # TODO + # Is this a safe way for uniquing identifying string name = '.conststr.%x_%x' % (hash(string), len(string)) content = lc.Constant.stringz(string) try: @@ -323,10 +320,8 @@ class CBuilder(object): except LLVMException: globalstr = mod.add_global_variable(content.type, name=name) globalstr.initializer = content -# ptr = mod.add_global_variable(lc.Type.pointer(content.type.element), -# name=name+".ptr") return CTemp(self, globalstr.bitcast( - lc.Type.pointer(content.type.element))) + types.pointer(content.type.element))) def get_intrinsic(self, intrinsic_id, tys): @@ -423,18 +418,62 @@ class CBuilder(object): def unreachable(self): self.builder.unreachable() +class _DeclareCDef(object): + def __init__(self, cdef): + self.cdef = cdef + + def __str__(self): + return self.cdef._name_ + + def __call__(self, module): + try: + func = self.cdef.define(module) + except NameError as e: + (func,) = e + return func + +class CDeclare(object): + def __init__(self, name, ty, ptr): + self._name = name + self._type = ty + self._ptr = ptr + + def __call__(self, module): + fnptr = types.pointer(self._type) + ptr = lc.Constant.int(types.intp, self._ptr) + ptr = ptr.inttoptr(fnptr) + return ptr + + def __str__(self): + return self._name + class CDefinition(CBuilder): ''' Inherit this class to for defining functions ''' - _name_ = 'unamed' # name of the function; should overide in subclass - _retty_ = lc.Type.void() # return type; can overide in subclass + _name_ = '' # name of the function; should overide in subclass + _retty_ = types.void # return type; can overide in subclass _argtys_ = [] # a list of tuple(name, type); can overide in subclass + def __new__(cls, *args, **kws): + try: + fnsp = getattr(cls, 'specialize') + except AttributeError: + pass + else: + cls = type('%s_Specialized' % cls.__name__, (cls,), {}) + fnsp(*args, **kws) + + obj = object.__new__(_DeclareCDef) + obj.__init__(cls) + return obj + @classmethod - def define(cls, module, **kws): + def define(cls, module): functype = lc.Type.function(cls._retty_, [v for k, v in cls._argtys_]) - name = cls._name_ % kws + name = cls._name_ + if not name: + raise NameError("Function name cannot be empty.") func = module.get_or_insert_function(functype, name=name) if not func.is_declaration: # already defined? raise NameError(func) @@ -444,8 +483,9 @@ class CDefinition(CBuilder): func.args[i].name = name # Create builder and populate body - cbuilder = cls(func) - cbuilder.body(*cbuilder.args, **kws) + cbuilder = object.__new__(cls) + cbuilder.__init__(func) + cbuilder.body(*cbuilder.args) cbuilder.close() return func @@ -662,7 +702,7 @@ class CValue(object): def __getitem__(self, idx): self._ensure_is_pointer() if not isinstance(idx, CValue): - idx = self.parent.constant(lc.Type.int(), idx) + idx = self.parent.constant(types.int, idx) bldr = self.parent.builder ptr = bldr.gep(self.value, [idx.value]) return CVar(self.parent, ptr) @@ -827,7 +867,7 @@ class CArray(CValue): # if isinstance(idx, CValue): # idx = idx.value # elif not isinstance(idx, lc.Value): -# idx = self.parent.constant(lc.Type.int(), idx).value +# idx = self.parent.constant(types.int, idx).value # ptr = builder.gep(self.value, [idx]) # return CVar(self.parent, ptr) @@ -838,7 +878,7 @@ class CStruct(CValue): def __init__(self, parent, ptr): super(CStruct, self).__init__(parent) - makeind = lambda x: self.parent.constant(lc.Type.int(), x).value + makeind = lambda x: self.parent.constant(types.int, x).value self.ptr = ptr for i, (fd, _) in enumerate(self._fields_): gep = self.parent.builder.gep(ptr, [makeind(0), makeind(i)]) diff --git a/parallel_vectorize.py b/parallel_vectorize.py index b5a03f5..4c8daab 100644 --- a/parallel_vectorize.py +++ b/parallel_vectorize.py @@ -95,7 +95,7 @@ class ParallelUFunc(CDefinition): which should be implemented in subclass or mixin. ''' - #_name_ = 'parallel_ufunc_%(ThreadCount)d' + _argtys_ = [ ('func', C.void_p), ('worker', C.void_p), @@ -107,12 +107,9 @@ class ParallelUFunc(CDefinition): @classmethod def specialize(cls, num_thread): - name = 'parallel_ufunc_%d' % num_thread - newcls = type(name, (cls,), { - '_name_' : name, - 'ThreadCount': num_thread, - }) - return newcls + print num_thread + cls._name_ = 'parallel_ufunc_%d' % num_thread + cls.ThreadCount = num_thread def body(self, func, worker, args, dimensions, steps, data): # Setup variables @@ -346,16 +343,10 @@ class SpecializedParallelUFunc(CDefinition): @classmethod def specialize(cls, pufunc_def, core_def, func_def): - name = 'specialized_%s_%s_%s'% (pufunc_def._name_, - core_def._name_, - func_def._name_) - newcls = type(name, (cls,), { - '_name_' : name, - 'PUFuncDef': pufunc_def, - 'CoreDef' : core_def, - 'FuncDef' : func_def, - }) - return newcls + cls._name_ = 'specialized_%s_%s_%s'% (pufunc_def, core_def, func_def) + cls.PUFuncDef = pufunc_def + cls.CoreDef = core_def + cls.FuncDef = func_def class PThreadAPI(CExternal): pthread_t = C.void_p diff --git a/test_parallel_vectorize.py b/test_parallel_vectorize.py index ab28363..54d6b14 100644 --- a/test_parallel_vectorize.py +++ b/test_parallel_vectorize.py @@ -48,10 +48,12 @@ class Tester(CDefinition): ArgCount = 2 WorkCount = 10000 - PUfuncDef = ParallelUFuncPosix.specialize(num_thread=2) - SPUF = SpecializedParallelUFunc.specialize( - PUfuncDef, UFuncCore_D_D, Work_D_D) - sppufunc = self.depends(SPUF) + + spufdef = SpecializedParallelUFunc(ParallelUFuncPosix(num_thread=2), + UFuncCore_D_D(), + Work_D_D()) + + sppufunc = self.depends(spufdef) # real work NULL = self.constant_null(C.void_p) diff --git a/test_parallel_vectorize_numpy.py b/test_parallel_vectorize_numpy.py index 7b1ca51..392c81d 100644 --- a/test_parallel_vectorize_numpy.py +++ b/test_parallel_vectorize_numpy.py @@ -1,13 +1,20 @@ +''' +Test parallel-vectorize with numpy.fromfunc. +Uses the work load from test_parallel_vectorize. +''' + from test_parallel_vectorize import * import numpy as np def main(): module = Module.new(__name__) - PUfuncDef = ParallelUFuncPosix.specialize(num_thread=2) - SPUF = SpecializedParallelUFunc.specialize( - PUfuncDef, UFuncCore_D_D, Work_D_D) - sppufunc = SPUF.define(module) + + spufdef = SpecializedParallelUFunc(ParallelUFuncPosix(num_thread=2), + UFuncCore_D_D(), + Work_D_D()) + + sppufunc = spufdef(module) module.verify() @@ -27,6 +34,11 @@ def main(): ptr_t = long # py2 only + # Becareful that fromfunc does not provide full error checking yet. + # If typenum is out-of-bound, we have nasty memory corruptions. + # For instance, -1 for typenum will cause segfault. + # If elements of type-list (2nd arg) is tuple instead, + # there will also memory corruption. (Seems like code rewrite.) typenum = np.dtype(np.double).num ufunc = np.fromfunc([ptr_t(funcptr)], [[typenum, typenum]], 1, 1, [None]) From 8b45094e20c69f8a5c9aa9ca74de40763d27b8f2 Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Tue, 7 Aug 2012 16:56:28 -0700 Subject: [PATCH 19/57] Add test that uses function pointer. --- test_parallel_vectorize_numpy_2.py | 69 ++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 test_parallel_vectorize_numpy_2.py diff --git a/test_parallel_vectorize_numpy_2.py b/test_parallel_vectorize_numpy_2.py new file mode 100644 index 0000000..a511980 --- /dev/null +++ b/test_parallel_vectorize_numpy_2.py @@ -0,0 +1,69 @@ +''' +Test parallel-vectorize with numpy.fromfunc. +Uses the work load from test_parallel_vectorize. +This time we pass a function pointer. +''' + +from test_parallel_vectorize import * + +import numpy as np + +def main(): + module = Module.new(__name__) + exe = CExecutor(module) + + workdef = Work_D_D() + workfunc = workdef(module) + + # get pointer to workfunc + workfunc_ptr = exe.engine.get_pointer_to_function(workfunc) + + workdecl = CDeclare(workfunc.name, workfunc.type.pointee, workfunc_ptr) + + spufdef = SpecializedParallelUFunc(ParallelUFuncPosix(num_thread=2), + UFuncCore_D_D(), + workdecl) + + + sppufunc = spufdef(module) + sppufunc.verify() + print(sppufunc) + module.verify() + + mpm = PassManager.new() + pmbuilder = PassManagerBuilder.new() + pmbuilder.opt_level = 3 + pmbuilder.populate(mpm) + + mpm.run(module) + print(module) + + + # run + + funcptr = exe.engine.get_pointer_to_function(sppufunc) + print("Function pointer: %x" % funcptr) + + ptr_t = long # py2 only + + # Becareful that fromfunc does not provide full error checking yet. + # If typenum is out-of-bound, we have nasty memory corruptions. + # For instance, -1 for typenum will cause segfault. + # If elements of type-list (2nd arg) is tuple instead, + # there will also memory corruption. (Seems like code rewrite.) + typenum = np.dtype(np.double).num + ufunc = np.fromfunc([ptr_t(funcptr)], [[typenum, typenum]], 1, 1, [None]) + + x = np.linspace(0., 10., 1000) + x.dtype=np.double +# print x + ans = ufunc(x) +# print ans + + if not ( ans == x/2.345 ).all(): + raise ValueError('Computation failed') + else: + print('Good') + +if __name__ == '__main__': + main() From 68e2359ad2ddefba0dca44831e1c5c53a89122fb Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Wed, 8 Aug 2012 15:17:31 -0700 Subject: [PATCH 20/57] cleanup and doc --- llvm_cbuilder/builder.py | 423 ++++++++++++++++++++++++++++++++----- llvm_cbuilder/executor.py | 14 ++ llvm_cbuilder/libc.py | 8 + tests/test_cstr_collide.py | 17 ++ 4 files changed, 406 insertions(+), 56 deletions(-) create mode 100644 llvm_cbuilder/libc.py create mode 100644 tests/test_cstr_collide.py diff --git a/llvm_cbuilder/builder.py b/llvm_cbuilder/builder.py index e591e9d..99133e6 100644 --- a/llvm_cbuilder/builder.py +++ b/llvm_cbuilder/builder.py @@ -8,6 +8,10 @@ import llvm.ee as le from llvm import LLVMException from . import shortnames as types +### +# Utilities +### + def _is_int(ty): return isinstance(ty, lc.IntegerType) @@ -47,6 +51,19 @@ def _change_block_temporarily_dummy(*args): yield class _IfElse(object): + '''if-else construct. + + Example + ------- + with cbuilder.ifelse(cond) as ifelse: + with ifelse.then(): + # code when cond is true + # this block is mandatory + with ifelse.otherwise(): + # code when cond is false + # this block is optional + ''' + def __init__(self, parent, cond): self.parent = parent self.cond = cond @@ -84,6 +101,22 @@ class _IfElse(object): builder.position_at_end(bbend) class _Loop(object): + '''while...do loop. + + Example + ------- + with cbuilder.loop() as loop: + with loop.condition() as setcond: + # Put the condition evaluation here + setcond( cond ) # set loop condition + # Do not put code after setcond(...) + with loop.body(): + # Put the code of the loop body here + + Use loop.break_loop() to break out of the loop. + Use loop.continue_loop() to jump the condition evaulation. + ''' + def __init__(self, parent): self.parent = parent @@ -107,9 +140,8 @@ class _Loop(object): def body(self): builder = self.parent.builder builder.position_at_end(self._bbbody) - yield self - + # close last block if not _is_block_terminated(builder.basic_block): builder.branch(self._bbcond) @@ -121,9 +153,6 @@ class _Loop(object): def close(self): builder = self.parent.builder -# if not _is_block_terminated(builder.basic_block): -# with _change_block_temporarily(builder, builder.basic_block): -# builder.branch(self._bbend) builder.position_at_end(self._bbend) class CBuilder(object): @@ -131,14 +160,18 @@ class CBuilder(object): A wrapper class for features in llvm-py package to allow user to use C-like high-level language contruct easily. ''' + def __init__(self, function): + '''constructor + + function : is an empty function to be populating. + ''' self.function = function self.declare_block = self.function.append_basic_block('decl') self.first_body_block = self.function.append_basic_block('body') self.builder = lc.Builder.new(self.first_body_block) self.target_data = le.TargetData.new(self.function.module.data_layout) - - # prepare arguments + # Prepare arguments. Make all function arguments behave like variables. self.args = [] for arg in function.args: var = self.var(arg.type, arg, name=arg.name) @@ -146,21 +179,40 @@ class CBuilder(object): @staticmethod def new_function(mod, name, ret, args): + '''factory method + + Create a new function in the module and return a CBuilder instance. + ''' functype = lc.Type.function(ret, args) func = mod.add_function(functype, name=name) return CBuilder(func) def depends(self, fndecl): + '''add function dependency + + Returns a CFunc instance and define the function if it is not defined. + + fndecl : is a callable that takes a `llvm.core.Module` and returns + a function pointer. + ''' return CFunc(self, fndecl(self.function.module)) def printf(self, fmt, *args): - mod = self.function.module - functype = lc.Type.function(types.int, [types.char_p], True) - printf = mod.get_or_insert_function(functype, name='printf') - ret = self.builder.call(printf, [fmt.value]+_list_values(args)) + '''printf() from libc + + fmt : a character string holding printf format string. + *args : additional variable arguments. + ''' + from .libc import LibC + libc = LibC(self) + ret = libc.printf(fmt, *args) return CTemp(self, ret) def debug(self, *args): + '''debug print + + Use printf to dump the values of all arguments. + ''' type_mapper = { 'i8' : '%c', 'i16': '%hd', @@ -174,17 +226,27 @@ class CBuilder(object): if isinstance(i, str): itemsfmt.append(i.replace('%', '%%')) else: - ty = type_mapper[str(i.type)] + tyname = str(i.type) + if tyname == 'float': + # auto convert float to double + ty = '%e' + i = i.cast(types.double) + else: + ty = type_mapper[tyname] itemsfmt.append(ty) items.append(i) fmt = ' '.join(itemsfmt) + '\n' return self.printf(self.constant_string(fmt), *items) def var(self, ty, value=None, name=''): - ''' - Only allocate in the first block + '''allocate variable on the stack + + ty : variable type + value : [optional] initializer value + name : [optional] name used in LLVM IR ''' with _change_block_temporarily(self.builder, self.declare_block): + # goto the first block is_cstruct = _is_cstruct(ty) if is_cstruct: cstruct = ty @@ -203,27 +265,45 @@ class CBuilder(object): return CVar(self, ptr) def var_copy(self, val, name=''): + '''allocate a new variable by copying another value + + The new variable has the same type and value of `val`. + ''' return self.var(val.type, val, name=name) def array(self, ty, count, name=''): + '''allocate an array on the stack + + ty : array element type + count : array size; can be python int, llvm.core.Constant, or CValue + name : [optional] name used in LLVM IR + ''' if isinstance(count, int) or isinstance(count, lc.Constant): + # Only go to the first block if array size is fixed. contexthelper = _change_block_temporarily else: + # Do not go to the first block if the array size is dynamic. contexthelper = _change_block_temporarily_dummy with contexthelper(self.builder, self.declare_block): - is_cstruct = _is_cstruct(ty) - if is_cstruct: + if _is_cstruct(ty): # array of struct? cstruct = ty ty = ty.llvm_type() + if isinstance(count, CValue): count = count.value elif not isinstance(count, lc.Value): count = self.constant(types.int, count).value + ptr = self.builder.alloca_array(ty, count, name=name) return CArray(self, ptr) def ret(self, val=None): + '''insert return statement + + val : if is `None`, insert return-void + else, return `val` + ''' retty = self.function.type.pointee.return_type if val is not None: if val.type != retty: @@ -238,18 +318,26 @@ class CBuilder(object): @contextlib.contextmanager def ifelse(self, cond): + '''start a if-else block + + cond : branch condition + ''' cb = _IfElse(self, cond) yield cb cb.close() @contextlib.contextmanager def loop(self): + '''start a loop block + ''' cb = _Loop(self) yield cb cb.close() @contextlib.contextmanager def forever(self): + '''start a forever loop block + ''' with self.loop() as loop: with loop.condition() as setcond: NULL = self.constant_null(types.int) @@ -259,6 +347,10 @@ class CBuilder(object): @contextlib.contextmanager def for_range(self, *args): + '''start a for-range block. + + *args : same as arguments of builtin `range()` + ''' def check_arg(x): if isinstance(x, int): return self.constant(types.int, x) @@ -287,15 +379,26 @@ class CBuilder(object): idx += step def position_at_end(self, bb): + '''reposition inserter to the end of basic-block + + bb : a basic block + ''' self.basic_block = bb self.builder.position_at_end(bb) def close(self): + '''end code generation + ''' # Close declaration block with _change_block_temporarily(self.builder, self.declare_block): self.builder.branch(self.first_body_block) def constant(self, ty, val): + '''create a constant + + ty : data type + val : initializer + ''' if isinstance(ty, lc.IntegerType): res = lc.Constant.int(ty, val) elif ty == types.float or ty == types.double: @@ -306,119 +409,239 @@ class CBuilder(object): return CTemp(self, res) def constant_null(self, ty): + '''create a zero filled constant + + ty : data type + ''' res = lc.Constant.null(ty) return CTemp(self, res) def constant_string(self, string): + '''create a constant string + + This will de-duplication string of same content to minimize memory use. + ''' mod = self.function.module - # TODO - # Is this a safe way for uniquing identifying string - name = '.conststr.%x_%x' % (hash(string), len(string)) + collision = 0 + name_fmt = '.conststr.%x.%x' content = lc.Constant.stringz(string) - try: - globalstr = mod.get_global_variable_named(name) - except LLVMException: - globalstr = mod.add_global_variable(content.type, name=name) - globalstr.initializer = content - return CTemp(self, globalstr.bitcast( - types.pointer(content.type.element))) + while True: + name = name_fmt % (hash(string), collision) + try: + # check if the name already exists + globalstr = mod.get_global_variable_named(name) + except LLVMException: + # new constant string + globalstr = mod.add_global_variable(content.type, name=name) + globalstr.initializer = content + globalstr.global_constant = True + else: + # compare existing content + existed = str(globalstr.initializer) + if existed != str(content): + collision += 1 + continue # loop until we resolve the name collision + + return CTemp(self, globalstr.bitcast( + types.pointer(content.type.element))) def get_intrinsic(self, intrinsic_id, tys): + '''get intrinsic function + + intrinsic_id : numerical ID of target intrinsic + tys : type argument for the intrinsic + ''' lfunc = lc.Function.intrinsic(self.function.module, intrinsic_id, tys) return CFunc(self, lfunc) def get_function_named(self, name): + '''get function by name + ''' m = self.function.module func = m.get_function_named(name) return CFunc(self, func) def is_terminated(self): - ''' - Is the current basic-block terminated? + '''is the current basic-block terminated? ''' return _is_block_terminated(self.builder.basic_block) def atomic_cmpxchg(self, ptr, old, val, ordering, crossthread=True): + '''atomic compare-exchange + + ptr : pointer to data + old : old value to compare to + val : new value + ordering : memory ordering as a string + crossthread : set to `False` for single-thread code + + Returns the old value on success. + ''' res = self.builder.atomic_cmpxchg(ptr.value, old.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_xchg(self, ptr, val, ordering, crossthread=True): + '''atomic exchange + + ptr : pointer to data + val : new value + ordering : memory ordering as a string + crossthread : set to `False` for single-thread code + + Returns the old value + ''' + res = self.builder.atomic_xchg(ptr.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_add(self, ptr, val, ordering, crossthread=True): + '''atomic add + + ptr : pointer to data + val : new value + ordering : memory ordering as a string + crossthread : set to `False` for single-thread code + + Returns the computation result of the operation + ''' + res = self.builder.atomic_add(ptr.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_sub(self, ptr, val, ordering, crossthread=True): + '''atomic sub + + See `atomic_add` for parameters documentation + ''' res = self.builder.atomic_sub(ptr.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_and(self, ptr, val, ordering, crossthread=True): + '''atomic bitwise and + + See `atomic_add` for parameters documentation + ''' res = self.builder.atomic_and(ptr.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_nand(self, ptr, val, ordering, crossthread=True): + '''atomic bitwise nand + + See `atomic_add` for parameters documentation + ''' res = self.builder.atomic_nand(ptr.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_or(self, ptr, val, ordering, crossthread=True): + '''atomic bitwise or + + See `atomic_add` for parameters documentation + ''' res = self.builder.atomic_or(ptr.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_xor(self, ptr, val, ordering, crossthread=True): + '''atomic bitwise xor + + See `atomic_add` for parameters documentation + ''' + res = self.builder.atomic_xor(ptr.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_max(self, ptr, val, ordering, crossthread=True): + '''atomic signed maximum between value at `ptr` and `val` + + See `atomic_add` for parameters documentation + ''' res = self.builder.atomic_max(ptr.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_min(self, ptr, val, ordering, crossthread=True): + '''atomic signed minimum between value at `ptr` and `val` + + See `atomic_add` for parameters documentation + ''' res = self.builder.atomic_min(ptr.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_umax(self, ptr, val, ordering, crossthread=True): + '''atomic unsigned maximum between value at `ptr` and `val` + + See `atomic_add` for parameters documentation + ''' res = self.builder.atomic_umax(ptr.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_umin(self, ptr, val, ordering, crossthread=True): + '''atomic unsigned minimum between value at `ptr` and `val` + + See `atomic_add` for parameters documentation + ''' res = self.builder.atomic_umin(ptr.value, val.value, ordering, crossthread) return CTemp(self, res) def atomic_load(self, ptr, ordering, align=1, crossthread=True): + '''atomic load + + ptr : pointer to the value to load + align : memory alignment in bytes + See `atomic_add` for other documentation of other parameters + ''' res = self.builder.atomic_load(ptr.value, ordering, align, crossthread) return CTemp(self, res) def atomic_store(self, val, ptr, ordering, align=1, crossthread=True): + '''atomic store + + ptr : pointer to where to store + val : value to store + align : memory alignment in bytes + See `atomic_add` for other documentation of other parameters + ''' + res = self.builder.atomic_store(val.value, ptr.value, ordering, align, crossthread) return CTemp(self, res) def fence(self, ordering, crossthread=True): + '''insert memory fence + ''' res = self.builder.fence(ordering, crossthread) return CTemp(self, res) def alignment(self, ty): + '''get minimum alignment of `ty` + ''' return self.target_data.abi_alignment(ty) def unreachable(self): + '''insert instruction that causes segfault some platform (Intel), + or no-op on others. + + It has no defined semantic. + ''' self.builder.unreachable() class _DeclareCDef(object): + '''create a function a CDefinition to use with `CBuilder.depends` + + An instance of this class is created by the constructor of CDefinition. + Do not use directly. + ''' def __init__(self, cdef): self.cdef = cdef @@ -433,6 +656,9 @@ class _DeclareCDef(object): return func class CDeclare(object): + '''create a function from name, type and pointer to use with + `CBuilder.depends` + ''' def __init__(self, name, ty, ptr): self._name = name self._type = ty @@ -448,33 +674,51 @@ class CDeclare(object): return self._name class CDefinition(CBuilder): - ''' - Inherit this class to for defining functions + '''represents function definition + + Inherit from this class to create a new function definition. + + Class Members + ------------- + _name_ : name of the function + _retty_ : return type + _argtys_ : argument names and types as list of tuples; + e.g. [ ( 'myarg', lc.Type.int() ), ... ] ''' _name_ = '' # name of the function; should overide in subclass _retty_ = types.void # return type; can overide in subclass _argtys_ = [] # a list of tuple(name, type); can overide in subclass def __new__(cls, *args, **kws): - try: - fnsp = getattr(cls, 'specialize') - except AttributeError: - pass - else: + if cls.is_generic(): + # Call specialize if it is defined. cls = type('%s_Specialized' % cls.__name__, (cls,), {}) - fnsp(*args, **kws) + cls.specialize(*args, **kws) obj = object.__new__(_DeclareCDef) obj.__init__(cls) return obj + @classmethod + def is_generic(cls): + '''Is this a generic definition? + ''' + return hasattr(cls, 'specialize') + @classmethod def define(cls, module): + '''define the function in the module. + + Raises NameError if a function of the same name has already been + defined. + ''' functype = lc.Type.function(cls._retty_, [v for k, v in cls._argtys_]) name = cls._name_ if not name: - raise NameError("Function name cannot be empty.") + raise AttributeError("Function name cannot be empty.") + func = module.get_or_insert_function(functype, name=name) + if not func.is_declaration: # already defined? raise NameError(func) @@ -490,25 +734,24 @@ class CDefinition(CBuilder): return func def body(self): - ''' - Overide this function to define the body. + '''overide this function to define the body. ''' raise NotImplementedError class CValue(object): ''' - = Signess = - Since LLVM type does not provide signess attribute. This information + Signedness + ---------- + Since LLVM type does not provide signedness attribute. This information is provided in the CValue.unsigned attribute. The default value is `None`, meaning that this attribute is not set. - In casting operation, signess information is passed as an optional arg. + In casting operation, signednss information is passed as an optional arg. - In binary operation, signess of the left operand is used. + In binary operation, signedness of the left operand is used. ''' - # Attribute for for integer values. - unsigned = None + unsigned = None # attribute for for integer values. _BINOP_MAP = { # op-name : (signed int, unsigned int, real) @@ -542,6 +785,8 @@ class CValue(object): self.parent = parent def _use_binop(self, op): + '''implements binary operations + ''' def wrapped(rhs): self._ensure_same_type(rhs) binop = self._BINOP_MAP[op] @@ -560,6 +805,8 @@ class CValue(object): return wrapped def _use_bitwise(self, op): + '''implements bitwise operations + ''' def wrapped(rhs): self._ensure_same_type(rhs) if not self.is_int: @@ -608,6 +855,10 @@ class CValue(object): return self._use_bitwise('xor')(rhs) def _ensure_same_type(self, val): + '''ensure that this instance has the same type as `val` + + Raises TypeError if `self.type != val.type` + ''' if self.type != val.type: errmsg = "Type mismatch: %s != %s" raise TypeError(errmsg % (self.type, val.type)) @@ -621,6 +872,10 @@ class CValue(object): return _is_real(self.type) def cast(self, ty, unsigned=False): + '''cast to another type + + If `ty == self.type`, then pass thru + ''' make = lambda X: CTemp(self.parent, X) if self.type == ty: return self # pass thru @@ -651,6 +906,8 @@ class CValue(object): raise TypeError(errmsg % (self.type, ty)) def _cmp_op(self, name): + '''implements comparison operations + ''' def wrapped(rhs): make = lambda X: CTemp(self.parent, X) self._ensure_same_type(rhs) @@ -700,6 +957,10 @@ class CValue(object): raise TypeError("Must be a pointer; got %s" % self.type) def __getitem__(self, idx): + '''implement access indexing + + Uses GEP. + ''' self._ensure_is_pointer() if not isinstance(idx, CValue): idx = self.parent.constant(types.int, idx) @@ -708,15 +969,26 @@ class CValue(object): return CVar(self.parent, ptr) def load(self, volatile=False): + '''memory load for pointer types + ''' self._ensure_is_pointer() loaded = self.parent.builder.load(self.value, volatile=volatile) return CTemp(self.parent, loaded) def store(self, val, volatile=False): + '''memory store for pointer types + ''' self._ensure_is_pointer() self.parent.builder.store(val.value, self.value, volatile=volatile) def atomic_load(self, ordering, align=None, crossthread=True): + '''atomic load memory for pointer types + + align : overide to control memory alignment; otherwise the default + alignment of the type is used. + + Other parameters are the same as `CBuilder.atomic_load` + ''' self._ensure_is_pointer() if align is None: align = self.parent.alignment(self.type.pointee) @@ -725,6 +997,13 @@ class CValue(object): return CTemp(self.parent, inst) def atomic_store(self, value, ordering, align=None, crossthread=True): + '''atomic memory store for pointer types + + align : overide to control memory alignment; otherwise the default + alignment of the type is used. + + Other parameters are the same as `CBuilder.atomic_store` + ''' self._ensure_is_pointer() if align is None: align = self.parent.alignment(self.type.pointee) @@ -732,6 +1011,10 @@ class CValue(object): align=align, crossthread=crossthread) def atomic_cmpxchg(self, old, new, ordering, crossthread=True): + '''atomic compare-exchange for pointer types + + Other parameters are the same as `CBuilder.atomic_cmpxchg` + ''' self._ensure_is_pointer() inst = self.parent.builder.atomic_cmpxchg(self.value, old.value, new.value, ordering, @@ -740,11 +1023,17 @@ class CValue(object): class CFunc(CValue): + '''Wraps function pointer + ''' def __init__(self, parent, func): super(CFunc, self).__init__(parent) self.function = func def __call__(self, *args): + '''Call the function with the given arguments + + *args : variable arguments of CValue instances + ''' arg_values = _list_values(args) ftype = self.function.type.pointee for i, (exp, got) in enumerate(zip(ftype.args, arg_values)): @@ -764,6 +1053,8 @@ class CFunc(CValue): return self.function.type class CTemp(CValue): + '''Wraps temporary values + ''' def __init__(self, parent, value): super(CTemp, self).__init__(parent) self.value = value @@ -773,6 +1064,10 @@ class CTemp(CValue): return self.value.type class CVar(CValue): + '''Wraps variables + + Similar to C variables. + ''' def __init__(self, parent, ptr): super(CVar, self).__init__(parent) @@ -827,6 +1122,8 @@ class CVar(CValue): return self.parent.builder.load(self.ptr) def assign(self, val): + '''assign new value to the variable + ''' self._ensure_same_type(val) self.parent.builder.store(val.value, self.ptr) @@ -835,9 +1132,13 @@ class CVar(CValue): return self.ptr.type.pointee def reference(self): + '''get a pointer reference of the variable + ''' return CTemp(self.parent, self.ptr) def as_struct(self, cstruct_class, volatile=False): + '''load a pointer to a structure and assume a structure interface + ''' if _is_pointer(self.type): ptr = self.parent.builder.load(self.ptr, volatile=volatile) return cstruct_class(self.parent, ptr) @@ -845,6 +1146,10 @@ class CVar(CValue): return cstruct_class(self.parent, self.ptr) class CArray(CValue): + '''wraps a array + + Similar to C arrays + ''' def __init__(self, parent, base): super(CArray, self).__init__(parent) self.base_ptr = base @@ -860,18 +1165,17 @@ class CArray(CValue): def type(self): return self.base_ptr.type -## Moved to CValue -# def __getitem__(self, idx): -# self._ensure_is_pointer() -# builder = self.parent.builder -# if isinstance(idx, CValue): -# idx = idx.value -# elif not isinstance(idx, lc.Value): -# idx = self.parent.constant(types.int, idx).value -# ptr = builder.gep(self.value, [idx]) -# return CVar(self.parent, ptr) - class CStruct(CValue): + '''Wraps a structure + + Structure in LLVM can be identified by name of layout. + + Subclass to define a new structure. All fields are defined in the + `_fields_` class attribute as a list of tuple (name, type). + + Can define new methods which gets inlined to the parent CBuilder. + ''' + @classmethod def llvm_type(cls): return lc.Type.struct([v for k, v in cls._fields_]) @@ -883,12 +1187,19 @@ class CStruct(CValue): for i, (fd, _) in enumerate(self._fields_): gep = self.parent.builder.gep(ptr, [makeind(0), makeind(i)]) gep.name = "%s.%s" % (type(self).__name__, fd) + if hasattr(self, fd): + raise AttributeError("Field name shadows another attribute") setattr(self, fd, CVar(self.parent, gep)) def reference(self): return CTemp(self.parent, self.ptr) class CExternal(object): + '''subclass to define external interface + + All class attributes that are `llvm.core.FunctionType` are converted + to `CFunc` instance during instantiation. + ''' def __init__(self, cbuilder): is_func = lambda x: isinstance(x, lc.FunctionType) non_magic = lambda s: not ( s.startswith('__') and s.endswith('__') ) diff --git a/llvm_cbuilder/executor.py b/llvm_cbuilder/executor.py index bd7d78c..acfd13d 100644 --- a/llvm_cbuilder/executor.py +++ b/llvm_cbuilder/executor.py @@ -1,3 +1,7 @@ +''' +This is mostly a convenience module for testing with ctypes. +''' + from llvm.core import Type, Module import llvm.ee as le import ctypes as ct @@ -29,6 +33,8 @@ MAP_CTYPES = { } class CExecutor(object): + '''a convenient class for creating ctype functions from LLVM modules + ''' def __init__(self, mod_or_engine): if isinstance(mod_or_engine, Module): self.engine = le.EngineBuilder.new(mod_or_engine).opt(3).create() @@ -36,6 +42,14 @@ class CExecutor(object): self.engine = mod_or_engine def get_ctype_function(self, fn, *typeinfo): + '''create a ctype function from a LLVM function + + typeinfo : string of types (see `MAP_CTYPES`) or + list of ctypes datatype. + First value is the return type. + A function that takes no argument and return nothing + should use `"void"` or `None`. + ''' if len(typeinfo)==1 and isinstance(typeinfo[0], str): types = [ MAP_CTYPES[s.strip()] for s in typeinfo[0].split(',') ] if not types: diff --git a/llvm_cbuilder/libc.py b/llvm_cbuilder/libc.py new file mode 100644 index 0000000..8d4a5c1 --- /dev/null +++ b/llvm_cbuilder/libc.py @@ -0,0 +1,8 @@ +from .builder import CExternal +import llvm.core as lc +from . import shortnames as types + +class LibC(CExternal): + printf = lc.Type.function(types.int, [types.char_p], True) + # TODO a lot more to add + diff --git a/tests/test_cstr_collide.py b/tests/test_cstr_collide.py new file mode 100644 index 0000000..4a427c6 --- /dev/null +++ b/tests/test_cstr_collide.py @@ -0,0 +1,17 @@ +from llvm.core import * +from llvm_cbuilder import * +from llvm_cbuilder import shortnames as C + +import unittest + +class TestCstrCollide(unittest.TestCase): + def test_same_string(self): + mod = Module.new(__name__) + cb = CBuilder.new_function(mod, 'test_cstr_collide', C.void, []) + + a = cb.constant_string("hello") + b = cb.constant_string("hello") + self.assertEqual(a.value, b.value) + +if __name__ == '__main__': + unittest.main() From 3009d8468f3ca85650852d3777ca1c547c57c06d Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Wed, 8 Aug 2012 15:29:16 -0700 Subject: [PATCH 21/57] more cleanup and doc --- README.md | 16 +++++++++++ parallel_vectorize.py | 63 +++++++++++++++++++++++-------------------- 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index cab0f28..a63039a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,23 @@ +llvm_cbuilder +------------- + A llvm-py Builder wrapper for writing in slightly higher-level constructs. This is aiming for two usecases: + 1. Emit LLVM code in a more human-readable way; + 2. Writing low-level code that you can't do it properly/portably with C, e.g template (generic), atomic operations, memory ordering... + +Parallel Vectorize +------------------ + +`parallel_vectorize.py` implements a set of code generator that bases on +llvm_cbuilder to create specialized parallel ufunc. + +See `test_parallel_vectorize_numpy*.py` for testing and demo of the code +with the new `numpy.fromfunc()`. + + diff --git a/parallel_vectorize.py b/parallel_vectorize.py index 4c8daab..f26ad46 100644 --- a/parallel_vectorize.py +++ b/parallel_vectorize.py @@ -17,8 +17,7 @@ from llvm_cbuilder import * import llvm_cbuilder.shortnames as C class WorkQueue(CStruct): - ''' - Structure for workqueue for parallel-ufunc. + '''structure for workqueue for parallel-ufunc. ''' _fields_ = [ @@ -29,8 +28,7 @@ class WorkQueue(CStruct): def Lock(self): - ''' - Inline the lock procedure. + '''inline the lock procedure. ''' with self.parent.loop() as loop: with loop.condition() as setcond: @@ -45,8 +43,7 @@ class WorkQueue(CStruct): pass def Unlock(self): - ''' - Inline the unlock procedure. + '''inline the unlock procedure. ''' unlocked = self.parent.constant(self.lock.type, 0) locked = self.parent.constant(self.lock.type, 1) @@ -61,8 +58,7 @@ class WorkQueue(CStruct): class ContextCommon(CStruct): - ''' - Structure for thread-shared context information in parallel-ufunc. + '''structure for thread-shared context information in parallel-ufunc. ''' _fields_ = [ # loop ufunc args @@ -77,8 +73,7 @@ class ContextCommon(CStruct): ] class Context(CStruct): - ''' - Structure for thread-specific context information in parallel-ufunc. + '''structure for thread-specific context information in parallel-ufunc. ''' _fields_ = [ ('common', C.pointer(ContextCommon.llvm_type())), @@ -87,7 +82,11 @@ class Context(CStruct): ] class ParallelUFunc(CDefinition): - ''' + '''the generic parallel vectorize mechanism + + Can be specialized to the maximum number of threads on the platform. + + Platform dependent threading function is implemented in def _dispatch_worker(self, worker, contexts, num_thread): @@ -107,7 +106,8 @@ class ParallelUFunc(CDefinition): @classmethod def specialize(cls, num_thread): - print num_thread + '''specialize to the maximum # of thread + ''' cls._name_ = 'parallel_ufunc_%d' % num_thread cls.ThreadCount = num_thread @@ -172,8 +172,7 @@ class ParallelUFunc(CDefinition): self.ret() def _populate_workqueues(self, workqueues, N, ChunkSize, num_thread): - ''' - Loop over all threads and populate the workqueue for each of them. + '''loop over all threads and populate the workqueue for each of them. ''' ONE = self.constant(num_thread.type, 1) with self.for_range(num_thread) as (loop, i): @@ -186,8 +185,7 @@ class ParallelUFunc(CDefinition): last_wq.last.assign(N) def _populate_context(self, contexts, common, num_thread): - ''' - Loop over all threads and populate contexts for each of them. + '''loop over all threads and populate contexts for each of them. ''' ONE = self.constant(num_thread.type, 1) with self.for_range(num_thread) as (loop, i): @@ -198,8 +196,7 @@ class ParallelUFunc(CDefinition): self.constant_null(cur_ctxt.completed.type)) class ParallelUFuncPosixMixin(object): - ''' - Implements _dispatch_worker to use pthread. + '''ParallelUFunc mixin that implements _dispatch_worker to use pthread. ''' def _dispatch_worker(self, worker, contexts, num_thread): api = PThreadAPI(self) @@ -219,7 +216,10 @@ class ParallelUFuncPosixMixin(object): api.pthread_join(threads[i], NULL) class UFuncCore(CDefinition): - ''' + '''core work of a ufunc worker thread + + Subclass to implement UFuncCore._do_work + Generates the workqueue handling and work stealing and invoke the work function for each work item. ''' @@ -242,8 +242,7 @@ class UFuncCore(CDefinition): self.ret() def _do_workqueue(self, common, workqueue, tid, completed): - ''' - Process local workqueue. + '''process local workqueue. ''' ZERO = self.constant_null(C.int) @@ -264,8 +263,7 @@ class UFuncCore(CDefinition): completed += self.constant(completed.type, 1) def _do_work_stealing(self, common, tid, completed): - ''' - Steal work from other workqueues. + '''steal work from other workqueues. ''' # self.debug("start work stealing", tid) steal_continue = self.var(C.int, 1) @@ -283,8 +281,7 @@ class UFuncCore(CDefinition): def _do_work_stealing_innerloop(self, common, steal_continue, tid, completed): - ''' - Loop over all other threads and try to steal work. + '''loop over all other threads and try to steal work. ''' with self.for_range(common.num_thread) as (loop, i): with self.ifelse( i != tid ) as ifelse: @@ -296,10 +293,10 @@ class UFuncCore(CDefinition): def _do_work_stealing_check(self, common, otherqueue, steal_continue, tid, completed): - ''' - Check the workqueue for any remaining work and steal it. + '''check the workqueue for any remaining work and steal it. ''' otherqueue.Lock() + # Acquired ONE = self.constant(otherqueue.last.type, 1) STEAL_CONTINUE = self.constant(steal_continue.type, 1) with self.ifelse(otherqueue.next < otherqueue.last) as ifelse: @@ -318,14 +315,18 @@ class UFuncCore(CDefinition): with ifelse.otherwise(): otherqueue.Unlock() + # Released def _do_work(self, common, item, tid): - ''' - Prepare to call the actual work function. + '''prepare to call the actual work function + + Implementation depends on number and type of arguments. ''' raise NotImplementedError class SpecializedParallelUFunc(CDefinition): + '''a generic ufunc that wraps ParallelUFunc, UFuncCore and the workload + ''' _argtys_ = [ ('args', C.pointer(C.char_p)), ('dimensions', C.pointer(C.intp)), @@ -343,12 +344,16 @@ class SpecializedParallelUFunc(CDefinition): @classmethod def specialize(cls, pufunc_def, core_def, func_def): + '''specialize to a combination of ParallelUFunc, UFuncCore and workload + ''' cls._name_ = 'specialized_%s_%s_%s'% (pufunc_def, core_def, func_def) cls.PUFuncDef = pufunc_def cls.CoreDef = core_def cls.FuncDef = func_def class PThreadAPI(CExternal): + '''external declaration of pthread API + ''' pthread_t = C.void_p pthread_create = Type.function(C.int, From 3b0aeea6f28c07acac864c25f90cfde9e0efd15a Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Wed, 8 Aug 2012 16:27:57 -0700 Subject: [PATCH 22/57] change CDeclare to CFuncRef --- llvm_cbuilder/builder.py | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/llvm_cbuilder/builder.py b/llvm_cbuilder/builder.py index 99133e6..3b854b5 100644 --- a/llvm_cbuilder/builder.py +++ b/llvm_cbuilder/builder.py @@ -655,16 +655,36 @@ class _DeclareCDef(object): (func,) = e return func -class CDeclare(object): - '''create a function from name, type and pointer to use with - `CBuilder.depends` +class CFuncRef(object): + '''create a function reference to use with `CBuilder.depends` + + Either from name, type and pointer, + Or from a llvm.core.FunctionType instance ''' - def __init__(self, name, ty, ptr): - self._name = name - self._type = ty - self._ptr = ptr + def __init__(self, *args, **kwargs): + def one_arg(fn): + self._fn = fn + self._name = fn.name + + def three_arg(name, ty, ptr): + self._name = name + self._type = ty + self._ptr = ptr + + try: + three_arg(*args, **kwargs) + self._meth = self._from_pointer + except TypeError: + one_arg(*args, **kwargs) + self._meth = self._from_func def __call__(self, module): + return self._meth() + + def _from_func(self): + return self._fn + + def _from_pointer(self): fnptr = types.pointer(self._type) ptr = lc.Constant.int(types.intp, self._ptr) ptr = ptr.inttoptr(fnptr) From e35b8ca636cf0f4ff0cabb2d523c954aacbd42c6 Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Wed, 8 Aug 2012 17:32:39 -0700 Subject: [PATCH 23/57] Add some float casting --- llvm_cbuilder/builder.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/llvm_cbuilder/builder.py b/llvm_cbuilder/builder.py index 3b854b5..15ef477 100644 --- a/llvm_cbuilder/builder.py +++ b/llvm_cbuilder/builder.py @@ -917,10 +917,19 @@ class CValue(object): else: return make(self.parent.builder.uitofp(self.value, ty)) elif self.is_real: - if not unsigned: - return make(self.parent.builder.fptosi(self.value, ty)) + if _is_int(ty): + if not unsigned: + return make(self.parent.builder.fptosi(self.value, ty)) + else: + return make(self.parent.builder.fptoui(self.value, ty)) else: - return make(self.parent.builder.fptoui(self.value, ty)) + if ty == types.double: + assert self.type == types.float + return make(self.parent.builder.fpext(self.value, ty)) + else: + assert ty == types.float + assert self.type == types.double + return make(self.parent.builder.fptrunc(self.value, ty)) errmsg = "Cast from %s to %s is not possible." raise TypeError(errmsg % (self.type, ty)) From b23d7909fbd4b1c59e4320d3025b7ca0a7ce6924 Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Wed, 8 Aug 2012 17:33:00 -0700 Subject: [PATCH 24/57] add parallel_vectorize_from_func --- parallel_vectorize.py | 84 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/parallel_vectorize.py b/parallel_vectorize.py index f26ad46..f70954b 100644 --- a/parallel_vectorize.py +++ b/parallel_vectorize.py @@ -16,6 +16,8 @@ from llvm.passes import * from llvm_cbuilder import * import llvm_cbuilder.shortnames as C +import sys + class WorkQueue(CStruct): '''structure for workqueue for parallel-ufunc. ''' @@ -365,3 +367,85 @@ class PThreadAPI(CExternal): pthread_join = Type.function(C.int, [C.void_p, C.void_p]) +class UFuncCoreGeneric(UFuncCore): + '''A generic ufunc core worker from LLVM function type + ''' + def _do_work(self, common, item, tid): + ufunc_type = Type.function(self.RETTY, self.ARGTYS) + ufunc_ptr = CFunc(self, common.func.cast(C.pointer(ufunc_type)).value) + + get_offset = lambda B, S, T: B[item * S].reference().cast(C.pointer(T)) + + indata = [] + for i, argty in enumerate(self.ARGTYS): + ptr = get_offset(common.args[i], common.steps[i], argty) + indata.append(ptr.load()) + + out_index = len(self.ARGTYS) + outptr = get_offset(common.args[out_index], common.steps[out_index], + self.RETTY) + + res = ufunc_ptr(*indata) + outptr.store(res) + + @classmethod + def specialize(cls, fntype): + '''specialize to a LLVM function type + + fntype : a LLVM function type (llvm.core.FunctionType) + ''' + cls._name_ = '.'.join([cls._name_] + + map(str, [fntype.return_type] + fntype.args)) + + cls.RETTY = fntype.return_type + cls.ARGTYS = tuple(fntype.args) + + +if sys.platform not in ['win32']: + class ParallelUFuncPlatform(ParallelUFunc, ParallelUFuncPosixMixin): + pass +else: + raise NotImplementedError("Threading for %s" % sys.platform) + + + +def parallel_vectorize_from_func(lfunc, engine=None): + fntype = lfunc.type.pointee + def_spuf = SpecializedParallelUFunc(ParallelUFuncPlatform(num_thread=2), + UFuncCoreGeneric(fntype), + CFuncRef(lfunc)) + spuf = def_spuf(lfunc.module) + if engine is None: + return spuf + else: + import numpy as np + + fptr = engine.get_pointer_to_function(spuf) + inct = len(fntype.args) + outct = 1 + + # TODO refactor + typemap = { + 'i8' : np.uint8, + 'i16' : np.uint16, + 'i32' : np.uint32, + 'i64' : np.uint64, + 'float' : np.float32, + 'double' : np.float64, + } + try: + ptr_t = long + except: + ptr_t = int + assert False, "Having check this yet" + + get_typenum = lambda T:np.dtype(typemap[str(T)]).num + assert fntype.return_type != C.void + tys = list(map(get_typenum, list(fntype.args) + [fntype.return_type])) + # Becareful that fromfunc does not provide full error checking yet. + # If typenum is out-of-bound, we have nasty memory corruptions. + # For instance, -1 for typenum will cause segfault. + # If elements of type-list (2nd arg) is tuple instead, + # there will also memory corruption. (Seems like code rewrite.) + return np.fromfunc([ptr_t(fptr)], [tys], inct, outct, [None]) + From 753f74d48562193ecbd6dc9cd5ec52bdf59fe26c Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Wed, 8 Aug 2012 17:33:24 -0700 Subject: [PATCH 25/57] Fix tests --- test_parallel_vectorize.py | 2 +- test_parallel_vectorize_numpy_2.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test_parallel_vectorize.py b/test_parallel_vectorize.py index 54d6b14..1a850f5 100644 --- a/test_parallel_vectorize.py +++ b/test_parallel_vectorize.py @@ -23,7 +23,7 @@ class UFuncCore_D_D(UFuncCore): outbase = common.args[1] instep = common.steps[0] - outstep = common.steps[0] + outstep = common.steps[1] indata = inbase[item * instep].reference().cast(C.pointer(C.double)) outdata = outbase[item * outstep].reference().cast(C.pointer(C.double)) diff --git a/test_parallel_vectorize_numpy_2.py b/test_parallel_vectorize_numpy_2.py index a511980..f846a25 100644 --- a/test_parallel_vectorize_numpy_2.py +++ b/test_parallel_vectorize_numpy_2.py @@ -18,7 +18,7 @@ def main(): # get pointer to workfunc workfunc_ptr = exe.engine.get_pointer_to_function(workfunc) - workdecl = CDeclare(workfunc.name, workfunc.type.pointee, workfunc_ptr) + workdecl = CFuncRef(workfunc.name, workfunc.type.pointee, workfunc_ptr) spufdef = SpecializedParallelUFunc(ParallelUFuncPosix(num_thread=2), UFuncCore_D_D(), From c6d5538eade22fec6587a0177631dcb5fabfa63e Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Wed, 8 Aug 2012 17:33:34 -0700 Subject: [PATCH 26/57] Add usecase tests --- test_usecase_1_1.py | 56 +++++++++++++++++++++++++++++++++++++++++++ test_usecase_2_1.py | 58 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 test_usecase_1_1.py create mode 100644 test_usecase_2_1.py diff --git a/test_usecase_1_1.py b/test_usecase_1_1.py new file mode 100644 index 0000000..5cc4d7a --- /dev/null +++ b/test_usecase_1_1.py @@ -0,0 +1,56 @@ +from parallel_vectorize import * +from llvm_cbuilder import shortnames as C +from llvm.core import * +import numpy as np +import unittest +from random import random + +class OneOne(CDefinition): + + def body(self, inval): + self.ret( (inval * inval).cast(self.OUT_TYPE) ) + + @classmethod + def specialize(cls, itype, otype): + cls._name_ = '.'.join(map(str, ['oneone', itype, otype])) + cls._retty_ = otype + cls._argtys_ = [ + ('inval', itype), + ] + cls.OUT_TYPE = otype + + +class TestParallelVectorize(unittest.TestCase): + def test_parallelvectorize_d_d(self): + self.template(C.double, C.double) + + def test_parallelvectorize_d_f(self): + self.template(C.double, C.float) + + def template(self, itype, otype): + module = Module.new(__name__) + exe = CExecutor(module) + + def_oneone = OneOne(itype, otype) + oneone = def_oneone(module) + ufunc = parallel_vectorize_from_func(oneone, exe.engine) + # print(module) + module.verify() + + x = np.linspace(.0, 10., 1000) + x.dtype = np.double + + ans = ufunc(x) + gold = x * x + + for x, y in zip(ans, gold): + if y != 0: + err = abs(x - y)/y + self.assertLess(err, 1e-6) + else: + self.assertEqual(x, y) + + +if __name__ == '__main__': + unittest.main() + diff --git a/test_usecase_2_1.py b/test_usecase_2_1.py new file mode 100644 index 0000000..087ee9f --- /dev/null +++ b/test_usecase_2_1.py @@ -0,0 +1,58 @@ +from parallel_vectorize import * +from llvm_cbuilder import shortnames as C +from llvm.core import * +import numpy as np +import unittest +from random import random + +class TwoOne(CDefinition): + + def body(self, a, b): + self.ret( (a * b).cast(self.OUT_TYPE) ) + + @classmethod + def specialize(cls, itype1, itype2, otype): + cls._name_ = '.'.join(map(str, ['oneone', itype1, itype2, otype])) + cls._retty_ = otype + cls._argtys_ = [ + ('a', itype1), + ('b', itype2), + ] + cls.OUT_TYPE = otype + + +class TestParallelVectorize(unittest.TestCase): + def test_parallelvectorize_dd_d(self): + self.template(C.double, C.double, C.double) + + def test_parallelvectorize_dd_f(self): + self.template(C.double, C.double, C.float) + + def template(self, itype1, itype2, otype): + module = Module.new(__name__) + exe = CExecutor(module) + + def_twoone = TwoOne(itype1, itype2, otype) + twoone = def_twoone(module) + ufunc = parallel_vectorize_from_func(twoone, exe.engine) + # print(module) + module.verify() + + A = np.linspace(.0, 10., 1000) + A.dtype = np.double + B = np.linspace(-10., 0., 1000) + B.dtype = np.double + + ans = ufunc(A, B) + gold = A * B + + for x, y in zip(ans, gold): + if y != 0: + err = abs(x - y)/y + self.assertLess(err, 1e-6) + else: + self.assertEqual(x, y) + +if __name__ == '__main__': + unittest.main() + From 9ccc6782c7b5a9afeeefc2ed6acd13ca30c95c3f Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Wed, 8 Aug 2012 17:46:20 -0700 Subject: [PATCH 27/57] Add doc for parallel_vectorize_from_func. Add FunctionAlreadyExists exception. --- llvm_cbuilder/builder.py | 7 +++++-- parallel_vectorize.py | 6 ++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/llvm_cbuilder/builder.py b/llvm_cbuilder/builder.py index 15ef477..fc4baa3 100644 --- a/llvm_cbuilder/builder.py +++ b/llvm_cbuilder/builder.py @@ -12,6 +12,9 @@ from . import shortnames as types # Utilities ### +class FunctionAlreadyExists(NameError): + pass + def _is_int(ty): return isinstance(ty, lc.IntegerType) @@ -651,7 +654,7 @@ class _DeclareCDef(object): def __call__(self, module): try: func = self.cdef.define(module) - except NameError as e: + except FunctionAlreadyExists as e: (func,) = e return func @@ -740,7 +743,7 @@ class CDefinition(CBuilder): func = module.get_or_insert_function(functype, name=name) if not func.is_declaration: # already defined? - raise NameError(func) + raise FunctionAlreadyExists(func) # Name all arguments for i, (name, _) in enumerate(cls._argtys_): diff --git a/parallel_vectorize.py b/parallel_vectorize.py index f70954b..210dea8 100644 --- a/parallel_vectorize.py +++ b/parallel_vectorize.py @@ -410,6 +410,12 @@ else: def parallel_vectorize_from_func(lfunc, engine=None): + '''create ufunc from a llvm.core.Function + + If engine is given, return a function object which can be called + from python. (This needs Jay's numpy.fromfunc). + Otherwise, return the specialized ufunc as a llvm.core.Function + ''' fntype = lfunc.type.pointee def_spuf = SpecializedParallelUFunc(ParallelUFuncPlatform(num_thread=2), UFuncCoreGeneric(fntype), From 7f84ecbe3b64f7e6438dee212c4c9535d4f5f5d4 Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Fri, 10 Aug 2012 17:58:54 -0700 Subject: [PATCH 28/57] Add notebook: llvm_cbuilder quick tour --- notebook/llvm_cbuilder_intro.ipynb | 586 +++++++++++++++++++++++++++++ notebook/llvm_cbuilder_intro.pdf | Bin 0 -> 130681 bytes 2 files changed, 586 insertions(+) create mode 100644 notebook/llvm_cbuilder_intro.ipynb create mode 100644 notebook/llvm_cbuilder_intro.pdf diff --git a/notebook/llvm_cbuilder_intro.ipynb b/notebook/llvm_cbuilder_intro.ipynb new file mode 100644 index 0000000..9234f3d --- /dev/null +++ b/notebook/llvm_cbuilder_intro.ipynb @@ -0,0 +1,586 @@ +{ + "metadata": { + "name": "llvm_cbuilder_intro" + }, + "nbformat": 2, + "worksheets": [ + { + "cells": [ + { + "cell_type": "markdown", + "source": [ + "llvm_cbuilder: A Quick Tour", + "---------------------------", + "", + "Writing code generation logic in pure llvmpy requires one to think like a compiler -- ", + "dealing with basic-blocks, branches, SSA, etc.", + "The resulting code looks like assembly code, with no apparent hint about the control-flow.", + "", + "llvm_cbuilder is originally designed to simplify the translation of C code into llvmpy.", + "It provides a simple API for mimicking C programming in Python.", + "With llvm_cbulder, one can write very low-level code, probably even lower-level than C.", + "At the same time, one can use if-else, loop, structures and a bit of OOP." + ] + }, + { + "cell_type": "markdown", + "source": [ + "A Simple Example", + "----------------", + "", + "Let's see some action.", + "We will define a function that calculates the square of a double-precision float." + ] + }, + { + "cell_type": "code", + "collapsed": true, + "input": [ + "from llvm.core import *", + "from llvm_cbuilder import *", + "import llvm_cbuilder.shortnames as C", + "", + "class Square(CDefinition):", + " # prototype: double square(double x)", + " _name_ = 'square' # function name", + " _retty_ = C.double", + " _argtys_ = [ ('x', C.double) ]", + " ", + " def body(self, x):", + " y = x * x # just write out the expression", + " self.ret(y)" + ], + "language": "python", + "outputs": [], + "prompt_number": 1 + }, + { + "cell_type": "markdown", + "source": [ + "Notice how numerical expressions can be written naturally." + ] + }, + { + "cell_type": "markdown", + "source": [ + "Let's see the emitted code:" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "m = Module.new('my_module')", + "llvm_square = Square()(m) # define square() in my_module", + "print(m)" + ], + "language": "python", + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "; ModuleID = 'my_module'", + "", + "define double @square(double %x) {", + "decl:", + " %x1 = alloca double", + " br label %body", + "", + "body: ; preds = %decl", + " store double %x, double* %x1", + " %0 = load double* %x1", + " %1 = load double* %x1", + " %2 = fmul double %0, %1", + " ret double %2", + "}", + "" + ] + } + ], + "prompt_number": 2 + }, + { + "cell_type": "markdown", + "source": [ + "Let's generate a ctype function object to call `square()` in the Python code:" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "exe = CExecutor(m)", + "square = exe.get_ctype_function(llvm_square, \"double, double\")", + "result = square(1.2)", + "print(result)" + ], + "language": "python", + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "1.44" + ] + } + ], + "prompt_number": 3 + }, + { + "cell_type": "markdown", + "source": [ + "Control Flow Constructs", + "-----------------------", + "", + "The main strength of llvm_cbuilder is the control-flow constructs. ", + "They use the python \"with\" statement to setup new code blocks to", + "contain different paths of the control flow.", + "", + "The following example demonstrates both the if-else and loop contructs:" + ] + }, + { + "cell_type": "code", + "collapsed": true, + "input": [ + "class IsPrime(CDefinition):", + " # prototype int isprime(int x)", + " _name_ = 'isprime'", + " _retty_ = C.int", + " _argtys_ = [ ('x', C.int) ]", + " ", + " def body(self, x):", + " two = self.constant(C.int, 2)", + " true = one = self.constant(C.int, 1)", + " false = zero = self.constant(C.int, 0)", + " ", + " with self.ifelse( x <= two ) as ifelse:", + " with ifelse.then():", + " self.ret(true)", + " ", + " with self.ifelse( (x % two) == zero ) as ifelse:", + " with ifelse.then():", + " self.ret(false)", + " ", + " idx = self.var(C.int, 3, name='idx')", + " with self.loop() as loop:", + " with loop.condition() as setcond:", + " setcond( idx < x )", + " ", + " with loop.body():", + " with self.ifelse( (x % idx) == zero ) as ifelse:", + " with ifelse.then():", + " self.ret(false)", + " idx += two", + " self.ret(true)" + ], + "language": "python", + "outputs": [], + "prompt_number": 4 + }, + { + "cell_type": "markdown", + "source": [ + "The code above is quite verbose.", + "It is like writing in Pascal or Ada. ", + "But, it is still easier than writing in llvmpy directly.", + "Take a look at the generated LLVM IR below." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "llvm_isprime = IsPrime()(m)", + "print(llvm_isprime)" + ], + "language": "python", + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "", + "define i32 @isprime(i32 %x) {", + "decl:", + " %x1 = alloca i32", + " %idx = alloca i32", + " br label %body", + "", + "body: ; preds = %decl", + " store i32 %x, i32* %x1", + " %0 = load i32* %x1", + " %1 = icmp sle i32 %0, 2", + " br i1 %1, label %if.then, label %if.else", + "", + "if.then: ; preds = %body", + " ret i32 1", + "", + "if.else: ; preds = %body", + " br label %if.end", + "", + "if.end: ; preds = %if.else", + " %2 = load i32* %x1", + " %3 = srem i32 %2, 2", + " %4 = icmp eq i32 %3, 0", + " br i1 %4, label %if.then2, label %if.else3", + "", + "if.then2: ; preds = %if.end", + " ret i32 0", + "", + "if.else3: ; preds = %if.end", + " br label %if.end4", + "", + "if.end4: ; preds = %if.else3", + " store i32 3, i32* %idx", + " br label %loop.cond", + "", + "loop.cond: ; preds = %if.end7, %if.end4", + " %5 = load i32* %idx", + " %6 = load i32* %x1", + " %7 = icmp slt i32 %5, %6", + " br i1 %7, label %loop.body, label %loop.end", + "", + "loop.body: ; preds = %loop.cond", + " %8 = load i32* %x1", + " %9 = load i32* %idx", + " %10 = srem i32 %8, %9", + " %11 = icmp eq i32 %10, 0", + " br i1 %11, label %if.then5, label %if.else6", + "", + "loop.end: ; preds = %loop.cond", + " ret i32 1", + "", + "if.then5: ; preds = %loop.body", + " ret i32 0", + "", + "if.else6: ; preds = %loop.body", + " br label %if.end7", + "", + "if.end7: ; preds = %if.else6", + " %12 = load i32* %idx", + " %13 = add i32 %12, 2", + " store i32 %13, i32* %idx", + " br label %loop.cond", + "}", + "" + ] + } + ], + "prompt_number": 5 + }, + { + "cell_type": "markdown", + "source": [ + "We'll setup a ctype function object to try it out:" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "isprime = exe.get_ctype_function(llvm_isprime, 'int, int')", + "prime_100 = filter(isprime, range(2, 100))", + "print(prime_100)" + ], + "language": "python", + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]" + ] + } + ], + "prompt_number": 6 + }, + { + "cell_type": "markdown", + "source": [ + "Structures", + "----------", + "", + "It is possible to create structures in llvm_cbuilder. ", + "Beware that structures in LLVM are unlike those in C.", + "LLVM type system allows structures to be literal or identified.", + "Literal structures are equivalent iff they have the same elements.", + "Identified structures are equivalent iff they have the same name.", + "In llvm_cbuilder, all structures are, by default, literal types.", + "", + "Here's an example:" + ] + }, + { + "cell_type": "code", + "collapsed": true, + "input": [ + "class Vector2D(CStruct):", + " _fields_ = [", + " ('x', C.float),", + " ('y', C.float),", + " ]" + ], + "language": "python", + "outputs": [], + "prompt_number": 7 + }, + { + "cell_type": "markdown", + "source": [ + "That's all you need for a structure. Very much like defining structures with ctypes.", + "", + "We can also bind methods to structures which inline code to the caller." + ] + }, + { + "cell_type": "code", + "collapsed": true, + "input": [ + "class Vector2D(CStruct):", + " _fields_ = [", + " ('x', C.float),", + " ('y', C.float),", + " ]", + " ", + " def add(self, other):", + " self.x += other.x", + " self.y += other.y" + ], + "language": "python", + "outputs": [], + "prompt_number": 8 + }, + { + "cell_type": "markdown", + "source": [ + "Setup a a test function:" + ] + }, + { + "cell_type": "code", + "collapsed": true, + "input": [ + "class TestVector(CDefinition):", + " _name_ = 'testvector'", + " _retty_ = C.float", + " _argtys_ = [('x', C.float), ", + " ('y', C.float)] ", + " ", + " def body(self, x, y):", + " va = self.var(Vector2D)", + " vb = self.var(Vector2D)", + " va.x.assign(self.constant(C.float, 10))", + " va.y.assign(self.constant(C.float, 20))", + " vb.x.assign(x)", + " vb.y.assign(y)", + " ", + " va.add(vb)", + " ", + " return self.ret(va.x * va.y)", + " " + ], + "language": "python", + "outputs": [], + "prompt_number": 9 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "llvm_testvec = TestVector()(m)", + "testvec = exe.get_ctype_function(llvm_testvec, \"float, float, float\")", + "print(testvec(-8, 5)) # should output 50" + ], + "language": "python", + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "50.0" + ] + } + ], + "prompt_number": 10 + }, + { + "cell_type": "markdown", + "source": [ + "Generic Programming", + "-------------------", + "", + "llvm_cbuilder supports generic function (or template function for C++).", + "When a `CDefinition` defines the `specialize` class method,", + "its constructor will invoke `specialize`.", + "All parameters passed to the constructor are forwarded to `specialize`.", + "", + "The constructor of a generic CDefinition creates a new dynamic class with the original CDefinition subclass as the parent.", + "Thus, `specialize` can modify class attributes without affecting the parent." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "class GenericSquare(CDefinition):", + " @classmethod", + " def specialize(cls, data_type):", + " cls._name_ = '.'.join(['square', str(data_type)]) # set function name", + " cls._retty_ = data_type # set return type", + " cls._argtys_ = [ ('x', data_type) ] # set argument type", + " ", + " def body(self, x):", + " y = x * x # just write out the expression", + " self.ret(y)", + " ", + "llvm_square_int = GenericSquare(data_type=C.int)(m)", + "llvm_square_float = GenericSquare(data_type=C.float)(m)", + "", + "print(llvm_square_int)", + "print(llvm_square_float)" + ], + "language": "python", + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "", + "define i32 @square.i32(i32 %x) {", + "decl:", + " %x1 = alloca i32", + " br label %body", + "", + "body: ; preds = %decl", + " store i32 %x, i32* %x1", + " %0 = load i32* %x1", + " %1 = load i32* %x1", + " %2 = mul i32 %0, %1", + " ret i32 %2", + "}", + "", + "", + "define float @square.float(float %x) {", + "decl:", + " %x1 = alloca float", + " br label %body", + "", + "body: ; preds = %decl", + " store float %x, float* %x1", + " %0 = load float* %x1", + " %1 = load float* %x1", + " %2 = fmul float %0, %1", + " ret float %2", + "}", + "" + ] + } + ], + "prompt_number": 11 + }, + { + "cell_type": "markdown", + "source": [ + "External Functions", + "------------------", + "", + "`CExternal` is a convenient class to help accessing externally-defined functions.", + "", + "We define an external interface to `sqrtf()` in libm." + ] + }, + { + "cell_type": "code", + "collapsed": true, + "input": [ + "class LibM(CExternal):", + " sqrtf = Type.function(C.float, [C.float])" + ], + "language": "python", + "outputs": [], + "prompt_number": 12 + }, + { + "cell_type": "markdown", + "source": [ + "All class attributes that are `llvm.core.FunctionType` in `CExternal` ", + "are converted to a `CFunc` instance.", + "`CFunc` instances are callable.", + "`CFunc.__call__` generates code that perform the corresponding LLVM function call.", + "", + "The following example demonstrates the usage of `CExternal` and `CFunc`." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "class TestSqrtf(CDefinition):", + " _name_ = 'test_sqrtf'", + " _retty_ = C.float", + " _argtys_ = [('x', C.float)]", + " ", + " def body(self, x):", + " libm = LibM(self) # init the API", + " y = libm.sqrtf(x) # call sqrtf", + " self.ret(y)", + " ", + "llvm_sqrtf = TestSqrtf()(m)", + "sqrtf = exe.get_ctype_function(llvm_sqrtf, 'float, float')", + "print(sqrtf(144))" + ], + "language": "python", + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "12.0" + ] + } + ], + "prompt_number": 13 + }, + { + "cell_type": "markdown", + "source": [ + "Casting", + "-------", + "", + "Unlike C, llvm_cbuilder does not automatically cast variables.", + "It is up to the user to explicit cast types.", + "All binary operations require both operands to be the same type.", + "All values in llvm_cbuilder has a `cast(destty)` method.", + "", + "For example:" + ] + }, + { + "cell_type": "code", + "collapsed": true, + "input": [ + "class CastExample(CDefinition):", + " _name_ = 'cast_example'", + " _retty_ = C.float", + " _argtys_ = [('x', C.int)]", + " ", + " def body(self, x):", + " y = x.cast(C.float) # cast integer x to float", + " self.ret(y)" + ], + "language": "python", + "outputs": [], + "prompt_number": 14 + }, + { + "cell_type": "markdown", + "source": [ + "More... (TODO)" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/notebook/llvm_cbuilder_intro.pdf b/notebook/llvm_cbuilder_intro.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b647a1548d8c950965013b3fe4535650f814aa88 GIT binary patch literal 130681 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

iN>A%`R~DAGU$ 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

i9zDz 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

ZncP{NN8~whjP*zOezTgtlJrNRE{iZP0>|@V2 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 Tu*h5>M8d&1?!9}JHGcaqKLP57 literal 0 HcmV?d00001 From c1d3044c77848d238f6cfa693665c0e5ba31875f Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Sat, 11 Aug 2012 14:06:13 -0700 Subject: [PATCH 29/57] Fix parallel_vectorize_from_func: * use signed integer; * use multiprocessing.cpu_count() to determine # of threads. --- notebook/parallel_vectorize.ipynb | 160 ++++++++++++++++++++++++++++++ parallel_vectorize.py | 18 ++-- 2 files changed, 171 insertions(+), 7 deletions(-) create mode 100644 notebook/parallel_vectorize.ipynb diff --git a/notebook/parallel_vectorize.ipynb b/notebook/parallel_vectorize.ipynb new file mode 100644 index 0000000..b0bdaff --- /dev/null +++ b/notebook/parallel_vectorize.ipynb @@ -0,0 +1,160 @@ +{ + "metadata": { + "name": "parallel_vectorize" + }, + "nbformat": 2, + "worksheets": [ + { + "cells": [ + { + "cell_type": "markdown", + "source": [ + "Parallel Vectorize", + "------------------", + "", + "The `parallel_vectorize.py` module contains a set of llvmpy code generators", + "for creating mulithreaded _ufunc_. ", + "It depends on the new `numpy.fromfunc` for turning arbitrary function pointers into _ufunc_.", + "", + "From LLVM Function", + "------------------", + "", + "The `parallel_vectorize_from_func` method generates multithreaded _ufunc_ from LLVM functions.", + "", + "First, we will implement a workload function:" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "from llvm_cbuilder import *", + "from llvm_cbuilder import shortnames as C", + "from llvm.core import *", + "", + "# Implement a workload", + "class Square(CDefinition):", + " _name_ = 'square'", + " _retty_ = C.double # 1 output: double", + " _argtys_ = [('x', C.double)] # 1 input: double", + " ", + " def body(self, x):", + " self.ret(x * x)", + "", + "m = Module.new('my_module')", + "llvm_square = Square()(m) # Generate a llvm function", + "print(llvm_square) " + ], + "language": "python", + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "", + "define double @square(double %x) {", + "decl:", + " %x1 = alloca double", + " br label %body", + "", + "body: ; preds = %decl", + " store double %x, double* %x1", + " %0 = load double* %x1", + " %1 = load double* %x1", + " %2 = fmul double %0, %1", + " ret double %2", + "}", + "" + ] + } + ], + "prompt_number": 1 + }, + { + "cell_type": "markdown", + "source": [ + "Then, we will generate a _ufunc_ from `llvm_square`:" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "from llvm.ee import *", + "engine = EngineBuilder.new(m).create() # Generate JIT engine", + "", + "from parallel_vectorize import parallel_vectorize_from_func", + "ufunc_square = parallel_vectorize_from_func(llvm_square, engine) # Generate UFunc" + ], + "language": "python", + "outputs": [], + "prompt_number": 2 + }, + { + "cell_type": "markdown", + "source": [ + "We are ready to use `ufunc_square` as a regular _ufunc_." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "import numpy as np", + "A = np.arange(10., dtype=np.double)", + "ufunc_square(A)" + ], + "language": "python", + "outputs": [ + { + "output_type": "pyout", + "prompt_number": 3, + "text": [ + "array([ 0., 1., 4., 9., 16., 25., 36., 49., 64., 81.])" + ] + } + ], + "prompt_number": 3 + }, + { + "cell_type": "markdown", + "source": [ + "Here's another example that uses three inputs:" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "class SumOfThree(CDefinition):", + " _name_ = 'sum.of.three'", + " _retty_ = C.int", + " _argtys_ = [('x', C.int),", + " ('y', C.int),", + " ('z', C.int)]", + " def body(self, x, y, z):", + " self.ret( x + y + z )", + "", + "llvm_sum3 = SumOfThree()(m)", + "ufunc_sum3 = parallel_vectorize_from_func(llvm_sum3, engine)", + "A = np.arange(10, dtype=np.int32)", + "B = A * 10", + "C = A * 100", + "ufunc_sum3(A, B, C)" + ], + "language": "python", + "outputs": [ + { + "output_type": "pyout", + "prompt_number": 4, + "text": [ + "array([ 0, 111, 222, 333, 444, 555, 666, 777, 888, 999], dtype=int32)" + ] + } + ], + "prompt_number": 4 + } + ] + } + ] +} \ No newline at end of file diff --git a/parallel_vectorize.py b/parallel_vectorize.py index 210dea8..a3fc743 100644 --- a/parallel_vectorize.py +++ b/parallel_vectorize.py @@ -416,10 +416,14 @@ def parallel_vectorize_from_func(lfunc, engine=None): from python. (This needs Jay's numpy.fromfunc). Otherwise, return the specialized ufunc as a llvm.core.Function ''' + import multiprocessing + NUM_CPU = multiprocessing.cpu_count() + fntype = lfunc.type.pointee - def_spuf = SpecializedParallelUFunc(ParallelUFuncPlatform(num_thread=2), - UFuncCoreGeneric(fntype), - CFuncRef(lfunc)) + def_spuf = SpecializedParallelUFunc( + ParallelUFuncPlatform(num_thread=NUM_CPU), + UFuncCoreGeneric(fntype), + CFuncRef(lfunc)) spuf = def_spuf(lfunc.module) if engine is None: return spuf @@ -432,10 +436,10 @@ def parallel_vectorize_from_func(lfunc, engine=None): # TODO refactor typemap = { - 'i8' : np.uint8, - 'i16' : np.uint16, - 'i32' : np.uint32, - 'i64' : np.uint64, + 'i8' : np.int8, + 'i16' : np.int16, + 'i32' : np.int32, + 'i64' : np.int64, 'float' : np.float32, 'double' : np.float64, } From 1009daf3fa032e94a5ddb26aa933148643c9c90e Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Sat, 11 Aug 2012 14:55:35 -0700 Subject: [PATCH 30/57] Add notebook: parallel_vectorize --- notebook/llvm_cbuilder_intro.pdf | Bin 130681 -> 73415 bytes notebook/parallel_vectorize.ipynb | 93 +++++++++++++++++++++++++++++- notebook/parallel_vectorize.pdf | Bin 0 -> 66363 bytes 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 notebook/parallel_vectorize.pdf diff --git a/notebook/llvm_cbuilder_intro.pdf b/notebook/llvm_cbuilder_intro.pdf index b647a1548d8c950965013b3fe4535650f814aa88..a7b17d48308c8792bce64c244e7678e19f1fc6df 100644 GIT binary patch literal 73415 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

iN>A%`R~DAGU$ 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

i9zDz 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

ZncP{NN8~whjP*zOezTgtlJrNRE{iZP0>|@V2 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 Tu*h5>M8d&1?!9}JHGcaqKLP57 diff --git a/notebook/parallel_vectorize.ipynb b/notebook/parallel_vectorize.ipynb index b0bdaff..518594a 100644 --- a/notebook/parallel_vectorize.ipynb +++ b/notebook/parallel_vectorize.ipynb @@ -78,7 +78,7 @@ }, { "cell_type": "code", - "collapsed": false, + "collapsed": true, "input": [ "from llvm.ee import *", "engine = EngineBuilder.new(m).create() # Generate JIT engine", @@ -153,6 +153,97 @@ } ], "prompt_number": 4 + }, + { + "cell_type": "markdown", + "source": [ + "* * *", + "", + "Internals", + "---------", + "", + "There are four functions behind each multithreaded _ufunc_.", + "", + "1. the workload function (user defined);", + "2. the thread worker function (`UFuncCoreGeneric`);", + "3. the thread manager function (`ParallelUFuncPlatform`);", + "4. the ufunc entry point function (`SpecializedParallelUFunc`).", + "", + "**UFuncCoreGeneric** specializes to a llvm function type.", + "**It currently understands simple builtin scalar types (integers, float, double) only as arguments and return-type for the workload function.**", + "It sends work items to the workload function and performs work-stealing when it has finished its own workqueue.", + "Work-stealing uses atomic compare-exchange (or CAS) instruction to acquire ownership of a workqueue.", + "Work-stealing is implemented in the `UFuncCore._do_work_stealing`.", + "It can be disabled on platform that does not support atomic operations.", + "", + "**ParallelUFuncPlatform** specializes to the maximum number of threads. ", + "It divides all works equally among all threads.", + "Each thread executes the function generated by `UFuncCoreGeneric` once.", + "", + "**SpecializedParallelUFunc** is the specialized _ufunc_ entry point for a specific combination of ", + "workload, UFuncCoreGeneric and ParallelUFuncPlatform.", + "", + "Here's an example that uses `SpecializedParallelUFunc` directly for the `SumOfThree` workload." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "import parallel_vectorize as pv", + "# specialize", + "def_spuf = pv.SpecializedParallelUFunc(pv.ParallelUFuncPlatform(num_thread=2),", + " pv.UFuncCoreGeneric(llvm_sum3.type.pointee),", + " CFuncRef(llvm_sum3))", + "# define", + "spuf = def_spuf(m)", + "print(spuf.name)" + ], + "language": "python", + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "specialized_parallel_ufunc_2_ufunc_worker.i32.i32.i32.i32_sum.of.three" + ] + } + ], + "prompt_number": 5 + }, + { + "cell_type": "markdown", + "source": [ + "`CFuncRef` also accepts arbitrary function pointer as long as the function type is provided." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "# specialize", + "fnty = llvm_sum3.type.pointee", + "sum3ptr = engine.get_pointer_to_function(llvm_sum3)", + "print(\"as function pointer: %x\" % sum3ptr)", + "def_spuf = pv.SpecializedParallelUFunc(pv.ParallelUFuncPlatform(num_thread=2),", + " pv.UFuncCoreGeneric(fnty),", + " CFuncRef('sum3.as.ptr', fnty, sum3ptr)) # name, type, ptr", + "# define", + "spuf = def_spuf(m)", + "print(spuf.name)" + ], + "language": "python", + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "as function pointer: 7f0bfc090740", + "specialized_parallel_ufunc_2_ufunc_worker.i32.i32.i32.i32_sum3.as.ptr" + ] + } + ], + "prompt_number": 6 } ] } diff --git a/notebook/parallel_vectorize.pdf b/notebook/parallel_vectorize.pdf new file mode 100644 index 0000000000000000000000000000000000000000..394f3647f8bbc6b2a742f000d9eba3601f4b0fe0 GIT binary patch literal 66363 zcmce71CS-{wq;dy*|yCt+qP}nwr$(CZQHhOyQ^#Zzj*K6nHw<^F>fa0MCQqLc4mJ2 z%Z#&Qt+kUxPDq4?{wFgeN%M8hCnO_29louB1tb?2KCP6ojj59vKGPpa0TLe{pH{@& z%E{Q_&(=!c$ymtP(ALNpl7|P<(aFJB-x|^_YZ-eo=CJL4ruGi5DP@?|zaJauC_&Q? z)IsfNuVoVpm>baL@tZFq)*#tvJms@K?aF}8AiV-D@|lpimkJAZJ~YtPV}S!Fr+@

!vaZmL`9&HMV5usg-miO9%<0JmI*Vv&|k9ONu%m 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 Date: Mon, 13 Aug 2012 15:53:09 -0700 Subject: [PATCH 31/57] reorganize --- README.md | 23 - llvm_cbuilder/__init__.py => __init__.py | 0 llvm_cbuilder/builder.py => builder.py | 0 llvm_cbuilder/executor.py => executor.py | 0 llvm_cbuilder/libc.py => libc.py | 0 notebook/llvm_cbuilder_intro.ipynb | 586 ------------------- notebook/llvm_cbuilder_intro.pdf | Bin 73415 -> 0 bytes notebook/parallel_vectorize.ipynb | 251 -------- notebook/parallel_vectorize.pdf | Bin 66363 -> 0 bytes parallel_vectorize.py | 461 --------------- llvm_cbuilder/shortnames.py => shortnames.py | 0 test_parallel_vectorize.py | 130 ---- test_parallel_vectorize_numpy.py | 57 -- test_parallel_vectorize_numpy_2.py | 69 --- test_usecase_1_1.py | 56 -- test_usecase_2_1.py | 58 -- tests/test_atomic_add.py | 115 ---- tests/test_atomic_cmpxchg.py | 122 ---- tests/test_cstr_collide.py | 17 - tests/test_isprime.py | 125 ---- tests/test_loopcontrol.py | 135 ----- tests/test_nestedloops.py | 128 ---- tests/test_print.py | 60 -- tests/test_pthread.py | 90 --- tests/test_struct.py | 52 -- 25 files changed, 2535 deletions(-) delete mode 100644 README.md rename llvm_cbuilder/__init__.py => __init__.py (100%) rename llvm_cbuilder/builder.py => builder.py (100%) rename llvm_cbuilder/executor.py => executor.py (100%) rename llvm_cbuilder/libc.py => libc.py (100%) delete mode 100644 notebook/llvm_cbuilder_intro.ipynb delete mode 100644 notebook/llvm_cbuilder_intro.pdf delete mode 100644 notebook/parallel_vectorize.ipynb delete mode 100644 notebook/parallel_vectorize.pdf delete mode 100644 parallel_vectorize.py rename llvm_cbuilder/shortnames.py => shortnames.py (100%) delete mode 100644 test_parallel_vectorize.py delete mode 100644 test_parallel_vectorize_numpy.py delete mode 100644 test_parallel_vectorize_numpy_2.py delete mode 100644 test_usecase_1_1.py delete mode 100644 test_usecase_2_1.py delete mode 100644 tests/test_atomic_add.py delete mode 100644 tests/test_atomic_cmpxchg.py delete mode 100644 tests/test_cstr_collide.py delete mode 100644 tests/test_isprime.py delete mode 100644 tests/test_loopcontrol.py delete mode 100644 tests/test_nestedloops.py delete mode 100644 tests/test_print.py delete mode 100644 tests/test_pthread.py delete mode 100644 tests/test_struct.py diff --git a/README.md b/README.md deleted file mode 100644 index a63039a..0000000 --- a/README.md +++ /dev/null @@ -1,23 +0,0 @@ -llvm_cbuilder -------------- - -A llvm-py Builder wrapper for writing in slightly higher-level constructs. -This is aiming for two usecases: - -1. Emit LLVM code in a more human-readable way; - -2. Writing low-level code that you can't do it properly/portably with C, e.g - template (generic), atomic operations, memory ordering... - - - -Parallel Vectorize ------------------- - -`parallel_vectorize.py` implements a set of code generator that bases on -llvm_cbuilder to create specialized parallel ufunc. - -See `test_parallel_vectorize_numpy*.py` for testing and demo of the code -with the new `numpy.fromfunc()`. - - diff --git a/llvm_cbuilder/__init__.py b/__init__.py similarity index 100% rename from llvm_cbuilder/__init__.py rename to __init__.py diff --git a/llvm_cbuilder/builder.py b/builder.py similarity index 100% rename from llvm_cbuilder/builder.py rename to builder.py diff --git a/llvm_cbuilder/executor.py b/executor.py similarity index 100% rename from llvm_cbuilder/executor.py rename to executor.py diff --git a/llvm_cbuilder/libc.py b/libc.py similarity index 100% rename from llvm_cbuilder/libc.py rename to libc.py diff --git a/notebook/llvm_cbuilder_intro.ipynb b/notebook/llvm_cbuilder_intro.ipynb deleted file mode 100644 index 9234f3d..0000000 --- a/notebook/llvm_cbuilder_intro.ipynb +++ /dev/null @@ -1,586 +0,0 @@ -{ - "metadata": { - "name": "llvm_cbuilder_intro" - }, - "nbformat": 2, - "worksheets": [ - { - "cells": [ - { - "cell_type": "markdown", - "source": [ - "llvm_cbuilder: A Quick Tour", - "---------------------------", - "", - "Writing code generation logic in pure llvmpy requires one to think like a compiler -- ", - "dealing with basic-blocks, branches, SSA, etc.", - "The resulting code looks like assembly code, with no apparent hint about the control-flow.", - "", - "llvm_cbuilder is originally designed to simplify the translation of C code into llvmpy.", - "It provides a simple API for mimicking C programming in Python.", - "With llvm_cbulder, one can write very low-level code, probably even lower-level than C.", - "At the same time, one can use if-else, loop, structures and a bit of OOP." - ] - }, - { - "cell_type": "markdown", - "source": [ - "A Simple Example", - "----------------", - "", - "Let's see some action.", - "We will define a function that calculates the square of a double-precision float." - ] - }, - { - "cell_type": "code", - "collapsed": true, - "input": [ - "from llvm.core import *", - "from llvm_cbuilder import *", - "import llvm_cbuilder.shortnames as C", - "", - "class Square(CDefinition):", - " # prototype: double square(double x)", - " _name_ = 'square' # function name", - " _retty_ = C.double", - " _argtys_ = [ ('x', C.double) ]", - " ", - " def body(self, x):", - " y = x * x # just write out the expression", - " self.ret(y)" - ], - "language": "python", - "outputs": [], - "prompt_number": 1 - }, - { - "cell_type": "markdown", - "source": [ - "Notice how numerical expressions can be written naturally." - ] - }, - { - "cell_type": "markdown", - "source": [ - "Let's see the emitted code:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "m = Module.new('my_module')", - "llvm_square = Square()(m) # define square() in my_module", - "print(m)" - ], - "language": "python", - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "; ModuleID = 'my_module'", - "", - "define double @square(double %x) {", - "decl:", - " %x1 = alloca double", - " br label %body", - "", - "body: ; preds = %decl", - " store double %x, double* %x1", - " %0 = load double* %x1", - " %1 = load double* %x1", - " %2 = fmul double %0, %1", - " ret double %2", - "}", - "" - ] - } - ], - "prompt_number": 2 - }, - { - "cell_type": "markdown", - "source": [ - "Let's generate a ctype function object to call `square()` in the Python code:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "exe = CExecutor(m)", - "square = exe.get_ctype_function(llvm_square, \"double, double\")", - "result = square(1.2)", - "print(result)" - ], - "language": "python", - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "1.44" - ] - } - ], - "prompt_number": 3 - }, - { - "cell_type": "markdown", - "source": [ - "Control Flow Constructs", - "-----------------------", - "", - "The main strength of llvm_cbuilder is the control-flow constructs. ", - "They use the python \"with\" statement to setup new code blocks to", - "contain different paths of the control flow.", - "", - "The following example demonstrates both the if-else and loop contructs:" - ] - }, - { - "cell_type": "code", - "collapsed": true, - "input": [ - "class IsPrime(CDefinition):", - " # prototype int isprime(int x)", - " _name_ = 'isprime'", - " _retty_ = C.int", - " _argtys_ = [ ('x', C.int) ]", - " ", - " def body(self, x):", - " two = self.constant(C.int, 2)", - " true = one = self.constant(C.int, 1)", - " false = zero = self.constant(C.int, 0)", - " ", - " with self.ifelse( x <= two ) as ifelse:", - " with ifelse.then():", - " self.ret(true)", - " ", - " with self.ifelse( (x % two) == zero ) as ifelse:", - " with ifelse.then():", - " self.ret(false)", - " ", - " idx = self.var(C.int, 3, name='idx')", - " with self.loop() as loop:", - " with loop.condition() as setcond:", - " setcond( idx < x )", - " ", - " with loop.body():", - " with self.ifelse( (x % idx) == zero ) as ifelse:", - " with ifelse.then():", - " self.ret(false)", - " idx += two", - " self.ret(true)" - ], - "language": "python", - "outputs": [], - "prompt_number": 4 - }, - { - "cell_type": "markdown", - "source": [ - "The code above is quite verbose.", - "It is like writing in Pascal or Ada. ", - "But, it is still easier than writing in llvmpy directly.", - "Take a look at the generated LLVM IR below." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "llvm_isprime = IsPrime()(m)", - "print(llvm_isprime)" - ], - "language": "python", - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "", - "define i32 @isprime(i32 %x) {", - "decl:", - " %x1 = alloca i32", - " %idx = alloca i32", - " br label %body", - "", - "body: ; preds = %decl", - " store i32 %x, i32* %x1", - " %0 = load i32* %x1", - " %1 = icmp sle i32 %0, 2", - " br i1 %1, label %if.then, label %if.else", - "", - "if.then: ; preds = %body", - " ret i32 1", - "", - "if.else: ; preds = %body", - " br label %if.end", - "", - "if.end: ; preds = %if.else", - " %2 = load i32* %x1", - " %3 = srem i32 %2, 2", - " %4 = icmp eq i32 %3, 0", - " br i1 %4, label %if.then2, label %if.else3", - "", - "if.then2: ; preds = %if.end", - " ret i32 0", - "", - "if.else3: ; preds = %if.end", - " br label %if.end4", - "", - "if.end4: ; preds = %if.else3", - " store i32 3, i32* %idx", - " br label %loop.cond", - "", - "loop.cond: ; preds = %if.end7, %if.end4", - " %5 = load i32* %idx", - " %6 = load i32* %x1", - " %7 = icmp slt i32 %5, %6", - " br i1 %7, label %loop.body, label %loop.end", - "", - "loop.body: ; preds = %loop.cond", - " %8 = load i32* %x1", - " %9 = load i32* %idx", - " %10 = srem i32 %8, %9", - " %11 = icmp eq i32 %10, 0", - " br i1 %11, label %if.then5, label %if.else6", - "", - "loop.end: ; preds = %loop.cond", - " ret i32 1", - "", - "if.then5: ; preds = %loop.body", - " ret i32 0", - "", - "if.else6: ; preds = %loop.body", - " br label %if.end7", - "", - "if.end7: ; preds = %if.else6", - " %12 = load i32* %idx", - " %13 = add i32 %12, 2", - " store i32 %13, i32* %idx", - " br label %loop.cond", - "}", - "" - ] - } - ], - "prompt_number": 5 - }, - { - "cell_type": "markdown", - "source": [ - "We'll setup a ctype function object to try it out:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "isprime = exe.get_ctype_function(llvm_isprime, 'int, int')", - "prime_100 = filter(isprime, range(2, 100))", - "print(prime_100)" - ], - "language": "python", - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]" - ] - } - ], - "prompt_number": 6 - }, - { - "cell_type": "markdown", - "source": [ - "Structures", - "----------", - "", - "It is possible to create structures in llvm_cbuilder. ", - "Beware that structures in LLVM are unlike those in C.", - "LLVM type system allows structures to be literal or identified.", - "Literal structures are equivalent iff they have the same elements.", - "Identified structures are equivalent iff they have the same name.", - "In llvm_cbuilder, all structures are, by default, literal types.", - "", - "Here's an example:" - ] - }, - { - "cell_type": "code", - "collapsed": true, - "input": [ - "class Vector2D(CStruct):", - " _fields_ = [", - " ('x', C.float),", - " ('y', C.float),", - " ]" - ], - "language": "python", - "outputs": [], - "prompt_number": 7 - }, - { - "cell_type": "markdown", - "source": [ - "That's all you need for a structure. Very much like defining structures with ctypes.", - "", - "We can also bind methods to structures which inline code to the caller." - ] - }, - { - "cell_type": "code", - "collapsed": true, - "input": [ - "class Vector2D(CStruct):", - " _fields_ = [", - " ('x', C.float),", - " ('y', C.float),", - " ]", - " ", - " def add(self, other):", - " self.x += other.x", - " self.y += other.y" - ], - "language": "python", - "outputs": [], - "prompt_number": 8 - }, - { - "cell_type": "markdown", - "source": [ - "Setup a a test function:" - ] - }, - { - "cell_type": "code", - "collapsed": true, - "input": [ - "class TestVector(CDefinition):", - " _name_ = 'testvector'", - " _retty_ = C.float", - " _argtys_ = [('x', C.float), ", - " ('y', C.float)] ", - " ", - " def body(self, x, y):", - " va = self.var(Vector2D)", - " vb = self.var(Vector2D)", - " va.x.assign(self.constant(C.float, 10))", - " va.y.assign(self.constant(C.float, 20))", - " vb.x.assign(x)", - " vb.y.assign(y)", - " ", - " va.add(vb)", - " ", - " return self.ret(va.x * va.y)", - " " - ], - "language": "python", - "outputs": [], - "prompt_number": 9 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "llvm_testvec = TestVector()(m)", - "testvec = exe.get_ctype_function(llvm_testvec, \"float, float, float\")", - "print(testvec(-8, 5)) # should output 50" - ], - "language": "python", - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "50.0" - ] - } - ], - "prompt_number": 10 - }, - { - "cell_type": "markdown", - "source": [ - "Generic Programming", - "-------------------", - "", - "llvm_cbuilder supports generic function (or template function for C++).", - "When a `CDefinition` defines the `specialize` class method,", - "its constructor will invoke `specialize`.", - "All parameters passed to the constructor are forwarded to `specialize`.", - "", - "The constructor of a generic CDefinition creates a new dynamic class with the original CDefinition subclass as the parent.", - "Thus, `specialize` can modify class attributes without affecting the parent." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "class GenericSquare(CDefinition):", - " @classmethod", - " def specialize(cls, data_type):", - " cls._name_ = '.'.join(['square', str(data_type)]) # set function name", - " cls._retty_ = data_type # set return type", - " cls._argtys_ = [ ('x', data_type) ] # set argument type", - " ", - " def body(self, x):", - " y = x * x # just write out the expression", - " self.ret(y)", - " ", - "llvm_square_int = GenericSquare(data_type=C.int)(m)", - "llvm_square_float = GenericSquare(data_type=C.float)(m)", - "", - "print(llvm_square_int)", - "print(llvm_square_float)" - ], - "language": "python", - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "", - "define i32 @square.i32(i32 %x) {", - "decl:", - " %x1 = alloca i32", - " br label %body", - "", - "body: ; preds = %decl", - " store i32 %x, i32* %x1", - " %0 = load i32* %x1", - " %1 = load i32* %x1", - " %2 = mul i32 %0, %1", - " ret i32 %2", - "}", - "", - "", - "define float @square.float(float %x) {", - "decl:", - " %x1 = alloca float", - " br label %body", - "", - "body: ; preds = %decl", - " store float %x, float* %x1", - " %0 = load float* %x1", - " %1 = load float* %x1", - " %2 = fmul float %0, %1", - " ret float %2", - "}", - "" - ] - } - ], - "prompt_number": 11 - }, - { - "cell_type": "markdown", - "source": [ - "External Functions", - "------------------", - "", - "`CExternal` is a convenient class to help accessing externally-defined functions.", - "", - "We define an external interface to `sqrtf()` in libm." - ] - }, - { - "cell_type": "code", - "collapsed": true, - "input": [ - "class LibM(CExternal):", - " sqrtf = Type.function(C.float, [C.float])" - ], - "language": "python", - "outputs": [], - "prompt_number": 12 - }, - { - "cell_type": "markdown", - "source": [ - "All class attributes that are `llvm.core.FunctionType` in `CExternal` ", - "are converted to a `CFunc` instance.", - "`CFunc` instances are callable.", - "`CFunc.__call__` generates code that perform the corresponding LLVM function call.", - "", - "The following example demonstrates the usage of `CExternal` and `CFunc`." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "class TestSqrtf(CDefinition):", - " _name_ = 'test_sqrtf'", - " _retty_ = C.float", - " _argtys_ = [('x', C.float)]", - " ", - " def body(self, x):", - " libm = LibM(self) # init the API", - " y = libm.sqrtf(x) # call sqrtf", - " self.ret(y)", - " ", - "llvm_sqrtf = TestSqrtf()(m)", - "sqrtf = exe.get_ctype_function(llvm_sqrtf, 'float, float')", - "print(sqrtf(144))" - ], - "language": "python", - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "12.0" - ] - } - ], - "prompt_number": 13 - }, - { - "cell_type": "markdown", - "source": [ - "Casting", - "-------", - "", - "Unlike C, llvm_cbuilder does not automatically cast variables.", - "It is up to the user to explicit cast types.", - "All binary operations require both operands to be the same type.", - "All values in llvm_cbuilder has a `cast(destty)` method.", - "", - "For example:" - ] - }, - { - "cell_type": "code", - "collapsed": true, - "input": [ - "class CastExample(CDefinition):", - " _name_ = 'cast_example'", - " _retty_ = C.float", - " _argtys_ = [('x', C.int)]", - " ", - " def body(self, x):", - " y = x.cast(C.float) # cast integer x to float", - " self.ret(y)" - ], - "language": "python", - "outputs": [], - "prompt_number": 14 - }, - { - "cell_type": "markdown", - "source": [ - "More... (TODO)" - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/notebook/llvm_cbuilder_intro.pdf b/notebook/llvm_cbuilder_intro.pdf deleted file mode 100644 index a7b17d48308c8792bce64c244e7678e19f1fc6df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73415 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

!vaZmL`9&HMV5usg-miO9%<0JmI*Vv&|k9ONu%m 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 num_thread, equally divide the works. - # If total_work % num_thread != 0, the last thread does all remaining works. - # If total_work < num_thread, each thread does one work, - # and set num_thread to total_work - N = dimensions[0] - ChunkSize = self.var_copy(N / num_thread.cast(N.type)) - ChunkSize_NULL = self.constant_null(ChunkSize.type) - with self.ifelse(ChunkSize == ChunkSize_NULL) as ifelse: - with ifelse.then(): - ChunkSize.assign(self.constant(ChunkSize.type, 1)) - num_thread.assign(N.cast(num_thread.type)) - - # Populate workqueue for all threads - self._populate_workqueues(workqueues, N, ChunkSize, num_thread) - - # Populate contexts for all threads - self._populate_context(contexts, common, num_thread) - - # Dispatch worker threads - self._dispatch_worker(worker, contexts, num_thread) - - ## DEBUG ONLY ## - # Check for race condition - if True: - total_completed = self.var(C.intp, 0, name='total_completed') - for t in range(ThreadCount): - cur_ctxt = contexts[t].as_struct(Context) - total_completed += cur_ctxt.completed - # self.debug(cur_ctxt.id, 'completed', cur_ctxt.completed) - - with self.ifelse( total_completed == N ) as ifelse: - with ifelse.then(): - # self.debug("All is well!") - pass # keep quite if all is well - with ifelse.otherwise(): - self.debug("ERROR: race occurred! Trigger segfault") - self.unreachable() - - # Return - self.ret() - - def _populate_workqueues(self, workqueues, N, ChunkSize, num_thread): - '''loop over all threads and populate the workqueue for each of them. - ''' - ONE = self.constant(num_thread.type, 1) - with self.for_range(num_thread) as (loop, i): - cur_wq = workqueues[i].as_struct(WorkQueue) - cur_wq.next.assign(i.cast(ChunkSize.type) * ChunkSize) - cur_wq.last.assign((i + ONE).cast(ChunkSize.type) * ChunkSize) - cur_wq.lock.assign(self.constant(C.int, 0)) - # end loop - last_wq = workqueues[num_thread - ONE].as_struct(WorkQueue) - last_wq.last.assign(N) - - def _populate_context(self, contexts, common, num_thread): - '''loop over all threads and populate contexts for each of them. - ''' - ONE = self.constant(num_thread.type, 1) - with self.for_range(num_thread) as (loop, i): - cur_ctxt = contexts[i].as_struct(Context) - cur_ctxt.common.assign(common.reference()) - cur_ctxt.id.assign(i) - cur_ctxt.completed.assign( - self.constant_null(cur_ctxt.completed.type)) - -class ParallelUFuncPosixMixin(object): - '''ParallelUFunc mixin that implements _dispatch_worker to use pthread. - ''' - def _dispatch_worker(self, worker, contexts, num_thread): - api = PThreadAPI(self) - NULL = self.constant_null(C.void_p) - - threads = self.array(api.pthread_t, num_thread, name='threads') - - # self.debug("launch threads") - # TODO error handling - - ONE = self.constant(num_thread.type, 1) - with self.for_range(num_thread) as (loop, i): - api.pthread_create(threads[i].reference(), NULL, worker, - contexts[i].reference().cast(C.void_p)) - - with self.for_range(num_thread) as (loop, i): - api.pthread_join(threads[i], NULL) - -class UFuncCore(CDefinition): - '''core work of a ufunc worker thread - - Subclass to implement UFuncCore._do_work - - Generates the workqueue handling and work stealing and invoke - the work function for each work item. - ''' - _name_ = 'ufunc_worker' - _argtys_ = [ - ('context', C.pointer(Context.llvm_type())), - ] - - def body(self, context): - context = context.as_struct(Context) - common = context.common.as_struct(ContextCommon) - tid = context.id - - # self.debug("start thread", tid, "/", common.num_thread) - workqueue = common.workqueues[tid].as_struct(WorkQueue) - - self._do_workqueue(common, workqueue, tid, context.completed) - self._do_work_stealing(common, tid, context.completed) # optional - - self.ret() - - def _do_workqueue(self, common, workqueue, tid, completed): - '''process local workqueue. - ''' - ZERO = self.constant_null(C.int) - - with self.forever() as loop: - workqueue.Lock() - # Critical section - item = self.var_copy(workqueue.next, name='item') - workqueue.next += self.constant(item.type, 1) - last = self.var_copy(workqueue.last, name='last') - # Release - workqueue.Unlock() - - with self.ifelse( item >= last ) as ifelse: - with ifelse.then(): - loop.break_loop() - - self._do_work(common, item, tid) - completed += self.constant(completed.type, 1) - - def _do_work_stealing(self, common, tid, completed): - '''steal work from other workqueues. - ''' - # self.debug("start work stealing", tid) - steal_continue = self.var(C.int, 1) - STEAL_STOP = self.constant_null(steal_continue.type) - - # Loop until all workqueues are done. - with self.loop() as loop: - with loop.condition() as setcond: - setcond( steal_continue != STEAL_STOP ) - - with loop.body(): - steal_continue.assign(STEAL_STOP) - self._do_work_stealing_innerloop(common, steal_continue, tid, - completed) - - def _do_work_stealing_innerloop(self, common, steal_continue, tid, - completed): - '''loop over all other threads and try to steal work. - ''' - with self.for_range(common.num_thread) as (loop, i): - with self.ifelse( i != tid ) as ifelse: - with ifelse.then(): - otherqueue = common.workqueues[i].as_struct(WorkQueue) - self._do_work_stealing_check(common, otherqueue, - steal_continue, tid, - completed) - - def _do_work_stealing_check(self, common, otherqueue, steal_continue, tid, - completed): - '''check the workqueue for any remaining work and steal it. - ''' - otherqueue.Lock() - # Acquired - ONE = self.constant(otherqueue.last.type, 1) - STEAL_CONTINUE = self.constant(steal_continue.type, 1) - with self.ifelse(otherqueue.next < otherqueue.last) as ifelse: - with ifelse.then(): - otherqueue.last -= ONE - item = self.var_copy(otherqueue.last) - - otherqueue.Unlock() - # Released - - self._do_work(common, item, tid) - completed += self.constant(completed.type, 1) - - # Mark incomplete thread - steal_continue.assign(STEAL_CONTINUE) - - with ifelse.otherwise(): - otherqueue.Unlock() - # Released - - def _do_work(self, common, item, tid): - '''prepare to call the actual work function - - Implementation depends on number and type of arguments. - ''' - raise NotImplementedError - -class SpecializedParallelUFunc(CDefinition): - '''a generic ufunc that wraps ParallelUFunc, UFuncCore and the workload - ''' - _argtys_ = [ - ('args', C.pointer(C.char_p)), - ('dimensions', C.pointer(C.intp)), - ('steps', C.pointer(C.intp)), - ('data', C.void_p), - ] - - def body(self, args, dimensions, steps, data,): - pufunc = self.depends(self.PUFuncDef) - core = self.depends(self.CoreDef) - func = self.depends(self.FuncDef) - to_void_p = lambda x: x.cast(C.void_p) - pufunc(to_void_p(func), to_void_p(core), args, dimensions, steps, data) - self.ret() - - @classmethod - def specialize(cls, pufunc_def, core_def, func_def): - '''specialize to a combination of ParallelUFunc, UFuncCore and workload - ''' - cls._name_ = 'specialized_%s_%s_%s'% (pufunc_def, core_def, func_def) - cls.PUFuncDef = pufunc_def - cls.CoreDef = core_def - cls.FuncDef = func_def - -class PThreadAPI(CExternal): - '''external declaration of pthread API - ''' - pthread_t = C.void_p - - pthread_create = Type.function(C.int, - [C.pointer(pthread_t), # thread_t - C.void_p, # thread attr - C.void_p, # function - C.void_p]) # arg - - pthread_join = Type.function(C.int, [C.void_p, C.void_p]) - - -class UFuncCoreGeneric(UFuncCore): - '''A generic ufunc core worker from LLVM function type - ''' - def _do_work(self, common, item, tid): - ufunc_type = Type.function(self.RETTY, self.ARGTYS) - ufunc_ptr = CFunc(self, common.func.cast(C.pointer(ufunc_type)).value) - - get_offset = lambda B, S, T: B[item * S].reference().cast(C.pointer(T)) - - indata = [] - for i, argty in enumerate(self.ARGTYS): - ptr = get_offset(common.args[i], common.steps[i], argty) - indata.append(ptr.load()) - - out_index = len(self.ARGTYS) - outptr = get_offset(common.args[out_index], common.steps[out_index], - self.RETTY) - - res = ufunc_ptr(*indata) - outptr.store(res) - - @classmethod - def specialize(cls, fntype): - '''specialize to a LLVM function type - - fntype : a LLVM function type (llvm.core.FunctionType) - ''' - cls._name_ = '.'.join([cls._name_] + - map(str, [fntype.return_type] + fntype.args)) - - cls.RETTY = fntype.return_type - cls.ARGTYS = tuple(fntype.args) - - -if sys.platform not in ['win32']: - class ParallelUFuncPlatform(ParallelUFunc, ParallelUFuncPosixMixin): - pass -else: - raise NotImplementedError("Threading for %s" % sys.platform) - - - -def parallel_vectorize_from_func(lfunc, engine=None): - '''create ufunc from a llvm.core.Function - - If engine is given, return a function object which can be called - from python. (This needs Jay's numpy.fromfunc). - Otherwise, return the specialized ufunc as a llvm.core.Function - ''' - import multiprocessing - NUM_CPU = multiprocessing.cpu_count() - - fntype = lfunc.type.pointee - def_spuf = SpecializedParallelUFunc( - ParallelUFuncPlatform(num_thread=NUM_CPU), - UFuncCoreGeneric(fntype), - CFuncRef(lfunc)) - spuf = def_spuf(lfunc.module) - if engine is None: - return spuf - else: - import numpy as np - - fptr = engine.get_pointer_to_function(spuf) - inct = len(fntype.args) - outct = 1 - - # TODO refactor - typemap = { - 'i8' : np.int8, - 'i16' : np.int16, - 'i32' : np.int32, - 'i64' : np.int64, - 'float' : np.float32, - 'double' : np.float64, - } - try: - ptr_t = long - except: - ptr_t = int - assert False, "Having check this yet" - - get_typenum = lambda T:np.dtype(typemap[str(T)]).num - assert fntype.return_type != C.void - tys = list(map(get_typenum, list(fntype.args) + [fntype.return_type])) - # Becareful that fromfunc does not provide full error checking yet. - # If typenum is out-of-bound, we have nasty memory corruptions. - # For instance, -1 for typenum will cause segfault. - # If elements of type-list (2nd arg) is tuple instead, - # there will also memory corruption. (Seems like code rewrite.) - return np.fromfunc([ptr_t(fptr)], [tys], inct, outct, [None]) - diff --git a/llvm_cbuilder/shortnames.py b/shortnames.py similarity index 100% rename from llvm_cbuilder/shortnames.py rename to shortnames.py diff --git a/test_parallel_vectorize.py b/test_parallel_vectorize.py deleted file mode 100644 index 1a850f5..0000000 --- a/test_parallel_vectorize.py +++ /dev/null @@ -1,130 +0,0 @@ -from parallel_vectorize import * - - -class Work_D_D(CDefinition): - _name_ = 'work_d_d' - _retty_ = C.double - _argtys_ = [ - ('inval', C.double), - ] - def body(self, inval): - self.ret(inval / self.constant(inval.type, 2.345)) - -class UFuncCore_D_D(UFuncCore): - ''' - Specialize UFuncCore for double input, double output. - ''' - _name_ = UFuncCore._name_ + '_d_d' - def _do_work(self, common, item, tid): - ufunc_type = Type.function(C.double, [C.double]) - ufunc_ptr = CFunc(self, common.func.cast(C.pointer(ufunc_type)).value) - - inbase = common.args[0] - outbase = common.args[1] - - instep = common.steps[0] - outstep = common.steps[1] - - indata = inbase[item * instep].reference().cast(C.pointer(C.double)) - outdata = outbase[item * outstep].reference().cast(C.pointer(C.double)) - - res = ufunc_ptr(indata.load()) - outdata.store(res) - -class ParallelUFuncPosix(ParallelUFunc, ParallelUFuncPosixMixin): - pass - -class Tester(CDefinition): - ''' - Generate test. - ''' - _name_ = 'tester' - - def body(self): - # depends - module = self.function.module - - ThreadCount = 2 - ArgCount = 2 - WorkCount = 10000 - - - spufdef = SpecializedParallelUFunc(ParallelUFuncPosix(num_thread=2), - UFuncCore_D_D(), - Work_D_D()) - - sppufunc = self.depends(spufdef) - - # real work - NULL = self.constant_null(C.void_p) - - args = self.array(C.char_p, 2, name='args') - - args_double = [] - for t in range(ThreadCount): - args_for_thread = self.array(C.double, WorkCount) - args[t].assign(args_for_thread.cast(C.char_p)) - args_double.append(args_for_thread) - - dims = self.array(C.intp, 1, name='dims') - dims[0].assign(self.constant(C.intp, WorkCount)) - - steps = self.array(C.intp, ArgCount, name='steps') - - for c in range(ArgCount): - steps[c].assign(self.constant(C.intp, 8)) - - # populate data - inbase = args_double[0] - - i = self.var(C.intp, 0) - with self.loop() as loop: - with loop.condition() as setcond: - setcond( i < self.constant(C.intp, WorkCount) ) - with loop.body(): - inbase[i].assign(i.cast(C.double)) - i += self.constant(C.intp, 1) - - # call parallel ufunc - sppufunc(args, dims, steps, NULL) - - # check error - outbase = args_double[-1] - with self.for_range(self.constant(C.intp, WorkCount)) as (loop, i): - test = outbase[i] != (inbase[i] / self.constant(C.double, 2.345)) - with self.ifelse( test ) as ifelse: - with ifelse.then(): - self.debug("Invalid data at i =", i, outbase[i], inbase[i]) - - self.ret() - -def main(): - module = Module.new(__name__) - - mpm = PassManager.new() - pmbuilder = PassManagerBuilder.new() - pmbuilder.opt_level = 3 - pmbuilder.populate(mpm) - - fntester = Tester.define(module) - -# print(module) - module.verify() - - mpm.run(module) - - print('optimized'.center(80,'-')) - print(module) - - # run - print('run') - exe = CExecutor(module) - func = exe.get_ctype_function(fntester, 'void') - - func() - # Will not reach here is race condition occurred - print('Good') - -if __name__ == '__main__': - main() - diff --git a/test_parallel_vectorize_numpy.py b/test_parallel_vectorize_numpy.py deleted file mode 100644 index 392c81d..0000000 --- a/test_parallel_vectorize_numpy.py +++ /dev/null @@ -1,57 +0,0 @@ -''' -Test parallel-vectorize with numpy.fromfunc. -Uses the work load from test_parallel_vectorize. -''' - -from test_parallel_vectorize import * - -import numpy as np - -def main(): - module = Module.new(__name__) - - spufdef = SpecializedParallelUFunc(ParallelUFuncPosix(num_thread=2), - UFuncCore_D_D(), - Work_D_D()) - - sppufunc = spufdef(module) - - module.verify() - - mpm = PassManager.new() - pmbuilder = PassManagerBuilder.new() - pmbuilder.opt_level = 3 - pmbuilder.populate(mpm) - - mpm.run(module) -# print module - - # run - - exe = CExecutor(module) - funcptr = exe.engine.get_pointer_to_function(sppufunc) - print("Function pointer: %x" % funcptr) - - ptr_t = long # py2 only - - # Becareful that fromfunc does not provide full error checking yet. - # If typenum is out-of-bound, we have nasty memory corruptions. - # For instance, -1 for typenum will cause segfault. - # If elements of type-list (2nd arg) is tuple instead, - # there will also memory corruption. (Seems like code rewrite.) - typenum = np.dtype(np.double).num - ufunc = np.fromfunc([ptr_t(funcptr)], [[typenum, typenum]], 1, 1, [None]) - - x = np.linspace(0., 10., 1000) - x.dtype=np.double -# print x - ans = ufunc(x) -# print ans - - if not ( ans == x/2.345 ).all(): - raise ValueError('Computation failed') - else: - print('Good') - -if __name__ == '__main__': - main() diff --git a/test_parallel_vectorize_numpy_2.py b/test_parallel_vectorize_numpy_2.py deleted file mode 100644 index f846a25..0000000 --- a/test_parallel_vectorize_numpy_2.py +++ /dev/null @@ -1,69 +0,0 @@ -''' -Test parallel-vectorize with numpy.fromfunc. -Uses the work load from test_parallel_vectorize. -This time we pass a function pointer. -''' - -from test_parallel_vectorize import * - -import numpy as np - -def main(): - module = Module.new(__name__) - exe = CExecutor(module) - - workdef = Work_D_D() - workfunc = workdef(module) - - # get pointer to workfunc - workfunc_ptr = exe.engine.get_pointer_to_function(workfunc) - - workdecl = CFuncRef(workfunc.name, workfunc.type.pointee, workfunc_ptr) - - spufdef = SpecializedParallelUFunc(ParallelUFuncPosix(num_thread=2), - UFuncCore_D_D(), - workdecl) - - - sppufunc = spufdef(module) - sppufunc.verify() - print(sppufunc) - module.verify() - - mpm = PassManager.new() - pmbuilder = PassManagerBuilder.new() - pmbuilder.opt_level = 3 - pmbuilder.populate(mpm) - - mpm.run(module) - print(module) - - - # run - - funcptr = exe.engine.get_pointer_to_function(sppufunc) - print("Function pointer: %x" % funcptr) - - ptr_t = long # py2 only - - # Becareful that fromfunc does not provide full error checking yet. - # If typenum is out-of-bound, we have nasty memory corruptions. - # For instance, -1 for typenum will cause segfault. - # If elements of type-list (2nd arg) is tuple instead, - # there will also memory corruption. (Seems like code rewrite.) - typenum = np.dtype(np.double).num - ufunc = np.fromfunc([ptr_t(funcptr)], [[typenum, typenum]], 1, 1, [None]) - - x = np.linspace(0., 10., 1000) - x.dtype=np.double -# print x - ans = ufunc(x) -# print ans - - if not ( ans == x/2.345 ).all(): - raise ValueError('Computation failed') - else: - print('Good') - -if __name__ == '__main__': - main() diff --git a/test_usecase_1_1.py b/test_usecase_1_1.py deleted file mode 100644 index 5cc4d7a..0000000 --- a/test_usecase_1_1.py +++ /dev/null @@ -1,56 +0,0 @@ -from parallel_vectorize import * -from llvm_cbuilder import shortnames as C -from llvm.core import * -import numpy as np -import unittest -from random import random - -class OneOne(CDefinition): - - def body(self, inval): - self.ret( (inval * inval).cast(self.OUT_TYPE) ) - - @classmethod - def specialize(cls, itype, otype): - cls._name_ = '.'.join(map(str, ['oneone', itype, otype])) - cls._retty_ = otype - cls._argtys_ = [ - ('inval', itype), - ] - cls.OUT_TYPE = otype - - -class TestParallelVectorize(unittest.TestCase): - def test_parallelvectorize_d_d(self): - self.template(C.double, C.double) - - def test_parallelvectorize_d_f(self): - self.template(C.double, C.float) - - def template(self, itype, otype): - module = Module.new(__name__) - exe = CExecutor(module) - - def_oneone = OneOne(itype, otype) - oneone = def_oneone(module) - ufunc = parallel_vectorize_from_func(oneone, exe.engine) - # print(module) - module.verify() - - x = np.linspace(.0, 10., 1000) - x.dtype = np.double - - ans = ufunc(x) - gold = x * x - - for x, y in zip(ans, gold): - if y != 0: - err = abs(x - y)/y - self.assertLess(err, 1e-6) - else: - self.assertEqual(x, y) - - -if __name__ == '__main__': - unittest.main() - diff --git a/test_usecase_2_1.py b/test_usecase_2_1.py deleted file mode 100644 index 087ee9f..0000000 --- a/test_usecase_2_1.py +++ /dev/null @@ -1,58 +0,0 @@ -from parallel_vectorize import * -from llvm_cbuilder import shortnames as C -from llvm.core import * -import numpy as np -import unittest -from random import random - -class TwoOne(CDefinition): - - def body(self, a, b): - self.ret( (a * b).cast(self.OUT_TYPE) ) - - @classmethod - def specialize(cls, itype1, itype2, otype): - cls._name_ = '.'.join(map(str, ['oneone', itype1, itype2, otype])) - cls._retty_ = otype - cls._argtys_ = [ - ('a', itype1), - ('b', itype2), - ] - cls.OUT_TYPE = otype - - -class TestParallelVectorize(unittest.TestCase): - def test_parallelvectorize_dd_d(self): - self.template(C.double, C.double, C.double) - - def test_parallelvectorize_dd_f(self): - self.template(C.double, C.double, C.float) - - def template(self, itype1, itype2, otype): - module = Module.new(__name__) - exe = CExecutor(module) - - def_twoone = TwoOne(itype1, itype2, otype) - twoone = def_twoone(module) - ufunc = parallel_vectorize_from_func(twoone, exe.engine) - # print(module) - module.verify() - - A = np.linspace(.0, 10., 1000) - A.dtype = np.double - B = np.linspace(-10., 0., 1000) - B.dtype = np.double - - ans = ufunc(A, B) - gold = A * B - - for x, y in zip(ans, gold): - if y != 0: - err = abs(x - y)/y - self.assertLess(err, 1e-6) - else: - self.assertEqual(x, y) - -if __name__ == '__main__': - unittest.main() - diff --git a/tests/test_atomic_add.py b/tests/test_atomic_add.py deleted file mode 100644 index 27688e7..0000000 --- a/tests/test_atomic_add.py +++ /dev/null @@ -1,115 +0,0 @@ -''' -Base on the test_pthread.py and extend to use atomic instructions -''' - -from llvm.core import * -from llvm.passes import * -from llvm.ee import * -from llvm_cbuilder import * -import llvm_cbuilder.shortnames as C -import unittest, logging - -# logging.basicConfig(level=logging.DEBUG) - -NUM_OF_THREAD = 4 -REPEAT = 10000 - -def gen_test_worker(mod): - cb = CBuilder.new_function(mod, 'worker', C.void, [C.pointer(C.int)]) - pval = cb.args[0] - one = cb.constant(pval.type.pointee, 1) - - ct = cb.var(C.int, 0) - limit = cb.constant(C.int, REPEAT) - with cb.loop() as loop: - with loop.condition() as setcond: - setcond( ct < limit ) - - with loop.body(): - cb.atomic_add(pval, one, 'acq_rel') - ct += one - - cb.ret() - cb.close() - return cb.function - -def gen_test_pthread(mod): - cb = CBuilder.new_function(mod, 'manager', C.int, [C.int]) - arg = cb.args[0] - - worker_func = cb.get_function_named('worker') - pthread_create = cb.get_function_named('pthread_create') - pthread_join = cb.get_function_named('pthread_join') - - - NULL = cb.constant_null(C.void_p) - cast_to_null = lambda x: x.cast(C.void_p) - - threads = cb.array(C.void_p, NUM_OF_THREAD) - - for tid in range(NUM_OF_THREAD): - pthread_create_args = [threads[tid].reference(), - NULL, - worker_func, - arg.reference()] - pthread_create(*map(cast_to_null, pthread_create_args)) - - worker_func(arg.reference()) - - for tid in range(NUM_OF_THREAD): - pthread_join_args = threads[tid], NULL - pthread_join(*map(cast_to_null, pthread_join_args)) - - - cb.ret(arg) - cb.close() - return cb.function - -class TestAtomicAdd(unittest.TestCase): - def test_atomic_add(self): - mod = Module.new(__name__) - # add pthread functions - - mod.add_function(Type.function(C.int, - [C.void_p, C.void_p, C.void_p, C.void_p]), - 'pthread_create') - - mod.add_function(Type.function(C.int, - [C.void_p, C.void_p]), - 'pthread_join') - - lf_test_worker = gen_test_worker(mod) - lf_test_pthread = gen_test_pthread(mod) - logging.debug(mod) - mod.verify() - - # optimize - fpm = FunctionPassManager.new(mod) - mpm = PassManager.new() - pmb = PassManagerBuilder.new() - pmb.vectorize = True - pmb.opt_level = 3 - pmb.populate(fpm) - pmb.populate(mpm) - - fpm.run(lf_test_worker) - fpm.run(lf_test_pthread) - mpm.run(mod) - logging.debug(mod) - mod.verify() - - # run - exe = CExecutor(mod) - exe.engine.get_pointer_to_function(mod.get_function_named('worker')) - func = exe.get_ctype_function(lf_test_pthread, 'int, int') - - inarg = 1234 - gold = inarg + (NUM_OF_THREAD + 1) * REPEAT - - for _ in range(1000): # run many many times to catch race condition - self.assertEqual(func(inarg), gold, "Unexpected race condition") - - -if __name__ == '__main__': - unittest.main() - diff --git a/tests/test_atomic_cmpxchg.py b/tests/test_atomic_cmpxchg.py deleted file mode 100644 index 27fb763..0000000 --- a/tests/test_atomic_cmpxchg.py +++ /dev/null @@ -1,122 +0,0 @@ -''' -Base on the test_pthread.py and extend to use atomic instructions -''' - -from llvm.core import * -from llvm.passes import * -from llvm.ee import * -from llvm_cbuilder import * -import llvm_cbuilder.shortnames as C -import unittest, logging - -# logging.basicConfig(level=logging.DEBUG) - -NUM_OF_THREAD = 4 -REPEAT = 10000 - -def gen_test_worker(mod): - cb = CBuilder.new_function(mod, 'worker', C.void, [C.pointer(C.int)]) - pval = cb.args[0] - one = cb.constant(pval.type.pointee, 1) - - ct = cb.var(C.int, 0) - limit = cb.constant(C.int, REPEAT) - with cb.loop() as loop: - with loop.condition() as setcond: - setcond( ct < limit ) - - with loop.body(): - oldval = pval.atomic_load('acquire') - updated = oldval + one - castmp = pval.atomic_cmpxchg(oldval, updated, 'release') - - with cb.ifelse( castmp == oldval ) as ifelse: - with ifelse.then(): - ct += one - - cb.ret() - cb.close() - return cb.function - -def gen_test_pthread(mod): - cb = CBuilder.new_function(mod, 'manager', C.int, [C.int]) - arg = cb.args[0] - - worker_func = cb.get_function_named('worker') - pthread_create = cb.get_function_named('pthread_create') - pthread_join = cb.get_function_named('pthread_join') - - - NULL = cb.constant_null(C.void_p) - cast_to_null = lambda x: x.cast(C.void_p) - - threads = cb.array(C.void_p, NUM_OF_THREAD) - - for tid in range(NUM_OF_THREAD): - pthread_create_args = [threads[tid].reference(), - NULL, - worker_func, - arg.reference()] - pthread_create(*map(cast_to_null, pthread_create_args)) - - worker_func(arg.reference()) - - for tid in range(NUM_OF_THREAD): - pthread_join_args = threads[tid], NULL - pthread_join(*map(cast_to_null, pthread_join_args)) - - - cb.ret(arg) - cb.close() - return cb.function - -class TestAtomicCmpXchg(unittest.TestCase): - def test_atomic_cmpxchg(self): - mod = Module.new(__name__) - # add pthread functions - - mod.add_function(Type.function(C.int, - [C.void_p, C.void_p, C.void_p, C.void_p]), - 'pthread_create') - - mod.add_function(Type.function(C.int, - [C.void_p, C.void_p]), - 'pthread_join') - - lf_test_worker = gen_test_worker(mod) - lf_test_pthread = gen_test_pthread(mod) - logging.debug(mod) - mod.verify() - - # optimize - fpm = FunctionPassManager.new(mod) - mpm = PassManager.new() - pmb = PassManagerBuilder.new() - pmb.vectorize = True - pmb.opt_level = 3 - pmb.populate(fpm) - pmb.populate(mpm) - - fpm.run(lf_test_worker) - fpm.run(lf_test_pthread) - mpm.run(mod) - logging.debug(mod) - mod.verify() - - # run - exe = CExecutor(mod) - exe.engine.get_pointer_to_function(mod.get_function_named('worker')) - func = exe.get_ctype_function(lf_test_pthread, 'int, int') - - inarg = 1234 - gold = inarg + (NUM_OF_THREAD + 1) * REPEAT - - for _ in range(1000): # run many many times to catch race condition - res = func(inarg) - self.assertEqual(res, gold, - "Unexpected race condition: res = %d" % res) - - -if __name__ == '__main__': - unittest.main() - diff --git a/tests/test_cstr_collide.py b/tests/test_cstr_collide.py deleted file mode 100644 index 4a427c6..0000000 --- a/tests/test_cstr_collide.py +++ /dev/null @@ -1,17 +0,0 @@ -from llvm.core import * -from llvm_cbuilder import * -from llvm_cbuilder import shortnames as C - -import unittest - -class TestCstrCollide(unittest.TestCase): - def test_same_string(self): - mod = Module.new(__name__) - cb = CBuilder.new_function(mod, 'test_cstr_collide', C.void, []) - - a = cb.constant_string("hello") - b = cb.constant_string("hello") - self.assertEqual(a.value, b.value) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_isprime.py b/tests/test_isprime.py deleted file mode 100644 index bcc0b9c..0000000 --- a/tests/test_isprime.py +++ /dev/null @@ -1,125 +0,0 @@ -from llvm.core import * -from llvm.passes import * -from llvm.ee import * -from llvm_cbuilder import * -import llvm_cbuilder.shortnames as C -import unittest, logging - -def is_prime(x): - if x <= 2: - return True - if (x % 2) == 0: - return False - for y in range(2, int(1 + x**0.5)): - if (x % y) == 0: - return False - return True - -def gen_is_prime(mod): - functype = Type.function(C.int, [C.int]) - func = mod.add_function(functype, 'isprime') - - cb = CBuilder(func) - - arg = cb.args[0] - - two = cb.constant(C.int, 2) - true = one = cb.constant(C.int, 1) - false = zero = cb.constant(C.int, 0) - - with cb.ifelse( arg <= two ) as ifelse: - with ifelse.then(): - cb.ret(true) - - with cb.ifelse( (arg % two) == zero ) as ifelse: - with ifelse.then(): - cb.ret(false) - - idx = cb.var(C.int, 3, name='idx') - with cb.loop() as loop: - with loop.condition() as setcond: - setcond( idx < arg ) - - with loop.body(): - with cb.ifelse( (arg % idx) == zero ) as ifelse: - with ifelse.then(): - cb.ret(false) - # increment - idx += two - - cb.ret(true) - cb.close() - return func - - -def gen_is_prime_fast(mod): - functype = Type.function(C.int, [C.int]) - func = mod.add_function(functype, 'isprime_fast') - - cb = CBuilder(func) - - arg = cb.args[0] - - two = cb.constant(C.int, 2) - true = one = cb.constant(C.int, 1) - false = zero = cb.constant(C.int, 0) - - with cb.ifelse( arg <= two ) as ifelse: - with ifelse.then(): - cb.ret(true) - - with cb.ifelse( (arg % two) == zero ) as ifelse: - with ifelse.then(): - cb.ret(false) - - idx = cb.var(C.int, 3, name='idx') - - sqrt = cb.get_intrinsic(INTR_SQRT, [C.float]) - - looplimit = one + sqrt(arg.cast(C.float)).cast(C.int) - - - with cb.loop() as loop: - with loop.condition() as setcond: - setcond( idx < looplimit ) - - with loop.body(): - with cb.ifelse( (arg % idx) == zero ) as ifelse: - with ifelse.then(): - cb.ret(false) - # increment - idx += two - - - cb.ret(true) - cb.close() - return func - -class TestIsPrime(unittest.TestCase): - def test_isprime(self): - mod = Module.new(__name__) - lf_isprime = gen_is_prime(mod) - logging.debug(mod) - mod.verify() - - exe = CExecutor(mod) - func = exe.get_ctype_function(lf_isprime, 'bool, int') - for x in range(2, 1000): - msg = "Failed at x = %d" % x - self.assertEqual(func(x), is_prime(x), msg) - - def test_isprime_fast(self): - mod = Module.new(__name__) - lf_isprime = gen_is_prime_fast(mod) - logging.debug(mod) - mod.verify() - - exe = CExecutor(mod) - func = exe.get_ctype_function(lf_isprime, 'bool, int') - for x in range(2, 1000): - msg = "Failed at x = %d" % x - self.assertEqual(func(x), is_prime(x), msg) - -if __name__ == '__main__': - unittest.main() - diff --git a/tests/test_loopcontrol.py b/tests/test_loopcontrol.py deleted file mode 100644 index 7e252bf..0000000 --- a/tests/test_loopcontrol.py +++ /dev/null @@ -1,135 +0,0 @@ -from llvm.core import * -from llvm.passes import * -from llvm.ee import * -from llvm_cbuilder import * -import llvm_cbuilder.shortnames as C -import unittest, logging - -def loopbreak(d): - z = 0 - for x in range(100): - for y in range(100): - z += x + y - if z > 50: - break - z -= d - return z - -def gen_loopbreak(mod): - functype = Type.function(C.int, [C.int]) - func = mod.add_function(functype, 'loopbreak') - - cb = CBuilder(func) - - d = cb.args[0] - x = cb.var(C.int) - y = cb.var(C.int) - z = cb.var(C.int) - - one = cb.constant(C.int, 1) - zero = cb.constant(C.int, 0) - limit = cb.constant(C.int, 100) - fifty = cb.constant(C.int, 50) - - z.assign(zero) - x.assign(zero) - with cb.loop() as outer: - with outer.condition() as setcond: - setcond( x < limit ) - - with outer.body(): - y.assign(zero) - with cb.loop() as inner: - with inner.condition() as setcond: - setcond( y < limit ) - - with inner.body(): - z += x + y - with cb.ifelse( z > fifty ) as ifelse: - with ifelse.then(): - inner.break_loop() - y += one - z -= d - x += one - - cb.ret(z) - cb.close() - return func - -def loopcontinue(d): - z = 0 - for x in range(100): - for y in range(100): - z += x + y - if z > 50: - continue - z += d - return z - -def gen_loopcontinue(mod): - functype = Type.function(C.int, [C.int]) - func = mod.add_function(functype, 'loopcontinue') - - cb = CBuilder(func) - - d = cb.args[0] - x = cb.var(C.int) - y = cb.var(C.int) - z = cb.var(C.int) - - one = cb.constant(C.int, 1) - zero = cb.constant(C.int, 0) - limit = cb.constant(C.int, 100) - fifty = cb.constant(C.int, 50) - - z.assign(zero) - x.assign(zero) - with cb.loop() as outer: - with outer.condition() as setcond: - setcond( x < limit ) - - with outer.body(): - y.assign(zero) - with cb.loop() as inner: - with inner.condition() as setcond: - setcond( y < limit ) - - with inner.body(): - z += x + y - y += one - with cb.ifelse( z > fifty ) as ifelse: - with ifelse.then(): - inner.continue_loop() - z += d - x += one - - cb.ret(z) - cb.close() - return func - -class TestLoopControl(unittest.TestCase): - def test_loopbreak(self): - mod = Module.new(__name__) - lfunc = gen_loopbreak(mod) - logging.debug(mod) - mod.verify() - - exe = CExecutor(mod) - func = exe.get_ctype_function(lfunc, 'int, int') - for x in range(100): - self.assertEqual(func(x), loopbreak(x)) - - def test_loopcontinue(self): - mod = Module.new(__name__) - lfunc = gen_loopcontinue(mod) - logging.debug(mod) - mod.verify() - - exe = CExecutor(mod) - func = exe.get_ctype_function(lfunc, 'int, int') - for x in range(100): - self.assertEqual(func(x), loopcontinue(x)) - -if __name__ == '__main__': - unittest.main() - diff --git a/tests/test_nestedloops.py b/tests/test_nestedloops.py deleted file mode 100644 index 0876bb2..0000000 --- a/tests/test_nestedloops.py +++ /dev/null @@ -1,128 +0,0 @@ -from llvm.core import * -from llvm.passes import * -from llvm.ee import * -from llvm_cbuilder import * -import llvm_cbuilder.shortnames as C -import unittest, logging - -def nestedloop1(d): - z = 0 - for x in range(100): - for y in range(100): - z += x * d + int(y / d) - return z - -def gen_nestedloop1(mod): - functype = Type.function(C.int, [C.int]) - func = mod.add_function(functype, 'nestedloop1') - - cb = CBuilder(func) - - d = cb.args[0] - x = cb.var(C.int) - y = cb.var(C.int) - z = cb.var(C.int) - - one = cb.constant(C.int, 1) - zero = cb.constant(C.int, 0) - limit = cb.constant(C.int, 100) - - z.assign(zero) - x.assign(zero) - with cb.loop() as outer: - with outer.condition() as setcond: - setcond( x < limit ) - - with outer.body(): - y.assign(zero) - with cb.loop() as inner: - with inner.condition() as setcond: - setcond( y < limit ) - - with inner.body(): - z += x * d + y / d - y += one - x += one - - cb.ret(z) - cb.close() - return func - - -def nestedloop2(d): - z = 0 - for x in range(1, 100): - for y in range(1, 100): - if x > y: - z += int(x / y) * d - else: - z += int(y / x) * d - return z - -def gen_nestedloop2(mod): - functype = Type.function(C.int, [C.int]) - func = mod.add_function(functype, 'nestedloop2') - - cb = CBuilder(func) - - d = cb.args[0] - x = cb.var(C.int) - y = cb.var(C.int) - z = cb.var(C.int) - - one = cb.constant(C.int, 1) - zero = cb.constant(C.int, 0) - limit = cb.constant(C.int, 100) - - z.assign(zero) - x.assign(one) - with cb.loop() as outer: - with outer.condition() as setcond: - setcond( x < limit ) - - with outer.body(): - y.assign(one) - with cb.loop() as inner: - with inner.condition() as setcond: - setcond( y < limit ) - - with inner.body(): - with cb.ifelse(x > y) as ifelse: - with ifelse.then(): - z += x / y * d - with ifelse.otherwise(): - z += y / x * d - y += one - x += one - - cb.ret(z) - cb.close() - return func - - -class TestNestedLoop(unittest.TestCase): - def test_nestedloop1(self): - mod = Module.new(__name__) - lfunc = gen_nestedloop1(mod) - logging.debug(mod) - mod.verify() - - exe = CExecutor(mod) - func = exe.get_ctype_function(lfunc, 'int, int') - for x in range(1, 100): - self.assertEqual(func(x), int(nestedloop1(x))) - - def test_nestedloop2(self): - mod = Module.new(__name__) - lfunc = gen_nestedloop2(mod) - logging.debug(mod) - mod.verify() - - exe = CExecutor(mod) - func = exe.get_ctype_function(lfunc, 'int, int') - for x in range(1, 100): - self.assertEqual(func(x), int(nestedloop2(x))) - -if __name__ == '__main__': - unittest.main() - diff --git a/tests/test_print.py b/tests/test_print.py deleted file mode 100644 index d971e31..0000000 --- a/tests/test_print.py +++ /dev/null @@ -1,60 +0,0 @@ -from llvm.core import * -from llvm.passes import * -from llvm.ee import * -from llvm_cbuilder import * -import llvm_cbuilder.shortnames as C -import sys, unittest, logging -from subprocess import Popen, PIPE - -def gen_debugprint(mod): - functype = Type.function(C.void, []) - func = mod.add_function(functype, 'debugprint') - - cb = CBuilder(func) - fmt = cb.constant_string("Show %d %.3f %.3e\n") - - an_int = cb.constant(C.int, 123) - a_float = cb.constant(C.double, 1.234) - a_double = cb.constant(C.double, 1e-31) - cb.printf(fmt, an_int, a_float, a_double) - - cb.debug('an_int =', an_int, 'a_float =', a_float, 'a_double =', a_double) - - cb.ret() - cb.close() - return func - -def main_debugprint(): - # generate code - mod = Module.new(__name__) - lfunc = gen_debugprint(mod) - logging.debug(mod) - mod.verify() - # run - exe = CExecutor(mod) - func = exe.get_ctype_function(lfunc, 'void') - func() - -class TestPrint(unittest.TestCase): - def test_debugprint(self): - p = Popen(["python", "test_print.py", "-child"], stdout=PIPE) - p.wait() - - lines = p.stdout.read().decode().splitlines(False) - - expect = [ - 'Show 123 1.234 1.000e-31', - 'an_int = 123 a_float = 1.234000e+00 a_double = 1.000000e-31', - ] - self.assertEqual(expect, lines) - - p.stdout.close() - -if __name__ == '__main__': - try: - if sys.argv[1] == '-child': - main_debugprint() - except IndexError: - unittest.main() - - diff --git a/tests/test_pthread.py b/tests/test_pthread.py deleted file mode 100644 index d1d3a72..0000000 --- a/tests/test_pthread.py +++ /dev/null @@ -1,90 +0,0 @@ -from llvm.core import * -from llvm.passes import * -from llvm.ee import * -from llvm_cbuilder import * -import llvm_cbuilder.shortnames as C -import unittest, logging - -# logging.basicConfig(level=logging.DEBUG) - -NUM_OF_THREAD = 4 - -def gen_test_worker(mod): - cb = CBuilder.new_function(mod, 'worker', C.void, [C.pointer(C.int)]) - pval = cb.args[0] - val = pval.load() - one = cb.constant(val.type, 1) - pval.store(val + one) - cb.ret() - cb.close() - -def gen_test_pthread(mod): - cb = CBuilder.new_function(mod, 'manager', C.int, [C.int]) - arg = cb.args[0] - - worker_func = cb.get_function_named('worker') - pthread_create = cb.get_function_named('pthread_create') - pthread_join = cb.get_function_named('pthread_join') - - - NULL = cb.constant_null(C.void_p) - cast_to_null = lambda x: x.cast(C.void_p) - - threads = cb.array(C.void_p, NUM_OF_THREAD) - - for tid in range(NUM_OF_THREAD): - pthread_create_args = [threads[tid].reference(), - NULL, - worker_func, - arg.reference()] - pthread_create(*map(cast_to_null, pthread_create_args)) - - worker_func(arg.reference()) - - for tid in range(NUM_OF_THREAD): - pthread_join_args = threads[tid], NULL - pthread_join(*map(cast_to_null, pthread_join_args)) - - cb.ret(arg) - cb.close() - return cb.function - -class TestPThread(unittest.TestCase): - def test_pthread(self): - mod = Module.new(__name__) - # add pthread functions - - mod.add_function(Type.function(C.int, - [C.void_p, C.void_p, C.void_p, C.void_p]), - 'pthread_create') - - mod.add_function(Type.function(C.int, - [C.void_p, C.void_p]), - 'pthread_join') - - gen_test_worker(mod) - lf_test_pthread = gen_test_pthread(mod) - logging.debug(mod) - mod.verify() - - exe = CExecutor(mod) - exe.engine.get_pointer_to_function(mod.get_function_named('worker')) - func = exe.get_ctype_function(lf_test_pthread, 'int, int') - - inarg = 1234 - gold = inarg + NUM_OF_THREAD + 1 - self.assertLessEqual(func(inarg), gold) - # Cannot determine the exact return value due to untamed race condition - - count_race = 0 - for _ in range(2**12): - if func(inarg) != gold: - count_race += 1 - - if count_race > 0: - logging.info("Race condition occured %d times.", count_race) - logging.info("Race condition is expected.") - -if __name__ == '__main__': - unittest.main() - diff --git a/tests/test_struct.py b/tests/test_struct.py deleted file mode 100644 index 35048e6..0000000 --- a/tests/test_struct.py +++ /dev/null @@ -1,52 +0,0 @@ -from llvm.core import * -from llvm_cbuilder import * -import llvm_cbuilder.shortnames as C -import unittest, ctypes - -class Vector2D(CStruct): - _fields_ = [ - ('x', C.float), - ('y', C.float), - ] - -class Vector2DCtype(ctypes.Structure): - _fields_ = [ - ('x', ctypes.c_float), - ('y', ctypes.c_float), - ] - -def gen_vector2d_dist(mod): - functype = Type.function(C.float, [C.pointer(Vector2D.llvm_type())]) - func = mod.add_function(functype, 'vector2d_dist') - - cb = CBuilder(func) - vec = cb.var(Vector2D, cb.args[0].load()) - dist = vec.x * vec.x + vec.y * vec.y - - cb.ret(dist) - cb.close() - return func - - -class TestStruct(unittest.TestCase): - def test_vector2d_dist(self): - # prepare module - mod = Module.new('mod') - lfunc = gen_vector2d_dist(mod) - mod.verify() - # run - exe = CExecutor(mod) - func = exe.get_ctype_function(lfunc, ctypes.c_float, ctypes.POINTER(Vector2DCtype)) - - from random import random - pydist = lambda x, y: x * x + y * y - for _ in range(100): - x, y = random(), random() - vec = Vector2DCtype(x=x, y=y) - ans = func(ctypes.pointer(vec)) - gold = pydist(x, y) - - self.assertLess(abs(ans-gold)/gold, 1e-6) - -if __name__ == '__main__': - unittest.main() From d55bb95d4d98277a2abcf5fbf07c622ea5472b09 Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Sun, 19 Aug 2012 21:27:28 -0700 Subject: [PATCH 32/57] Add translator to convert python code to llvm cbuilder construct --- translator.py | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 translator.py diff --git a/translator.py b/translator.py new file mode 100644 index 0000000..ac7338f --- /dev/null +++ b/translator.py @@ -0,0 +1,91 @@ +# A handy translator that converts control flow into the appropriate +# llvm_cbuilder constructs +import inspect, functools, ast +import pprint +def translate(func): + # TODO use meta package + wrapper = functools.wraps(func) + source = inspect.getsource(func) + lines = list(source.splitlines()) + + # skip first two lines + first_line_len = len(lines[2]) + indent = first_line_len - len(lines[2].lstrip()) + source = '\n'.join(line[indent:] for line in lines[2:]) + + caller_frame = inspect.currentframe().f_back + + tree = ast.parse(source) + tree = ExpandControlFlow().visit(tree) + tree = ast.fix_missing_locations(tree) + + # prepare locals for execution + local_dict = locals() + local_dict.update(caller_frame.f_locals) + local_dict.update(caller_frame.f_globals) + + + return eval(compile(tree, '', 'exec')) + +_if_else_template = ''' +with self.ifelse(__CONDITION__) as _ifelse_: + with _ifelse_.then(): + __THEN__ + with _ifelse_.otherwise(): + __OTHERWISE__ +''' + +_return_template = 'self.ret(__RETURN__)' + +def load_template(string): + tree = ast.parse(string) # return a Module + return tree.body[0] # get the first item of body + +class ExpandControlFlow(ast.NodeTransformer): + def visit_If(self, node): + condition = node.test + mapping = { + '__CONDITION__' : node.test, + '__THEN__' : node.body, + '__OTHERWISE__' : node.orelse, + } + + ifelse = load_template(_if_else_template) + ifelse = NameReplacer(mapping).visit(ifelse) + newnode = ast.copy_location(ifelse, node) + return self.generic_visit(newnode) + + def visit_Return(self, node): + mapping = {'__RETURN__' : node.value} + ret = load_template(_return_template) + repl = NameReplacer(mapping).visit(ret) + return ast.copy_location(repl, node) + +class NameReplacer(ast.NodeTransformer): + def __init__(self, mapping): + self.mapping = mapping + + def visit_With(self, node): + if (len(node.body)==1 + and isinstance(node.body[0], ast.Expr) + and isinstance(node.body[0].value, ast.Name)): + try: + repl = self.mapping.pop(node.body[0].value.id) + except KeyError: + pass + else: + old = node.body[0] + node.body = repl + return self.generic_visit(node) + + def visit_Name(self, node): + if type(node.ctx) is ast.Load: + try: + repl = self.mapping.pop(node.id) + except KeyError: + pass + else: + if repl is not None and not isinstance(repl, list): + return ast.copy_location(repl, node) + return node + From 562e3c1e463bb52146bcf8010b397f74e21a2376 Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Sun, 19 Aug 2012 21:35:45 -0700 Subject: [PATCH 33/57] Fix: if-else branch should terminate automatically. Insert unreachable to if.end block if necessary. --- builder.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/builder.py b/builder.py index fc4baa3..7a6ed32 100644 --- a/builder.py +++ b/builder.py @@ -97,11 +97,15 @@ class _IfElse(object): self._to_close.append(self.parent.builder.basic_block) bbend = self.parent.function.append_basic_block('if.end') builder = self.parent.builder + closed_count = 0 for bb in self._to_close: if not _is_block_terminated(bb): with _change_block_temporarily(builder, bb): builder.branch(bbend) + closed_count += 1 builder.position_at_end(bbend) + if not closed_count: + self.parent.unreachable() class _Loop(object): '''while...do loop. From 5319fb8c984f67613b70d58b490818d829779e6a Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Tue, 21 Aug 2012 12:25:48 -0500 Subject: [PATCH 34/57] Add initial vector support --- builder.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++----- executor.py | 1 - shortnames.py | 5 ++++ 3 files changed, 74 insertions(+), 8 deletions(-) diff --git a/builder.py b/builder.py index 7a6ed32..57ea551 100644 --- a/builder.py +++ b/builder.py @@ -26,6 +26,14 @@ def _is_real(ty): lc.Type.ppc_fp128() ] return any(ty == x for x in tys) +def _is_vector(ty, of=None): + if isinstance(ty, lc.VectorType): + if of is not None: + return of(ty.element) + return True + else: + return False + def _is_pointer(ty): return isinstance(ty, lc.PointerType) @@ -42,6 +50,11 @@ def _is_cstruct(ty): def _list_values(iterable): return [i.value for i in iterable] +def _auto_coerce_index(cbldr, idx): + if not isinstance(idx, CValue): + idx = cbldr.constant(types.int, idx) + return idx + @contextlib.contextmanager def _change_block_temporarily(builder, bb): origbb = builder.basic_block @@ -892,11 +905,11 @@ class CValue(object): @property def is_int(self): - return _is_int(self.type) + return _is_int(self.type) or _is_vector(self.type, _is_int) @property def is_real(self): - return _is_real(self.type) + return _is_real(self.type) or _is_vector(self.type, _is_real) def cast(self, ty, unsigned=False): '''cast to another type @@ -984,6 +997,10 @@ class CValue(object): return self._cmp_op('ge')(rhs) + @property + def is_vector(self): + return _is_vector(self.type) + @property def is_pointer(self): return _is_pointer(self.type) @@ -992,17 +1009,41 @@ class CValue(object): if not self.is_pointer: raise TypeError("Must be a pointer; got %s" % self.type) + def _ensure_is_vector(self): + if not self.is_vector: + raise TypeError("Must be a vector; got %s" % self.type) + def __getitem__(self, idx): '''implement access indexing Uses GEP. ''' - self._ensure_is_pointer() - if not isinstance(idx, CValue): - idx = self.parent.constant(types.int, idx) bldr = self.parent.builder - ptr = bldr.gep(self.value, [idx.value]) - return CVar(self.parent, ptr) + if self.is_pointer: + if type(idx) is slice: + # just handle case by case + # Case #1: A[idx:] get poointer offset by idx + if not idx.step and not idx.stop: + idx = _auto_coerce_index(self.parent, idx.start) + ptr = bldr.gep(self.value, [idx.value]) + return CArray(self.parent, ptr) + else: # return an variable at idx + idx = _auto_coerce_index(self.parent, idx) + ptr = bldr.gep(self.value, [idx.value]) + return CVar(self.parent, ptr) + elif self.is_vector: + idx = _auto_coerce_index(self.parent, idx) + val = bldr.extract_element(self.value, idx.value) + return CTemp(self.parent, val) + else: + raise TypeError("Must be a pointer or vector; got %s" % self.type) + + def __setitem__(self, idx, val): + self._ensure_is_vector() + idx = _auto_coerce_index(self.parent, idx) + bldr = self.parent.builder + vec = bldr.insert_element(self.value, val.value, idx.value) + bldr.store(vec, self.ptr) def load(self, volatile=False): '''memory load for pointer types @@ -1181,6 +1222,7 @@ class CVar(CValue): else: return cstruct_class(self.parent, self.ptr) + class CArray(CValue): '''wraps a array @@ -1201,6 +1243,26 @@ class CArray(CValue): def type(self): return self.base_ptr.type + def vector_load(self, count): + parent = self.parent + values = [self[i] for i in range(count)] + vec = parent.constant_null(types.vector(self.type.pointee, count)).value + for i, v in enumerate(values): + i = parent.constant(types.int, i) + vec = parent.builder.insert_element(vec, v.value, i.value) + return CTemp(parent, vec) + + def vector_store(self, vec): + if vec.type.element != self.type.pointee: + raise TypeError("Type mismatch; expect %s but got %s" % \ + (vec.type.element, self.type.pointee)) + parent = self.parent + for i in range(vec.type.count): + i = parent.constant(types.int, i) + val = parent.builder.extract_element(vec.value, i.value) + self[i].assign(CTemp(parent, val)) + return self + class CStruct(CValue): '''Wraps a structure diff --git a/executor.py b/executor.py index acfd13d..fedaeae 100644 --- a/executor.py +++ b/executor.py @@ -61,7 +61,6 @@ class CExecutor(object): else: retty = typeinfo[0] argtys = typeinfo[1:] - prototype = ct.CFUNCTYPE(retty, *argtys) fnptr = self.engine.get_pointer_to_function(fn) return prototype(fnptr) diff --git a/shortnames.py b/shortnames.py index beaaad3..012e53d 100644 --- a/shortnames.py +++ b/shortnames.py @@ -28,3 +28,8 @@ pointer_size = _determine_pointer_size() intp = {32: int32, 64: int64}[pointer_size] + +# vector +def vector(ty, ct): + return Type.vector(ty, 4) + From 5513a34e80ff6096e4879bb92a85ca613da9466a Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Tue, 21 Aug 2012 14:17:14 -0500 Subject: [PATCH 35/57] Add some doc and cleanup --- translator.py | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/translator.py b/translator.py index ac7338f..a857fc5 100644 --- a/translator.py +++ b/translator.py @@ -8,7 +8,12 @@ def translate(func): source = inspect.getsource(func) lines = list(source.splitlines()) - # skip first two lines + # skip first two lines + # assume they are + # @translate + # def blahblah(): + assert lines[0].lstrip().startswith('@') + assert lines[1].lstrip().startswith('def') first_line_len = len(lines[2]) indent = first_line_len - len(lines[2].lstrip()) source = '\n'.join(line[indent:] for line in lines[2:]) @@ -38,10 +43,18 @@ with self.ifelse(__CONDITION__) as _ifelse_: _return_template = 'self.ret(__RETURN__)' def load_template(string): + ''' + Since ast.parse() returns a ast.Module node, + it is more useful to trim the Module and get to the first item of body + ''' tree = ast.parse(string) # return a Module return tree.body[0] # get the first item of body class ExpandControlFlow(ast.NodeTransformer): + ''' + Expand control flow contructs. + These are the most tedious thing to do in llvm_cbuilder. + ''' def visit_If(self, node): condition = node.test mapping = { @@ -51,22 +64,29 @@ class ExpandControlFlow(ast.NodeTransformer): } ifelse = load_template(_if_else_template) - ifelse = NameReplacer(mapping).visit(ifelse) + ifelse = MacroExpander(mapping).visit(ifelse) newnode = ast.copy_location(ifelse, node) return self.generic_visit(newnode) def visit_Return(self, node): mapping = {'__RETURN__' : node.value} ret = load_template(_return_template) - repl = NameReplacer(mapping).visit(ret) + repl = MacroExpander(mapping).visit(ret) return ast.copy_location(repl, node) -class NameReplacer(ast.NodeTransformer): +class MacroExpander(ast.NodeTransformer): def __init__(self, mapping): self.mapping = mapping def visit_With(self, node): - if (len(node.body)==1 + ''' + Expand X in the following: + with blah: + X + Nothing should go before or after X. + X must be a list of nodes. + ''' + if (len(node.body)==1 # the body of and isinstance(node.body[0], ast.Expr) and isinstance(node.body[0].value, ast.Name)): try: @@ -76,9 +96,12 @@ class NameReplacer(ast.NodeTransformer): else: old = node.body[0] node.body = repl - return self.generic_visit(node) + return self.generic_visit(node) # recursively apply expand all macros def visit_Name(self, node): + ''' + Expand all Name node to simple value + ''' if type(node.ctx) is ast.Load: try: repl = self.mapping.pop(node.id) From 2218f1d49e9f54c8a57a8717c07d4ee4eb7bb0fb Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Tue, 21 Aug 2012 17:26:12 -0500 Subject: [PATCH 36/57] Improve vector load store. Changes make llvm emit parallel ld/st instead of scalar ld/st. --- builder.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/builder.py b/builder.py index 57ea551..bfb2791 100644 --- a/builder.py +++ b/builder.py @@ -1245,11 +1245,11 @@ class CArray(CValue): def vector_load(self, count): parent = self.parent + builder = parent.builder values = [self[i] for i in range(count)] - vec = parent.constant_null(types.vector(self.type.pointee, count)).value - for i, v in enumerate(values): - i = parent.constant(types.int, i) - vec = parent.builder.insert_element(vec, v.value, i.value) + + vecty = types.vector(self.type.pointee, count) + vec = builder.load(builder.bitcast(self.base_ptr, types.pointer(vecty))) return CTemp(parent, vec) def vector_store(self, vec): @@ -1257,10 +1257,9 @@ class CArray(CValue): raise TypeError("Type mismatch; expect %s but got %s" % \ (vec.type.element, self.type.pointee)) parent = self.parent - for i in range(vec.type.count): - i = parent.constant(types.int, i) - val = parent.builder.extract_element(vec.value, i.value) - self[i].assign(CTemp(parent, val)) + builder = parent.builder + builder.store(vec.value, builder.bitcast(self.base_ptr, + types.pointer(vec.type))) return self class CStruct(CValue): From 4d773ed5ed0dc7a98e06bba33476b6a2983130ed Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Tue, 21 Aug 2012 18:32:44 -0500 Subject: [PATCH 37/57] Add alignment control to vector_load/store. E.g: align=1 for universal alignment. default to aligned. --- builder.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/builder.py b/builder.py index bfb2791..e1489a5 100644 --- a/builder.py +++ b/builder.py @@ -1243,23 +1243,24 @@ class CArray(CValue): def type(self): return self.base_ptr.type - def vector_load(self, count): + def vector_load(self, count, align=0): parent = self.parent builder = parent.builder values = [self[i] for i in range(count)] vecty = types.vector(self.type.pointee, count) - vec = builder.load(builder.bitcast(self.base_ptr, types.pointer(vecty))) + vec = builder.load(builder.bitcast(self.base_ptr, types.pointer(vecty)), + align=align) return CTemp(parent, vec) - def vector_store(self, vec): + def vector_store(self, vec, align=0): if vec.type.element != self.type.pointee: raise TypeError("Type mismatch; expect %s but got %s" % \ (vec.type.element, self.type.pointee)) parent = self.parent builder = parent.builder - builder.store(vec.value, builder.bitcast(self.base_ptr, - types.pointer(vec.type))) + vecptr = builder.bitcast(self.base_ptr, types.pointer(vec.type)) + builder.store(vec.value, vecptr, align=align) return self class CStruct(CValue): From eeda441dac468f6d1fde0ceb62bf28c9ca633773 Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Wed, 22 Aug 2012 13:22:37 -0500 Subject: [PATCH 38/57] Added stream vectorize. --- builder.py | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/builder.py b/builder.py index e1489a5..b30c363 100644 --- a/builder.py +++ b/builder.py @@ -5,6 +5,7 @@ import contextlib import llvm.core as lc import llvm.ee as le +import llvm.passes as lp from llvm import LLVMException from . import shortnames as types @@ -245,6 +246,9 @@ class CBuilder(object): for i in args: if isinstance(i, str): itemsfmt.append(i.replace('%', '%%')) + elif isinstance(i.type, lc.PointerType): + itemsfmt.append("%p") + items.append(i) else: tyname = str(i.type) if tyname == 'float': @@ -258,6 +262,26 @@ class CBuilder(object): fmt = ' '.join(itemsfmt) + '\n' return self.printf(self.constant_string(fmt), *items) + def sizeof(self, ty): + bldr = self.builder + ptrty = types.pointer(ty) + first = lc.Constant.null(ptrty) + second = bldr.gep(first, [lc.Constant.int(types.intp, 1)]) + + firstint = bldr.ptrtoint(first, types.intp) + secondint = bldr.ptrtoint(second, types.intp) + diff = bldr.sub(secondint, firstint) + return CTemp(self, diff) + + def min(self, x, y): + z = self.var(x.type) + with self.ifelse( x < y ) as ifelse: + with ifelse.then(): + z.assign(x) + with ifelse.otherwise(): + z.assign(y) + return z + def var(self, ty, value=None, name=''): '''allocate variable on the stack @@ -771,6 +795,14 @@ class CDefinition(CBuilder): cbuilder.__init__(func) cbuilder.body(*cbuilder.args) cbuilder.close() + + # optimize + fpm = lp.FunctionPassManager.new(module) + pmb = lp.PassManagerBuilder.new() + pmb.opt_level = 3 + pmb.vectorize = True + pmb.populate(fpm) + fpm.run(func) return func def body(self): @@ -1039,11 +1071,15 @@ class CValue(object): raise TypeError("Must be a pointer or vector; got %s" % self.type) def __setitem__(self, idx, val): - self._ensure_is_vector() idx = _auto_coerce_index(self.parent, idx) bldr = self.parent.builder - vec = bldr.insert_element(self.value, val.value, idx.value) - bldr.store(vec, self.ptr) + if self.is_vector: + vec = bldr.insert_element(self.value, val.value, idx.value) + bldr.store(vec, self.ptr) + elif self.is_pointer: + self[idx].assign(val) + else: + raise TypeError("Must be a pointer or vector; got %s" % self.type) def load(self, volatile=False): '''memory load for pointer types From d3909606576236bfb227bf5341989277df9094ac Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Thu, 23 Aug 2012 11:43:24 -0500 Subject: [PATCH 39/57] Add llvm_cbuilder translate features: * handle while loop * handle for loop * handle immediate values Todo: handle "break" and "continue" --- translator.py | 116 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 103 insertions(+), 13 deletions(-) diff --git a/translator.py b/translator.py index a857fc5..c0e9d5f 100644 --- a/translator.py +++ b/translator.py @@ -1,7 +1,10 @@ # A handy translator that converts control flow into the appropriate # llvm_cbuilder constructs import inspect, functools, ast -import pprint +import logging + +logger = logging.getLogger(__name__) + def translate(func): # TODO use meta package wrapper = functools.wraps(func) @@ -29,8 +32,15 @@ def translate(func): local_dict.update(caller_frame.f_locals) local_dict.update(caller_frame.f_globals) + try: + return eval(compile(tree, '', 'exec')) + except: + logger.debug(ast.dump(tree)) + from ArminRonacher import codegen # uses Armin Ronacher's codegen to debug + # http://dev.pocoo.org/hg/sandbox/file/852a1248c8eb/ast/codegen.py + logger.debug(codegen.to_source(tree)) + raise - return eval(compile(tree, '', 'exec')) _if_else_template = ''' with self.ifelse(__CONDITION__) as _ifelse_: @@ -40,14 +50,32 @@ with self.ifelse(__CONDITION__) as _ifelse_: __OTHERWISE__ ''' +_while_template = ''' +with self.loop() as _loop_: + with _loop_.condition() as _setcond_: + _setcond_(__CONDITION__) + with _loop_.body(): + __BODY__ +''' + +_for_range_template = ''' +with self.for_range(*__ARGS__) as (_loop_, __ITER__): + __BODY__ +''' + _return_template = 'self.ret(__RETURN__)' +_const_int_template = 'self.constant(C.int, __VALUE__)' +_const_long_template = 'self.constant(C.long, __VALUE__)' +_const_float_template = 'self.constant(C.double, __VALUE__)' + def load_template(string): ''' Since ast.parse() returns a ast.Module node, it is more useful to trim the Module and get to the first item of body ''' tree = ast.parse(string) # return a Module + assert isinstance(tree, ast.Module) return tree.body[0] # get the first item of body class ExpandControlFlow(ast.NodeTransformer): @@ -55,8 +83,23 @@ class ExpandControlFlow(ast.NodeTransformer): Expand control flow contructs. These are the most tedious thing to do in llvm_cbuilder. ''' + + ## Use breadcumb to track parent nodes + # def __init__(self): + # self.breadcumb = [] + # + # def visit(self, node): + # self.breadcumb.append(node) + # try: + # return super(ExpandControlFlow, self).visit(node) + # finally: + # self.breadcumb.pop() + # + # @property + # def parent(self): + # return self.breadcumb[-2] + def visit_If(self, node): - condition = node.test mapping = { '__CONDITION__' : node.test, '__THEN__' : node.body, @@ -65,8 +108,36 @@ class ExpandControlFlow(ast.NodeTransformer): ifelse = load_template(_if_else_template) ifelse = MacroExpander(mapping).visit(ifelse) - newnode = ast.copy_location(ifelse, node) - return self.generic_visit(newnode) + newnode = self.generic_visit(ifelse) + return ast.copy_location(newnode, node) + + def visit_While(self, node): + mapping = { + '__CONDITION__' : node.test, + '__BODY__' : node.body, + } + whileloop = load_template(_while_template) + whileloop = MacroExpander(mapping).visit(whileloop) + newnode = self.generic_visit(whileloop) + return ast.copy_location(newnode, node) + + def visit_For(self, node): + try: + if node.iter.func.id not in ['range', 'xrange']: + return node + except AttributeError: + return node + + mapping = { + '__ITER__' : node.target, + '__BODY__' : node.body, + '__ARGS__' : ast.Tuple(elts=node.iter.args, ctx=ast.Load()), + } + + forloop = load_template(_for_range_template) + forloop = MacroExpander(mapping).visit(forloop) + newnode = self.generic_visit(forloop) + return ast.copy_location(newnode, node) def visit_Return(self, node): mapping = {'__RETURN__' : node.value} @@ -74,6 +145,24 @@ class ExpandControlFlow(ast.NodeTransformer): repl = MacroExpander(mapping).visit(ret) return ast.copy_location(repl, node) + def visit_Num(self, node): + '''convert immediate values + ''' + typemap = { + int : _const_int_template, + long : _const_long_template, # TODO: disable long for py3 + float : _const_float_template, + } + + template = load_template(typemap[type(node.n)]) + + mapping = { + '__VALUE__' : node, + } + constant = MacroExpander(mapping).visit(template).value + newnode = ast.copy_location(constant, node) + return newnode + class MacroExpander(ast.NodeTransformer): def __init__(self, mapping): self.mapping = mapping @@ -96,19 +185,20 @@ class MacroExpander(ast.NodeTransformer): else: old = node.body[0] node.body = repl + return self.generic_visit(node) # recursively apply expand all macros def visit_Name(self, node): ''' Expand all Name node to simple value ''' - if type(node.ctx) is ast.Load: - try: - repl = self.mapping.pop(node.id) - except KeyError: - pass - else: - if repl is not None and not isinstance(repl, list): - return ast.copy_location(repl, node) + + try: + repl = self.mapping.pop(node.id) + except KeyError: + pass + else: + if repl is not None and not isinstance(repl, list): + return ast.copy_location(repl, node) return node From a046685b2c3499e6f1466058cd5f4c5ae63ff68d Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Thu, 30 Aug 2012 12:23:53 -0700 Subject: [PATCH 40/57] Add calling_conv. Set win32 api to use CC_X86_STDCALL. (need testing) --- builder.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/builder.py b/builder.py index b30c363..7aa5d93 100644 --- a/builder.py +++ b/builder.py @@ -1334,6 +1334,9 @@ class CExternal(object): All class attributes that are `llvm.core.FunctionType` are converted to `CFunc` instance during instantiation. ''' + + _calling_convention_ = None # default + def __init__(self, cbuilder): is_func = lambda x: isinstance(x, lc.FunctionType) non_magic = lambda s: not ( s.startswith('__') and s.endswith('__') ) @@ -1347,6 +1350,9 @@ class CExternal(object): mod = cbuilder.function.module for fname, ftype in to_declare: func = mod.get_or_insert_function(ftype, name=fname) + if self._calling_convention_: + func.calling_convention = self._calling_convention_ + if func.type.pointee != ftype: raise NameError("Function has already been declared " "with a different type: %s != %s" From e7b1af043f84c9a2593cf924fc2a72c1dd088506 Mon Sep 17 00:00:00 2001 From: Mark Wiebe Date: Thu, 30 Aug 2012 18:13:03 -0700 Subject: [PATCH 41/57] Use the function's calling convention when doing the call in cbuilder --- builder.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/builder.py b/builder.py index 7aa5d93..daad0c4 100644 --- a/builder.py +++ b/builder.py @@ -1155,6 +1155,8 @@ class CFunc(CValue): "argument %d mismatch: %s != %s" % (self.function.name, i, exp, got.type)) res = self.parent.builder.call(self.function, arg_values) + if hasattr(self.function, 'calling_convention'): + res.calling_convention = self.function.calling_convention return CTemp(self.parent, res) @property From 8505256510a36312a98a4f358db7a22a0c14effe Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Tue, 4 Sep 2012 10:05:24 -0700 Subject: [PATCH 42/57] Inline ufunc kernel in vectorize. (Better io throughput) --- builder.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/builder.py b/builder.py index daad0c4..216163c 100644 --- a/builder.py +++ b/builder.py @@ -192,6 +192,7 @@ class CBuilder(object): self.first_body_block = self.function.append_basic_block('body') self.builder = lc.Builder.new(self.first_body_block) self.target_data = le.TargetData.new(self.function.module.data_layout) + self._auto_inline_list = [] # Prepare arguments. Make all function arguments behave like variables. self.args = [] for arg in function.args: @@ -437,6 +438,10 @@ class CBuilder(object): with _change_block_temporarily(self.builder, self.declare_block): self.builder.branch(self.first_body_block) + # Do the auto inlining + for callinst in self._auto_inline_list: + lc.inline_function(callinst) + def constant(self, ty, val): '''create a constant @@ -680,6 +685,9 @@ class CBuilder(object): ''' self.builder.unreachable() + def add_auto_inline(self, callinst): + self._auto_inline_list.append(callinst) + class _DeclareCDef(object): '''create a function a CDefinition to use with `CBuilder.depends` @@ -1142,7 +1150,7 @@ class CFunc(CValue): super(CFunc, self).__init__(parent) self.function = func - def __call__(self, *args): + def __call__(self, *args, **opts): '''Call the function with the given arguments *args : variable arguments of CValue instances @@ -1157,6 +1165,10 @@ class CFunc(CValue): res = self.parent.builder.call(self.function, arg_values) if hasattr(self.function, 'calling_convention'): res.calling_convention = self.function.calling_convention + + if opts.get('inline'): + self.parent.add_auto_inline(res) + return CTemp(self.parent, res) @property From b941ae5f1fb61ac0bd7375ffa0bc526443b32bdb Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Thu, 6 Sep 2012 18:25:09 -0700 Subject: [PATCH 43/57] Use non-temporal hint at memory store for vectorizer. Note: LLVM optimizer is smart enough to not use NT write if it is slower. --- builder.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/builder.py b/builder.py index 216163c..6f8cd18 100644 --- a/builder.py +++ b/builder.py @@ -688,6 +688,12 @@ class CBuilder(object): def add_auto_inline(self, callinst): self._auto_inline_list.append(callinst) + + def set_memop_non_temporal(self, ldst): + const_one = self.constant(types.int, 1).value + md = lc.MetaData.get(self.function.module, [const_one]) + ldst.set_metadata('nontemporal', md) + class _DeclareCDef(object): '''create a function a CDefinition to use with `CBuilder.depends` @@ -1096,11 +1102,15 @@ class CValue(object): loaded = self.parent.builder.load(self.value, volatile=volatile) return CTemp(self.parent, loaded) - def store(self, val, volatile=False): + def store(self, val, volatile=False, nontemporal=False): '''memory store for pointer types ''' self._ensure_is_pointer() - self.parent.builder.store(val.value, self.value, volatile=volatile) + inst = self.parent.builder.store(val.value, self.value, + volatile=volatile) + if nontemporal: + self.parent.set_memop_non_temporal(inst) + def atomic_load(self, ordering, align=None, crossthread=True): '''atomic load memory for pointer types @@ -1248,11 +1258,13 @@ class CVar(CValue): def value(self): return self.parent.builder.load(self.ptr) - def assign(self, val): + def assign(self, val, nontemporal=False): '''assign new value to the variable ''' self._ensure_same_type(val) - self.parent.builder.store(val.value, self.ptr) + inst = self.parent.builder.store(val.value, self.ptr) + if nontemporal: + self.parent.set_memop_non_temporal(inst) @property def type(self): From 1e4633c44d83b7b24af3adb2edd416b5311cdd33 Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Fri, 7 Sep 2012 14:13:04 -0700 Subject: [PATCH 44/57] add invariant variable --- builder.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/builder.py b/builder.py index 6f8cd18..19822e0 100644 --- a/builder.py +++ b/builder.py @@ -1000,6 +1000,17 @@ class CValue(object): errmsg = "Cast from %s to %s is not possible." raise TypeError(errmsg % (self.type, ty)) + def ptrtoint(self): + self._ensure_is_pointer() + builder = self.parent.builder + return CTemp(self.parent, builder.ptrtoint(self.value, types.intp)) + + def inttoptr(self, ty): + if not self.is_int: + raise TypeError("Must be an integer") + builder = self.parent.builder + return CTemp(self.parent, builder.inttoptr(self.value, ty)) + def _cmp_op(self, name): '''implements comparison operations ''' @@ -1095,11 +1106,12 @@ class CValue(object): else: raise TypeError("Must be a pointer or vector; got %s" % self.type) - def load(self, volatile=False): + def load(self, volatile=False, invariant=False): '''memory load for pointer types ''' self._ensure_is_pointer() - loaded = self.parent.builder.load(self.value, volatile=volatile) + loaded = self.parent.builder.load(self.value, volatile=volatile, + invariant=invariant) return CTemp(self.parent, loaded) def store(self, val, volatile=False, nontemporal=False): @@ -1209,6 +1221,7 @@ class CVar(CValue): def __init__(self, parent, ptr): super(CVar, self).__init__(parent) self.ptr = ptr + self.invariant = False def _inplace_binop(self, op): def wrapped(rhs): @@ -1256,11 +1269,12 @@ class CVar(CValue): @property def value(self): - return self.parent.builder.load(self.ptr) + return self.parent.builder.load(self.ptr, invariant=self.invariant) def assign(self, val, nontemporal=False): '''assign new value to the variable ''' + if self.invariant: raise TypeError("Assignment to invariant variable") self._ensure_same_type(val) inst = self.parent.builder.store(val.value, self.ptr) if nontemporal: @@ -1354,6 +1368,7 @@ class CStruct(CValue): def reference(self): return CTemp(self.parent, self.ptr) + class CExternal(object): '''subclass to define external interface @@ -1385,3 +1400,4 @@ class CExternal(object): % (func.type, ftype) ) setattr(self, fname, CFunc(cbuilder, func)) + From af44641bb9fdee819848fb615088e4271ec1eb8b Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Fri, 7 Sep 2012 15:07:42 -0700 Subject: [PATCH 45/57] add noalias attribute to parameters --- builder.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/builder.py b/builder.py index 19822e0..a2f7aa9 100644 --- a/builder.py +++ b/builder.py @@ -765,7 +765,7 @@ class CDefinition(CBuilder): ''' _name_ = '' # name of the function; should overide in subclass _retty_ = types.void # return type; can overide in subclass - _argtys_ = [] # a list of tuple(name, type); can overide in subclass + _argtys_ = [] # a list of tuple(name, type, [attributes]); can overide in subclass def __new__(cls, *args, **kws): if cls.is_generic(): @@ -790,7 +790,7 @@ class CDefinition(CBuilder): Raises NameError if a function of the same name has already been defined. ''' - functype = lc.Type.function(cls._retty_, [v for k, v in cls._argtys_]) + functype = lc.Type.function(cls._retty_, [arg[1] for arg in cls._argtys_]) name = cls._name_ if not name: raise AttributeError("Function name cannot be empty.") @@ -801,9 +801,12 @@ class CDefinition(CBuilder): raise FunctionAlreadyExists(func) # Name all arguments - for i, (name, _) in enumerate(cls._argtys_): + for i, arginfo in enumerate(cls._argtys_): + name = arginfo[0] func.args[i].name = name - + if len(arginfo) > 2: + for attr in arginfo[2]: + func.args[i].add_attribute(attr) # Create builder and populate body cbuilder = object.__new__(cls) cbuilder.__init__(func) From 8a159b35f01f600673f059d1bcc29f296db6ec69 Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Fri, 7 Sep 2012 17:38:56 -0700 Subject: [PATCH 46/57] refactor llvm_cbuilder --- builder.py | 695 +++++++++++++++++++++++++---------------------------- 1 file changed, 329 insertions(+), 366 deletions(-) diff --git a/builder.py b/builder.py index a2f7aa9..f1c1d01 100644 --- a/builder.py +++ b/builder.py @@ -42,6 +42,9 @@ def _is_block_terminated(bb): instrs = bb.instructions return len(instrs) > 0 and instrs[-1].is_terminator +def _is_struct(ty): + return isinstance(ty, lc.StructType) + def _is_cstruct(ty): try: return issubclass(ty, CStruct) @@ -67,6 +70,10 @@ def _change_block_temporarily(builder, bb): def _change_block_temporarily_dummy(*args): yield +class CastError(TypeError): + def __init__(self, orig, to): + super(CastError, self).__init__("Cannot cast from %s to %s" % (orig, to)) + class _IfElse(object): '''if-else construct. @@ -399,7 +406,7 @@ class CBuilder(object): def check_arg(x): if isinstance(x, int): return self.constant(types.int, x) - if not x.is_int: + if not isinstance(x, IntegerValue): raise TypeError(x, "All args must be of integer type.") return x @@ -675,7 +682,11 @@ class CBuilder(object): def alignment(self, ty): '''get minimum alignment of `ty` ''' - return self.target_data.abi_alignment(ty) + return self.abi.abi_alignment(ty) + + @property + def abi(self): + return self.target_data def unreachable(self): '''insert instruction that causes segfault some platform (Intel), @@ -828,301 +839,335 @@ class CDefinition(CBuilder): raise NotImplementedError class CValue(object): - ''' - Signedness - ---------- - Since LLVM type does not provide signedness attribute. This information - is provided in the CValue.unsigned attribute. The default value is - `None`, meaning that this attribute is not set. + def __init__(self, parent, handle): + self.__parent = parent + self.__handle = handle - In casting operation, signednss information is passed as an optional arg. + @property + def handle(self): + return self.__handle - In binary operation, signedness of the left operand is used. - ''' + @property + def parent(self): + return self.__parent - unsigned = None # attribute for for integer values. + def _temp(self, val): + return CTemp(self.parent, val) - _BINOP_MAP = { - # op-name : (signed int, unsigned int, real) - 'add' : (lc.Builder.add, lc.Builder.add, lc.Builder.fadd), - 'sub' : (lc.Builder.sub, lc.Builder.sub, lc.Builder.fsub), - 'mul' : (lc.Builder.mul, lc.Builder.mul, lc.Builder.fmul), - 'div' : (lc.Builder.sdiv, lc.Builder.udiv, lc.Builder.fdiv), - 'mod' : (lc.Builder.srem, lc.Builder.urem, lc.Builder.frem), - } +def _get_operator_provider(ty): + if _is_pointer(ty): + return PointerValue + elif _is_int(ty): + return IntegerValue + elif _is_real(ty): + return RealValue + elif _is_vector(ty): + inner = _get_operator_provider(ty.element) + return type(str(ty), (inner, VectorIndexing), {}) + elif _is_struct(ty): + return StructValue + else: + assert False, (str(ty), type(ty)) - _BITWISE_MAP = { - # op-name : (signed int, unsigned int) - 'lshift' : (lc.Builder.shl, lc.Builder.shl), - 'rshift' : (lc.Builder.lshr, lc.Builder.ashr), - 'and' : (lc.Builder.and_, lc.Builder.and_), - 'or' : (lc.Builder.or_, lc.Builder.or_), - 'xor' : (lc.Builder.xor, lc.Builder.xor), - } +class CTemp(CValue): + def __new__(cls, parent, handle): + meta = _get_operator_provider(handle.type) + base = type(str('%s_%s' % (cls.__name__, handle.type)), (cls, meta), {}) + return object.__new__(base) - _CMP_MAP = { - # op-name : (signed int, unsigned int, real) - 'eq' : (lc.ICMP_EQ, lc.ICMP_EQ, lc.FCMP_OEQ), - 'ne' : (lc.ICMP_NE, lc.ICMP_NE, lc.FCMP_ONE), - 'lt' : (lc.ICMP_SLT, lc.ICMP_ULT, lc.FCMP_OLT), - 'le' : (lc.ICMP_SLE, lc.ICMP_ULE, lc.FCMP_OLE), - 'gt' : (lc.ICMP_SGT, lc.ICMP_UGT, lc.FCMP_OGT), - 'ge' : (lc.ICMP_SGE, lc.ICMP_UGE, lc.FCMP_OGE), - } + def __init__(self, *args, **kws): + super(CTemp, self).__init__(*args, **kws) + self._init_mixin() - def __init__(self, parent): - self.parent = parent + @property + def value(self): + return self.handle - def _use_binop(self, op): - '''implements binary operations - ''' - def wrapped(rhs): - self._ensure_same_type(rhs) - binop = self._BINOP_MAP[op] - if self.is_int: - if not self.unsigned: - idx = 0 - else: - idx = 1 - elif self.is_real: - idx = 2 - else: - errmsg = "Binary operation %s does not support type %s" - raise TypeError(errmsg % (op, self.type)) - res = binop[idx](self.parent.builder, self.value, rhs.value) - return CTemp(self.parent, res) - return wrapped + @property + def type(self): + return self.value.type - def _use_bitwise(self, op): - '''implements bitwise operations - ''' - def wrapped(rhs): - self._ensure_same_type(rhs) - if not self.is_int: - errmsg = "Bitwise operation %s does not support type %s" - raise TypeError(op, self.type) - if not self.unsigned: - idx = 0 - else: - idx = 1 - res = self._BITWISE_MAP[idx](self.parent.builder, - self.value, rhs.value) - return CTemp(self.parent, res) - return wrapped +class CVar(CValue): + def __new__(cls, parent, ptr): + meta = _get_operator_provider(ptr.type.pointee) + base = type(str('%s_%s' % (cls.__name__, ptr.type.pointee)), (cls, meta), {}) + return object.__new__(base) + + def __init__(self, parent, ptr): + super(CVar, self).__init__(parent, ptr) + self._init_mixin() + self.invariant = False + + def reference(self): + return self._temp(self.handle) + + @property + def ref(self): + return self.reference() + + @property + def value(self): + return self.parent.builder.load(self.ref.value, + invariant=self.invariant) + + @property + def type(self): + return self.ref.type.pointee + + def assign(self, val, **kws): + if self.invariant: + raise TypeError("Storing to invariant variable.") + self.parent.builder.store(val.value, self.ref.value, **kws) + return self + + def __iadd__(self, rhs): + return self.assign(self.__add__(rhs)) + + def __isub__(self, rhs): + return self.assign(self.__sub__(rhs)) + + def __imul__(self, rhs): + return self.assign(self.__mul__(rhs)) + + def __idiv__(self, rhs): + return self.assign(self.__div__(rhs)) + + def __imod__(self, rhs): + return self.assign(self.__mod__(rhs)) + + def __ilshift__(self, rhs): + return self.assign(self.__lshift__(rhs)) + + def __irshift__(self, rhs): + return self.assign(self.__rshift__(rhs)) + + def __iand__(self, rhs): + return self.assign(self.__and__(rhs)) + + def __ior__(self, rhs): + return self.assign(self.__or__(rhs)) + + def __ixor__(self, rhs): + return self.assign(self.__xor__(rhs)) + + +class OperatorMixin(object): + def _init_mixin(self): + pass + +class IntegerValue(OperatorMixin): + + def _init_mixin(self): + self._unsigned = False + + def _get_unsigned(self): + return self._unsigned + + def _set_unsigned(self, unsigned): + self._unsigned = bool(unsigned) + + unsigned = property(_get_unsigned, _set_unsigned) def __add__(self, rhs): - return self._use_binop('add')(rhs) + return self._temp(self.parent.builder.add(self.value, rhs.value)) def __sub__(self, rhs): - return self._use_binop('sub')(rhs) + return self._temp(self.parent.builder.sub(self.value, rhs.value)) def __mul__(self, rhs): - return self._use_binop('mul')(rhs) + return self._temp(self.parent.builder.mul(self.value, rhs.value)) def __div__(self, rhs): - return self._use_binop('div')(rhs) - - def __truediv__(self, rhs): - return self.__div__(rhs) + if self.unsigned: + return self._temp(self.parent.builder.udiv(self.value, rhs.value)) + else: + return self._temp(self.parent.builder.sdiv(self.value, rhs.value)) def __mod__(self, rhs): - return self._use_binop('mod')(rhs) + if self.unsigned: + return self._temp(self.parent.builder.urem(self.value, rhs.value)) + else: + return self._temp(self.parent.builder.srem(self.value, rhs.value)) - def __lshift__(self, rhs): - return self._use_bitwise('lshift')(rhs) + def __ilshift__(self, rhs): + return self._temp(self.parent.builder.shl(self.value, rhs.value)) - def __rshift__(self, rhs): - return self._use_bitwise('rshift')(rhs) + def __irshift__(self, rhs): + if self.unsigned: + return self._temp(self.self.parent.builder.lshr(self.value, rhs.value)) + else: + return self._temp(self.parent.builder.ashr(self.value, rhs.value)) - def __and__(self, rhs): - return self._use_bitwise('and')(rhs) + def __iand__(self, rhs): + return self._temp(self.parent.builder.and_(self.value, rhs.value)) - def __or__(self, rhs): - return self._use_bitwise('or')(rhs) + def __ior__(self, rhs): + return self._temp(self.parent.builder.or_(self.value, rhs.value)) - def __xor__(self, rhs): - return self._use_bitwise('xor')(rhs) - - def _ensure_same_type(self, val): - '''ensure that this instance has the same type as `val` - - Raises TypeError if `self.type != val.type` - ''' - if self.type != val.type: - errmsg = "Type mismatch: %s != %s" - raise TypeError(errmsg % (self.type, val.type)) - - @property - def is_int(self): - return _is_int(self.type) or _is_vector(self.type, _is_int) - - @property - def is_real(self): - return _is_real(self.type) or _is_vector(self.type, _is_real) - - def cast(self, ty, unsigned=False): - '''cast to another type - - If `ty == self.type`, then pass thru - ''' - make = lambda X: CTemp(self.parent, X) - if self.type == ty: - return self # pass thru - elif self.is_pointer and _is_pointer(ty): - builder = self.parent.builder - return make(builder.bitcast(self.value, ty)) - elif self.is_int: - if _is_int(ty): - if self.type.width < ty.width: - if not unsigned: - return make(self.parent.builder.sext(self.value, ty)) - else: - return make(self.parent.builder.zext(self.value, ty)) - else: - return make(self.parent.builder.trunc(self.value, ty)) - elif _is_real(ty): - if not unsigned: - return make(self.parent.builder.sitofp(self.value, ty)) - else: - return make(self.parent.builder.uitofp(self.value, ty)) - elif self.is_real: - if _is_int(ty): - if not unsigned: - return make(self.parent.builder.fptosi(self.value, ty)) - else: - return make(self.parent.builder.fptoui(self.value, ty)) - else: - if ty == types.double: - assert self.type == types.float - return make(self.parent.builder.fpext(self.value, ty)) - else: - assert ty == types.float - assert self.type == types.double - return make(self.parent.builder.fptrunc(self.value, ty)) - - errmsg = "Cast from %s to %s is not possible." - raise TypeError(errmsg % (self.type, ty)) - - def ptrtoint(self): - self._ensure_is_pointer() - builder = self.parent.builder - return CTemp(self.parent, builder.ptrtoint(self.value, types.intp)) - - def inttoptr(self, ty): - if not self.is_int: - raise TypeError("Must be an integer") - builder = self.parent.builder - return CTemp(self.parent, builder.inttoptr(self.value, ty)) - - def _cmp_op(self, name): - '''implements comparison operations - ''' - def wrapped(rhs): - make = lambda X: CTemp(self.parent, X) - self._ensure_same_type(rhs) - - flag_bag = self._CMP_MAP[name] - if self.is_int: - comparator = self.parent.builder.icmp - if not self.unsigned: - flag = flag_bag[0] - else: - flag = flag_bag[1] - elif self.is_real: - comparator = self.parent.builder.fcmp - flag = flag_bag[2] - else: - errmsg = "Comparision between %s and %s is not supported." - raise TypeError(errmsg % (self.type, rhs.type)) - - return CTemp(self.parent, comparator(flag, self.value, rhs.value)) - return wrapped - - def __eq__(self, rhs): - return self._cmp_op('eq')(rhs) - - def __ne__(self, rhs): - return self._cmp_op('ne')(rhs) + def __ixor__(self, rhs): + return self._temp(self.parent.builder.xor(self.value, rhs.value)) def __lt__(self, rhs): - return self._cmp_op('lt')(rhs) + if self.unsigned: + return self._temp(self.parent.builder.icmp(lc.ICMP_ULT, self.value, rhs.value)) + else: + return self._temp(self.parent.builder.icmp(lc.ICMP_SLT, self.value, rhs.value)) def __le__(self, rhs): - return self._cmp_op('le')(rhs) + if self.unsigned: + return self._temp(self.parent.builder.icmp(lc.ICMP_ULE, self.value, rhs.value)) + else: + return self._temp(self.parent.builder.icmp(lc.ICMP_SLE, self.value, rhs.value)) + + def __eq__(self, rhs): + return self._temp(self.parent.builder.icmp(lc.ICMP_EQ, self.value, rhs.value)) + + def __ne__(self, rhs): + return self._temp(self.parent.builder.icmp(lc.ICMP_NE, self.value, rhs.value)) def __gt__(self, rhs): - return self._cmp_op('gt')(rhs) + if self.unsigned: + return self._temp(self.parent.builder.icmp(lc.ICMP_UGT, self.value, rhs.value)) + else: + return self._temp(self.parent.builder.icmp(lc.ICMP_SGT, self.value, rhs.value)) def __ge__(self, rhs): - return self._cmp_op('ge')(rhs) + if self.unsigned: + return self._temp(self.parent.builder.icmp(lc.ICMP_UGE, self.value, rhs.value)) + else: + return self._temp(self.parent.builder.icmp(lc.ICMP_SGE, self.value, rhs.value)) + + def cast(self, ty, unsigned=False): + if ty == self.type: + return self._temp(self.value) + + if _is_real(ty): + if self.unsigned or unsigned: + return self._temp(self.parent.builder.uitofp(self.value, ty)) + else: + return self._temp(self.parent.builder.sitofp(self.value, ty)) + elif _is_int(ty): + if self.parent.abi.size(self.type) < self.parent.abi.size(ty): + if self.unsigned or unsigned: + return self._temp(self.parent.builder.zext(self.value, ty)) + else: + return self._temp(self.parent.builder.sext(self.value, ty)) + else: + return self._temp(self.parent.builder.trunc(self.value, ty)) + raise CastError(self.type, ty) + +class RealValue(OperatorMixin): + def __add__(self, rhs): + return self._temp(self.parent.builder.fadd(self.value, rhs.value)) + + def __sub__(self, rhs): + return self._temp(self.parent.builder.fsub(self.value, rhs.value)) + + def __mul__(self, rhs): + return self._temp(self.parent.builder.fmul(self.value, rhs.value)) + + def __div__(self, rhs): + return self._temp(self.parent.builder.fdiv(self.value, rhs.value)) + + def __mod__(self, rhs): + return self._temp(self.parent.builder.frem(self.value, rhs.value)) + + def __lt__(self, rhs): + return self._temp(self.parent.builder.fcmp(lc.FCMP_OLT, self.value, rhs.value)) + + def __le__(self, rhs): + return self._temp(self.parent.builder.fcmp(lc.FCMP_OLE, self.value, rhs.value)) + + def __eq__(self, rhs): + return self._temp(self.parent.builder.fcmp(lc.FCMP_OEQ, self.value, rhs.value)) + + def __ne__(self, rhs): + return self._temp(self.parent.builder.fcmp(lc.FCMP_ONE, self.value, rhs.value)) + + def __gt__(self, rhs): + return self._temp(self.parent.builder.fcmp(lc.FCMP_OGT, self.value, rhs.value)) + + def __ge__(self, rhs): + return self._temp(self.parent.builder.fcmp(lc.FCMP_OGE, self.value, rhs.value)) + + def cast(self, ty, unsigned=False): + if ty == self.type: + return self._temp(self.value) + + if _is_int(ty): + if unsigned: + return self._temp(self.parent.builder.fptoui(self.value, ty)) + else: + return self._temp(self.parent.builder.fptosi(self.value, ty)) + + if _is_real(ty): + if self.parent.abi.size(self.type) > self.parent.abi.size(ty): + return self._temp(self.parent.builder.fptrunc(self.value, ty)) + else: + return self._temp(self.parent.builder.fpext(self.value, ty)) + + raise CastError(self.type, ty) - @property - def is_vector(self): - return _is_vector(self.type) - - @property - def is_pointer(self): - return _is_pointer(self.type) - - def _ensure_is_pointer(self): - if not self.is_pointer: - raise TypeError("Must be a pointer; got %s" % self.type) - - def _ensure_is_vector(self): - if not self.is_vector: - raise TypeError("Must be a vector; got %s" % self.type) - +class PointerIndexing(OperatorMixin): def __getitem__(self, idx): '''implement access indexing Uses GEP. ''' bldr = self.parent.builder - if self.is_pointer: - if type(idx) is slice: - # just handle case by case - # Case #1: A[idx:] get poointer offset by idx - if not idx.step and not idx.stop: - idx = _auto_coerce_index(self.parent, idx.start) - ptr = bldr.gep(self.value, [idx.value]) - return CArray(self.parent, ptr) - else: # return an variable at idx - idx = _auto_coerce_index(self.parent, idx) + if type(idx) is slice: + # just handle case by case + # Case #1: A[idx:] get pointer offset by idx + if not idx.step and not idx.stop: + idx = _auto_coerce_index(self.parent, idx.start) ptr = bldr.gep(self.value, [idx.value]) - return CVar(self.parent, ptr) - elif self.is_vector: + return CArray(self.parent, ptr) + else: # return an variable at idx idx = _auto_coerce_index(self.parent, idx) - val = bldr.extract_element(self.value, idx.value) - return CTemp(self.parent, val) - else: - raise TypeError("Must be a pointer or vector; got %s" % self.type) + ptr = bldr.gep(self.value, [idx.value]) + return CVar(self.parent, ptr) def __setitem__(self, idx, val): idx = _auto_coerce_index(self.parent, idx) bldr = self.parent.builder - if self.is_vector: - vec = bldr.insert_element(self.value, val.value, idx.value) - bldr.store(vec, self.ptr) - elif self.is_pointer: - self[idx].assign(val) - else: - raise TypeError("Must be a pointer or vector; got %s" % self.type) + self[idx].assign(val) - def load(self, volatile=False, invariant=False): - '''memory load for pointer types - ''' - self._ensure_is_pointer() - loaded = self.parent.builder.load(self.value, volatile=volatile, - invariant=invariant) - return CTemp(self.parent, loaded) +class PointerCasting(OperatorMixin): + def cast(self, ty): + if ty == self.type: + return self._temp(self.value) - def store(self, val, volatile=False, nontemporal=False): - '''memory store for pointer types + if _is_pointer(ty): + return self._temp(self.parent.builder.bitcast(self.value, ty)) + raise CastError(self.type, ty) + + + +class VectorIndexing(OperatorMixin): + def __getitem__(self, idx): + '''implement access indexing + + Uses GEP. ''' - self._ensure_is_pointer() - inst = self.parent.builder.store(val.value, self.value, - volatile=volatile) + bldr = self.parent.builder + idx = _auto_coerce_index(self.parent, idx) + val = bldr.extract_element(self.value, idx.value) + return CTemp(self.parent, val) + + def __setitem__(self, idx, val): + idx = _auto_coerce_index(self.parent, idx) + bldr = self.parent.builder + vec = bldr.insert_element(self.value, val.value, idx.value) + bldr.store(vec, self.ref.value) + +class PointerValue(PointerIndexing, PointerCasting): + + def load(self, **kws): + return self._temp(self.parent.builder.load(self.value, **kws)) + + def store(self, val, nontemporal=False, **kws): + inst = self.parent.builder.store(val.value, self.value, **kws) if nontemporal: self.parent.set_memop_non_temporal(inst) @@ -1135,12 +1180,11 @@ class CValue(object): Other parameters are the same as `CBuilder.atomic_load` ''' - self._ensure_is_pointer() if align is None: align = self.parent.alignment(self.type.pointee) inst = self.parent.builder.atomic_load(self.value, ordering, align, crossthread=crossthread) - return CTemp(self.parent, inst) + return self._temp(inst) def atomic_store(self, value, ordering, align=None, crossthread=True): '''atomic memory store for pointer types @@ -1150,7 +1194,6 @@ class CValue(object): Other parameters are the same as `CBuilder.atomic_store` ''' - self._ensure_is_pointer() if align is None: align = self.parent.alignment(self.type.pointee) self.parent.builder.atomic_store(value.ptr, self.value, ordering, @@ -1161,19 +1204,34 @@ class CValue(object): Other parameters are the same as `CBuilder.atomic_cmpxchg` ''' - self._ensure_is_pointer() inst = self.parent.builder.atomic_cmpxchg(self.value, old.value, new.value, ordering, crossthread=crossthread) - return CTemp(self.parent, inst) + return self._temp(inst) + + def as_struct(self, cstruct_class, volatile=False): + '''load a pointer to a structure and assume a structure interface + ''' + ptr = self.parent.builder.load(self.value, volatile=volatile) + return cstruct_class(self.parent, self.value) + +class StructValue(OperatorMixin): + + def as_struct(self, cstruct_class): + '''assume a structure interface + ''' + return cstruct_class(self.parent, self.ref.value) -class CFunc(CValue): +class CFunc(CValue, PointerCasting): '''Wraps function pointer ''' def __init__(self, parent, func): - super(CFunc, self).__init__(parent) - self.function = func + super(CFunc, self).__init__(parent, func) + + @property + def function(self): + return self.handle def __call__(self, *args, **opts): '''Call the function with the given arguments @@ -1188,13 +1246,15 @@ class CFunc(CValue): "argument %d mismatch: %s != %s" % (self.function.name, i, exp, got.type)) res = self.parent.builder.call(self.function, arg_values) + if hasattr(self.function, 'calling_convention'): res.calling_convention = self.function.calling_convention if opts.get('inline'): self.parent.add_auto_inline(res) - return CTemp(self.parent, res) + if ftype.return_type != lc.Type.void(): + return CTemp(self.parent, res) @property def value(self): @@ -1204,123 +1264,25 @@ class CFunc(CValue): def type(self): return self.function.type -class CTemp(CValue): - '''Wraps temporary values - ''' - def __init__(self, parent, value): - super(CTemp, self).__init__(parent) - self.value = value - @property - def type(self): - return self.value.type - -class CVar(CValue): - '''Wraps variables - - Similar to C variables. - ''' - - def __init__(self, parent, ptr): - super(CVar, self).__init__(parent) - self.ptr = ptr - self.invariant = False - - def _inplace_binop(self, op): - def wrapped(rhs): - res = self._use_binop(op)(rhs) - self.assign(res) - return self - return wrapped - - def __iadd__(self, rhs): - return self._inplace_binop('add')(rhs) - - def __isub__(self, rhs): - return self._inplace_binop('sub')(rhs) - - def __imul__(self, rhs): - return self._inplace_binop('mul')(rhs) - - def __idiv__(self, rhs): - return self._inplace_binop('div')(rhs) - - def __imod__(self, rhs): - return self._inplace_binop('mod')(rhs) - - def _inplace_bitwise(self, op): - def wrapped(rhs): - res = self._use_bitwise(op)(rhs) - self.assign(res) - return self - return wrapped - - def __ilshift__(self, rhs): - return self._inplace_bitwise('lshift')(rhs) - - def __irshift__(self, rhs): - return self._inplace_bitwise('rshift')(rhs) - - def __iand__(self, rhs): - return self._inplace_bitwise('and')(rhs) - - def __ior__(self, rhs): - return self._inplace_bitwise('or')(rhs) - - def __ixor__(self, rhs): - return self._inplace_bitwise('xor')(rhs) - - @property - def value(self): - return self.parent.builder.load(self.ptr, invariant=self.invariant) - - def assign(self, val, nontemporal=False): - '''assign new value to the variable - ''' - if self.invariant: raise TypeError("Assignment to invariant variable") - self._ensure_same_type(val) - inst = self.parent.builder.store(val.value, self.ptr) - if nontemporal: - self.parent.set_memop_non_temporal(inst) - - @property - def type(self): - return self.ptr.type.pointee - - def reference(self): - '''get a pointer reference of the variable - ''' - return CTemp(self.parent, self.ptr) - - def as_struct(self, cstruct_class, volatile=False): - '''load a pointer to a structure and assume a structure interface - ''' - if _is_pointer(self.type): - ptr = self.parent.builder.load(self.ptr, volatile=volatile) - return cstruct_class(self.parent, ptr) - else: - return cstruct_class(self.parent, self.ptr) - - -class CArray(CValue): +class CArray(CValue, PointerIndexing, PointerCasting): '''wraps a array Similar to C arrays ''' def __init__(self, parent, base): - super(CArray, self).__init__(parent) - self.base_ptr = base + super(CArray, self).__init__(parent, base) @property def value(self): - return self.base_ptr + return self.handle def reference(self): - return CTemp(self.parent, self.base_ptr) + return self._temp(self.value) @property def type(self): - return self.base_ptr.type + return self.value.type def vector_load(self, count, align=0): parent = self.parent @@ -1328,9 +1290,9 @@ class CArray(CValue): values = [self[i] for i in range(count)] vecty = types.vector(self.type.pointee, count) - vec = builder.load(builder.bitcast(self.base_ptr, types.pointer(vecty)), + vec = builder.load(builder.bitcast(self.value, types.pointer(vecty)), align=align) - return CTemp(parent, vec) + return self._temp(vec) def vector_store(self, vec, align=0): if vec.type.element != self.type.pointee: @@ -1338,10 +1300,11 @@ class CArray(CValue): (vec.type.element, self.type.pointee)) parent = self.parent builder = parent.builder - vecptr = builder.bitcast(self.base_ptr, types.pointer(vec.type)) + vecptr = builder.bitcast(self.value, types.pointer(vec.type)) builder.store(vec.value, vecptr, align=align) return self + class CStruct(CValue): '''Wraps a structure @@ -1358,9 +1321,9 @@ class CStruct(CValue): return lc.Type.struct([v for k, v in cls._fields_]) def __init__(self, parent, ptr): - super(CStruct, self).__init__(parent) + super(CStruct, self).__init__(parent, ptr) makeind = lambda x: self.parent.constant(types.int, x).value - self.ptr = ptr + for i, (fd, _) in enumerate(self._fields_): gep = self.parent.builder.gep(ptr, [makeind(0), makeind(i)]) gep.name = "%s.%s" % (type(self).__name__, fd) @@ -1369,7 +1332,7 @@ class CStruct(CValue): setattr(self, fd, CVar(self.parent, gep)) def reference(self): - return CTemp(self.parent, self.ptr) + return self._temp(self.handle) class CExternal(object): From b0137289b9d2723edb469258beec2ec7ade7d056 Mon Sep 17 00:00:00 2001 From: Mark Florisson Date: Fri, 21 Sep 2012 22:39:48 +0200 Subject: [PATCH 47/57] Use meta to decompile python functions into ASTs --- builder.py | 10 +++++++--- translator.py | 20 ++++---------------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/builder.py b/builder.py index f1c1d01..5344d4f 100644 --- a/builder.py +++ b/builder.py @@ -364,9 +364,13 @@ class CBuilder(object): self.builder.ret(val.value) else: if retty != types.void: - errmsg = "Cannot return void" - raise TypeError(errmsg) - self.builder.ret_void() + # errmsg = "Cannot return void" + # raise TypeError(errmsg, retty) + bb = self.function.append_basic_block('unreachable_bb') + self.builder.position_at_end(bb) + self.builder.ret(self.builder.alloca(retty, name='undef')) + else: + self.builder.ret_void() @contextlib.contextmanager def ifelse(self, cond): diff --git a/translator.py b/translator.py index c0e9d5f..e9d9b54 100644 --- a/translator.py +++ b/translator.py @@ -1,5 +1,6 @@ # A handy translator that converts control flow into the appropriate # llvm_cbuilder constructs +from numba.functions import _get_ast import inspect, functools, ast import logging @@ -8,22 +9,9 @@ logger = logging.getLogger(__name__) def translate(func): # TODO use meta package wrapper = functools.wraps(func) - source = inspect.getsource(func) - lines = list(source.splitlines()) - - # skip first two lines - # assume they are - # @translate - # def blahblah(): - assert lines[0].lstrip().startswith('@') - assert lines[1].lstrip().startswith('def') - first_line_len = len(lines[2]) - indent = first_line_len - len(lines[2].lstrip()) - source = '\n'.join(line[indent:] for line in lines[2:]) - caller_frame = inspect.currentframe().f_back - - tree = ast.parse(source) + tree = _get_ast(func) + tree = ast.copy_location(ast.Module(body=tree.body), tree) tree = ExpandControlFlow().visit(tree) tree = ast.fix_missing_locations(tree) @@ -34,7 +22,7 @@ def translate(func): try: return eval(compile(tree, '', 'exec')) - except: + except Exception, e: logger.debug(ast.dump(tree)) from ArminRonacher import codegen # uses Armin Ronacher's codegen to debug # http://dev.pocoo.org/hg/sandbox/file/852a1248c8eb/ast/codegen.py From 10b3f188cc79d6e9bbf31eee6b122715cfb22c9b Mon Sep 17 00:00:00 2001 From: Mark Florisson Date: Tue, 18 Sep 2012 22:29:23 +0100 Subject: [PATCH 48/57] Some more progress towards CUDA gufuncs --- shortnames.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/shortnames.py b/shortnames.py index 012e53d..06221aa 100644 --- a/shortnames.py +++ b/shortnames.py @@ -1,3 +1,5 @@ +from numba.minivect import minitypes +import numba.decorators from llvm.core import Type void = Type.void() @@ -11,12 +13,15 @@ int64 = Type.int(64) float = Type.float() double = Type.double() +npy_intp = minitypes.npy_intp.to_llvm(numba.decorators.context) + # pointers pointer = Type.pointer void_p = pointer(char) char_p = pointer(char) +npy_intp_p = pointer(npy_intp) # platform dependent From bc03c9a8b7cb7b10b657beabbd24107f99cb7b41 Mon Sep 17 00:00:00 2001 From: Mark Florisson Date: Thu, 20 Sep 2012 22:23:09 +0200 Subject: [PATCH 49/57] Fix GC head for debug builds for gufunc fake arrays --- builder.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/builder.py b/builder.py index 5344d4f..b93cccc 100644 --- a/builder.py +++ b/builder.py @@ -1335,6 +1335,8 @@ class CStruct(CValue): raise AttributeError("Field name shadows another attribute") setattr(self, fd, CVar(self.parent, gep)) + self.type = self.llvm_type() + def reference(self): return self._temp(self.handle) From b9584e1071f6079cc8aecee8a36f64ce99565718 Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Fri, 21 Sep 2012 19:37:10 -0700 Subject: [PATCH 50/57] Fix assertion error in python source due to invalid lineno increment inside llvm_cbuilder.translator. --- translator.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/translator.py b/translator.py index e9d9b54..e3cb5ff 100644 --- a/translator.py +++ b/translator.py @@ -11,9 +11,20 @@ def translate(func): wrapper = functools.wraps(func) caller_frame = inspect.currentframe().f_back tree = _get_ast(func) - tree = ast.copy_location(ast.Module(body=tree.body), tree) + tree = ast.Module(body=tree.body) tree = ExpandControlFlow().visit(tree) - tree = ast.fix_missing_locations(tree) + + # NOTE: A hack to fix assertion error in debug mode due to bad lineno. + # Lineno must increase monotonically for co_lnotab, + # the "line number table" to work correctly. + # This script just set all lineno to 1 and col_offset = to 0. + # This makes it impossible to do traceback, but it is not possible + # anyway since we are dynamically changing the source code. + for node in ast.walk(tree): + # only ast.expr and ast.stmt and their subclass has lineno and col_offset. + if isinstance(node, ast.expr) or isinstance(node, ast.stmt): + node.lineno = 1 + node.col_offset = 0 # prepare locals for execution local_dict = locals() @@ -21,7 +32,8 @@ def translate(func): local_dict.update(caller_frame.f_globals) try: - return eval(compile(tree, '', 'exec')) + compiled = compile(tree, '', 'exec') + return eval(compiled) except Exception, e: logger.debug(ast.dump(tree)) from ArminRonacher import codegen # uses Armin Ronacher's codegen to debug @@ -97,7 +109,7 @@ class ExpandControlFlow(ast.NodeTransformer): ifelse = load_template(_if_else_template) ifelse = MacroExpander(mapping).visit(ifelse) newnode = self.generic_visit(ifelse) - return ast.copy_location(newnode, node) + return newnode def visit_While(self, node): mapping = { @@ -107,7 +119,7 @@ class ExpandControlFlow(ast.NodeTransformer): whileloop = load_template(_while_template) whileloop = MacroExpander(mapping).visit(whileloop) newnode = self.generic_visit(whileloop) - return ast.copy_location(newnode, node) + return newnode def visit_For(self, node): try: @@ -125,13 +137,13 @@ class ExpandControlFlow(ast.NodeTransformer): forloop = load_template(_for_range_template) forloop = MacroExpander(mapping).visit(forloop) newnode = self.generic_visit(forloop) - return ast.copy_location(newnode, node) + return newnode def visit_Return(self, node): mapping = {'__RETURN__' : node.value} ret = load_template(_return_template) repl = MacroExpander(mapping).visit(ret) - return ast.copy_location(repl, node) + return repl def visit_Num(self, node): '''convert immediate values @@ -148,7 +160,7 @@ class ExpandControlFlow(ast.NodeTransformer): '__VALUE__' : node, } constant = MacroExpander(mapping).visit(template).value - newnode = ast.copy_location(constant, node) + newnode = constant return newnode class MacroExpander(ast.NodeTransformer): @@ -187,6 +199,7 @@ class MacroExpander(ast.NodeTransformer): pass else: if repl is not None and not isinstance(repl, list): - return ast.copy_location(repl, node) + return repl return node + From 20a896b20620362e7b3737dc6018b1cf47d84349 Mon Sep 17 00:00:00 2001 From: Mark Florisson Date: Mon, 24 Sep 2012 12:21:12 +0100 Subject: [PATCH 51/57] Start on array expressions --- translator.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/translator.py b/translator.py index e3cb5ff..8fef17b 100644 --- a/translator.py +++ b/translator.py @@ -1,6 +1,6 @@ # A handy translator that converts control flow into the appropriate # llvm_cbuilder constructs -from numba.functions import _get_ast +from numba.functions import _get_ast, fix_ast_lineno import inspect, functools, ast import logging @@ -13,18 +13,7 @@ def translate(func): tree = _get_ast(func) tree = ast.Module(body=tree.body) tree = ExpandControlFlow().visit(tree) - - # NOTE: A hack to fix assertion error in debug mode due to bad lineno. - # Lineno must increase monotonically for co_lnotab, - # the "line number table" to work correctly. - # This script just set all lineno to 1 and col_offset = to 0. - # This makes it impossible to do traceback, but it is not possible - # anyway since we are dynamically changing the source code. - for node in ast.walk(tree): - # only ast.expr and ast.stmt and their subclass has lineno and col_offset. - if isinstance(node, ast.expr) or isinstance(node, ast.stmt): - node.lineno = 1 - node.col_offset = 0 + fix_ast_lineno(tree) # prepare locals for execution local_dict = locals() From ab0ce0d49aec00a6c92c679b735e2c8418ac16c8 Mon Sep 17 00:00:00 2001 From: "Travis E. Oliphant" Date: Thu, 27 Sep 2012 01:11:35 -0500 Subject: [PATCH 52/57] Update numbapro to handle latest numba changes. --- builder.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/builder.py b/builder.py index b93cccc..2826e1d 100644 --- a/builder.py +++ b/builder.py @@ -911,8 +911,7 @@ class CVar(CValue): @property def value(self): - return self.parent.builder.load(self.ref.value, - invariant=self.invariant) + return self.parent.builder.load(self.ref.value) @property def type(self): From f0ad108e2cf3e8c755dd28334c4496385fe9d624 Mon Sep 17 00:00:00 2001 From: Mark Florisson Date: Thu, 27 Sep 2012 16:30:14 +0100 Subject: [PATCH 53/57] Fix some stuff support casting int <-> pointers in llvm_cbuilder assign ndarray type to fake PyArrays --- builder.py | 5 +++++ shortnames.py | 1 + 2 files changed, 6 insertions(+) diff --git a/builder.py b/builder.py index 2826e1d..4a6f23b 100644 --- a/builder.py +++ b/builder.py @@ -1057,6 +1057,11 @@ class IntegerValue(OperatorMixin): return self._temp(self.parent.builder.sext(self.value, ty)) else: return self._temp(self.parent.builder.trunc(self.value, ty)) + elif _is_pointer(ty) and _is_int(self.type): + return self._temp(self.parent.builder.inttoptr(self.value, ty)) + elif _is_int(ty) and _is_pointer(self.type): + return self._temp(self.parent.builder.ptrtoint(self.value, ty)) + raise CastError(self.type, ty) class RealValue(OperatorMixin): diff --git a/shortnames.py b/shortnames.py index 06221aa..2acd69d 100644 --- a/shortnames.py +++ b/shortnames.py @@ -14,6 +14,7 @@ float = Type.float() double = Type.double() npy_intp = minitypes.npy_intp.to_llvm(numba.decorators.context) +py_ssize_t = minitypes.Py_ssize_t.to_llvm(numba.decorators.context) # pointers From 87a44e3117fd3b781eab21a628d86532a22a9c84 Mon Sep 17 00:00:00 2001 From: Mark Florisson Date: Fri, 26 Oct 2012 20:40:06 +0100 Subject: [PATCH 54/57] Get array slicing and expressions compiling --- builder.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/builder.py b/builder.py index 4a6f23b..cce1ba2 100644 --- a/builder.py +++ b/builder.py @@ -1040,6 +1040,9 @@ class IntegerValue(OperatorMixin): else: return self._temp(self.parent.builder.icmp(lc.ICMP_SGE, self.value, rhs.value)) + def __neg__(self): + return self._temp(self.parent.builder.neg(self.value)) + def cast(self, ty, unsigned=False): if ty == self.type: return self._temp(self.value) From bd377de19831f3ac21f4adfa162795690d82788b Mon Sep 17 00:00:00 2001 From: Mark Florisson Date: Mon, 29 Oct 2012 14:16:13 +0000 Subject: [PATCH 55/57] Fix cbuilder CDefinition specialization design and magic --- builder.py | 80 ++++++++++++++++++++++++++---------------------------- 1 file changed, 39 insertions(+), 41 deletions(-) diff --git a/builder.py b/builder.py index cce1ba2..b05a368 100644 --- a/builder.py +++ b/builder.py @@ -709,24 +709,6 @@ class CBuilder(object): md = lc.MetaData.get(self.function.module, [const_one]) ldst.set_metadata('nontemporal', md) -class _DeclareCDef(object): - '''create a function a CDefinition to use with `CBuilder.depends` - - An instance of this class is created by the constructor of CDefinition. - Do not use directly. - ''' - def __init__(self, cdef): - self.cdef = cdef - - def __str__(self): - return self.cdef._name_ - - def __call__(self, module): - try: - func = self.cdef.define(module) - except FunctionAlreadyExists as e: - (func,) = e - return func class CFuncRef(object): '''create a function reference to use with `CBuilder.depends` @@ -766,7 +748,8 @@ class CFuncRef(object): def __str__(self): return self._name -class CDefinition(CBuilder): + +class CDefinition(object): '''represents function definition Inherit from this class to create a new function definition. @@ -782,31 +765,33 @@ class CDefinition(CBuilder): _retty_ = types.void # return type; can overide in subclass _argtys_ = [] # a list of tuple(name, type, [attributes]); can overide in subclass - def __new__(cls, *args, **kws): - if cls.is_generic(): - # Call specialize if it is defined. - cls = type('%s_Specialized' % cls.__name__, (cls,), {}) - cls.specialize(*args, **kws) + def __init__(self, *args, **kwargs): + self.specialize(*args, **kwargs) + self.cbuilder = None - obj = object.__new__(_DeclareCDef) - obj.__init__(cls) - return obj + def specialize(self, *args, **kwargs): + """ + Override in subclasses + """ - @classmethod - def is_generic(cls): - '''Is this a generic definition? - ''' - return hasattr(cls, 'specialize') + def specialize_name(self): + """ + Specialize the class name to enable multiple function definitions + """ + cls = type(self) - @classmethod - def define(cls, module): + counter = getattr(cls, 'counter', 0) + cls._name_ = "%s_%d" % (cls._name_, counter) + cls.counter = counter + 1 + + def define(self, module): '''define the function in the module. Raises NameError if a function of the same name has already been defined. ''' - functype = lc.Type.function(cls._retty_, [arg[1] for arg in cls._argtys_]) - name = cls._name_ + functype = lc.Type.function(self._retty_, [arg[1] for arg in self._argtys_]) + name = self._name_ if not name: raise AttributeError("Function name cannot be empty.") @@ -816,17 +801,17 @@ class CDefinition(CBuilder): raise FunctionAlreadyExists(func) # Name all arguments - for i, arginfo in enumerate(cls._argtys_): + for i, arginfo in enumerate(self._argtys_): name = arginfo[0] func.args[i].name = name if len(arginfo) > 2: for attr in arginfo[2]: func.args[i].add_attribute(attr) + # Create builder and populate body - cbuilder = object.__new__(cls) - cbuilder.__init__(func) - cbuilder.body(*cbuilder.args) - cbuilder.close() + self.cbuilder = CBuilder(func) + self.body(*self.cbuilder.args) + self.cbuilder.close() # optimize fpm = lp.FunctionPassManager.new(module) @@ -837,11 +822,24 @@ class CDefinition(CBuilder): fpm.run(func) return func + def __call__(self, module): + # We don't really have to overload __call__ to do things like + # defining functions... + try: + func = self.define(module) + except FunctionAlreadyExists as e: + (func,) = e + return func + + def __getattr__(self, attr): + return getattr(self.cbuilder, attr) + def body(self): '''overide this function to define the body. ''' raise NotImplementedError + class CValue(object): def __init__(self, parent, handle): self.__parent = parent From 2685d60e38020b6962760ac6c268e1b187906232 Mon Sep 17 00:00:00 2001 From: Siu Kwan Lam Date: Mon, 5 Nov 2012 14:59:43 -0600 Subject: [PATCH 56/57] Fix CDefinition. Note: This fixes a horrible undeterminisitic bug for parallel vectorize. --- builder.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/builder.py b/builder.py index b05a368..67644db 100644 --- a/builder.py +++ b/builder.py @@ -812,6 +812,7 @@ class CDefinition(object): self.cbuilder = CBuilder(func) self.body(*self.cbuilder.args) self.cbuilder.close() + self.cbuilder = None # optimize fpm = lp.FunctionPassManager.new(module) @@ -839,6 +840,8 @@ class CDefinition(object): ''' raise NotImplementedError + def __str__(self): + return self._name_ class CValue(object): def __init__(self, parent, handle): From 004313fcbdcdc9932ac06a696fc657f848efd6e8 Mon Sep 17 00:00:00 2001 From: "Travis E. Oliphant" Date: Wed, 7 Nov 2012 11:01:40 -0600 Subject: [PATCH 57/57] Collected the data to move llvm_cbuilder --- __init__.py => llvm_cbuilder/__init__.py | 0 builder.py => llvm_cbuilder/builder.py | 0 executor.py => llvm_cbuilder/executor.py | 0 libc.py => llvm_cbuilder/libc.py | 0 shortnames.py => llvm_cbuilder/shortnames.py | 0 translator.py => llvm_cbuilder/translator.py | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename __init__.py => llvm_cbuilder/__init__.py (100%) rename builder.py => llvm_cbuilder/builder.py (100%) rename executor.py => llvm_cbuilder/executor.py (100%) rename libc.py => llvm_cbuilder/libc.py (100%) rename shortnames.py => llvm_cbuilder/shortnames.py (100%) rename translator.py => llvm_cbuilder/translator.py (100%) diff --git a/__init__.py b/llvm_cbuilder/__init__.py similarity index 100% rename from __init__.py rename to llvm_cbuilder/__init__.py diff --git a/builder.py b/llvm_cbuilder/builder.py similarity index 100% rename from builder.py rename to llvm_cbuilder/builder.py diff --git a/executor.py b/llvm_cbuilder/executor.py similarity index 100% rename from executor.py rename to llvm_cbuilder/executor.py diff --git a/libc.py b/llvm_cbuilder/libc.py similarity index 100% rename from libc.py rename to llvm_cbuilder/libc.py diff --git a/shortnames.py b/llvm_cbuilder/shortnames.py similarity index 100% rename from shortnames.py rename to llvm_cbuilder/shortnames.py diff --git a/translator.py b/llvm_cbuilder/translator.py similarity index 100% rename from translator.py rename to llvm_cbuilder/translator.py