cleanup and doc

This commit is contained in:
Siu Kwan Lam 2012-08-08 15:17:31 -07:00
commit 68e2359ad2
4 changed files with 406 additions and 56 deletions

View file

@ -8,6 +8,10 @@ import llvm.ee as le
from llvm import LLVMException from llvm import LLVMException
from . import shortnames as types from . import shortnames as types
###
# Utilities
###
def _is_int(ty): def _is_int(ty):
return isinstance(ty, lc.IntegerType) return isinstance(ty, lc.IntegerType)
@ -47,6 +51,19 @@ def _change_block_temporarily_dummy(*args):
yield yield
class _IfElse(object): 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): def __init__(self, parent, cond):
self.parent = parent self.parent = parent
self.cond = cond self.cond = cond
@ -84,6 +101,22 @@ class _IfElse(object):
builder.position_at_end(bbend) builder.position_at_end(bbend)
class _Loop(object): 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): def __init__(self, parent):
self.parent = parent self.parent = parent
@ -107,9 +140,8 @@ class _Loop(object):
def body(self): def body(self):
builder = self.parent.builder builder = self.parent.builder
builder.position_at_end(self._bbbody) builder.position_at_end(self._bbbody)
yield self yield self
# close last block
if not _is_block_terminated(builder.basic_block): if not _is_block_terminated(builder.basic_block):
builder.branch(self._bbcond) builder.branch(self._bbcond)
@ -121,9 +153,6 @@ class _Loop(object):
def close(self): def close(self):
builder = self.parent.builder 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) builder.position_at_end(self._bbend)
class CBuilder(object): class CBuilder(object):
@ -131,14 +160,18 @@ class CBuilder(object):
A wrapper class for features in llvm-py package A wrapper class for features in llvm-py package
to allow user to use C-like high-level language contruct easily. to allow user to use C-like high-level language contruct easily.
''' '''
def __init__(self, function): def __init__(self, function):
'''constructor
function : is an empty function to be populating.
'''
self.function = function self.function = function
self.declare_block = self.function.append_basic_block('decl') self.declare_block = self.function.append_basic_block('decl')
self.first_body_block = self.function.append_basic_block('body') self.first_body_block = self.function.append_basic_block('body')
self.builder = lc.Builder.new(self.first_body_block) self.builder = lc.Builder.new(self.first_body_block)
self.target_data = le.TargetData.new(self.function.module.data_layout) self.target_data = le.TargetData.new(self.function.module.data_layout)
# Prepare arguments. Make all function arguments behave like variables.
# prepare arguments
self.args = [] self.args = []
for arg in function.args: for arg in function.args:
var = self.var(arg.type, arg, name=arg.name) var = self.var(arg.type, arg, name=arg.name)
@ -146,21 +179,40 @@ class CBuilder(object):
@staticmethod @staticmethod
def new_function(mod, name, ret, args): 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) functype = lc.Type.function(ret, args)
func = mod.add_function(functype, name=name) func = mod.add_function(functype, name=name)
return CBuilder(func) return CBuilder(func)
def depends(self, fndecl): 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)) return CFunc(self, fndecl(self.function.module))
def printf(self, fmt, *args): def printf(self, fmt, *args):
mod = self.function.module '''printf() from libc
functype = lc.Type.function(types.int, [types.char_p], True)
printf = mod.get_or_insert_function(functype, name='printf') fmt : a character string holding printf format string.
ret = self.builder.call(printf, [fmt.value]+_list_values(args)) *args : additional variable arguments.
'''
from .libc import LibC
libc = LibC(self)
ret = libc.printf(fmt, *args)
return CTemp(self, ret) return CTemp(self, ret)
def debug(self, *args): def debug(self, *args):
'''debug print
Use printf to dump the values of all arguments.
'''
type_mapper = { type_mapper = {
'i8' : '%c', 'i8' : '%c',
'i16': '%hd', 'i16': '%hd',
@ -174,17 +226,27 @@ class CBuilder(object):
if isinstance(i, str): if isinstance(i, str):
itemsfmt.append(i.replace('%', '%%')) itemsfmt.append(i.replace('%', '%%'))
else: 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) itemsfmt.append(ty)
items.append(i) items.append(i)
fmt = ' '.join(itemsfmt) + '\n' fmt = ' '.join(itemsfmt) + '\n'
return self.printf(self.constant_string(fmt), *items) return self.printf(self.constant_string(fmt), *items)
def var(self, ty, value=None, name=''): def var(self, ty, value=None, name=''):
''' '''allocate variable on the stack
Only allocate in the first block
ty : variable type
value : [optional] initializer value
name : [optional] name used in LLVM IR
''' '''
with _change_block_temporarily(self.builder, self.declare_block): with _change_block_temporarily(self.builder, self.declare_block):
# goto the first block
is_cstruct = _is_cstruct(ty) is_cstruct = _is_cstruct(ty)
if is_cstruct: if is_cstruct:
cstruct = ty cstruct = ty
@ -203,27 +265,45 @@ class CBuilder(object):
return CVar(self, ptr) return CVar(self, ptr)
def var_copy(self, val, name=''): 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) return self.var(val.type, val, name=name)
def array(self, ty, count, 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): if isinstance(count, int) or isinstance(count, lc.Constant):
# Only go to the first block if array size is fixed.
contexthelper = _change_block_temporarily contexthelper = _change_block_temporarily
else: else:
# Do not go to the first block if the array size is dynamic.
contexthelper = _change_block_temporarily_dummy contexthelper = _change_block_temporarily_dummy
with contexthelper(self.builder, self.declare_block): with contexthelper(self.builder, self.declare_block):
is_cstruct = _is_cstruct(ty) if _is_cstruct(ty): # array of struct?
if is_cstruct:
cstruct = ty cstruct = ty
ty = ty.llvm_type() ty = ty.llvm_type()
if isinstance(count, CValue): if isinstance(count, CValue):
count = count.value count = count.value
elif not isinstance(count, lc.Value): elif not isinstance(count, lc.Value):
count = self.constant(types.int, count).value count = self.constant(types.int, count).value
ptr = self.builder.alloca_array(ty, count, name=name) ptr = self.builder.alloca_array(ty, count, name=name)
return CArray(self, ptr) return CArray(self, ptr)
def ret(self, val=None): 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 retty = self.function.type.pointee.return_type
if val is not None: if val is not None:
if val.type != retty: if val.type != retty:
@ -238,18 +318,26 @@ class CBuilder(object):
@contextlib.contextmanager @contextlib.contextmanager
def ifelse(self, cond): def ifelse(self, cond):
'''start a if-else block
cond : branch condition
'''
cb = _IfElse(self, cond) cb = _IfElse(self, cond)
yield cb yield cb
cb.close() cb.close()
@contextlib.contextmanager @contextlib.contextmanager
def loop(self): def loop(self):
'''start a loop block
'''
cb = _Loop(self) cb = _Loop(self)
yield cb yield cb
cb.close() cb.close()
@contextlib.contextmanager @contextlib.contextmanager
def forever(self): def forever(self):
'''start a forever loop block
'''
with self.loop() as loop: with self.loop() as loop:
with loop.condition() as setcond: with loop.condition() as setcond:
NULL = self.constant_null(types.int) NULL = self.constant_null(types.int)
@ -259,6 +347,10 @@ class CBuilder(object):
@contextlib.contextmanager @contextlib.contextmanager
def for_range(self, *args): def for_range(self, *args):
'''start a for-range block.
*args : same as arguments of builtin `range()`
'''
def check_arg(x): def check_arg(x):
if isinstance(x, int): if isinstance(x, int):
return self.constant(types.int, x) return self.constant(types.int, x)
@ -287,15 +379,26 @@ class CBuilder(object):
idx += step idx += step
def position_at_end(self, bb): def position_at_end(self, bb):
'''reposition inserter to the end of basic-block
bb : a basic block
'''
self.basic_block = bb self.basic_block = bb
self.builder.position_at_end(bb) self.builder.position_at_end(bb)
def close(self): def close(self):
'''end code generation
'''
# Close declaration block # Close declaration block
with _change_block_temporarily(self.builder, self.declare_block): with _change_block_temporarily(self.builder, self.declare_block):
self.builder.branch(self.first_body_block) self.builder.branch(self.first_body_block)
def constant(self, ty, val): def constant(self, ty, val):
'''create a constant
ty : data type
val : initializer
'''
if isinstance(ty, lc.IntegerType): if isinstance(ty, lc.IntegerType):
res = lc.Constant.int(ty, val) res = lc.Constant.int(ty, val)
elif ty == types.float or ty == types.double: elif ty == types.float or ty == types.double:
@ -306,119 +409,239 @@ class CBuilder(object):
return CTemp(self, res) return CTemp(self, res)
def constant_null(self, ty): def constant_null(self, ty):
'''create a zero filled constant
ty : data type
'''
res = lc.Constant.null(ty) res = lc.Constant.null(ty)
return CTemp(self, res) return CTemp(self, res)
def constant_string(self, string): 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 mod = self.function.module
# TODO collision = 0
# Is this a safe way for uniquing identifying string name_fmt = '.conststr.%x.%x'
name = '.conststr.%x_%x' % (hash(string), len(string))
content = lc.Constant.stringz(string) content = lc.Constant.stringz(string)
while True:
name = name_fmt % (hash(string), collision)
try: try:
# check if the name already exists
globalstr = mod.get_global_variable_named(name) globalstr = mod.get_global_variable_named(name)
except LLVMException: except LLVMException:
# new constant string
globalstr = mod.add_global_variable(content.type, name=name) globalstr = mod.add_global_variable(content.type, name=name)
globalstr.initializer = content 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( return CTemp(self, globalstr.bitcast(
types.pointer(content.type.element))) types.pointer(content.type.element)))
def get_intrinsic(self, intrinsic_id, tys): 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) lfunc = lc.Function.intrinsic(self.function.module, intrinsic_id, tys)
return CFunc(self, lfunc) return CFunc(self, lfunc)
def get_function_named(self, name): def get_function_named(self, name):
'''get function by name
'''
m = self.function.module m = self.function.module
func = m.get_function_named(name) func = m.get_function_named(name)
return CFunc(self, func) return CFunc(self, func)
def is_terminated(self): def is_terminated(self):
''' '''is the current basic-block terminated?
Is the current basic-block terminated?
''' '''
return _is_block_terminated(self.builder.basic_block) return _is_block_terminated(self.builder.basic_block)
def atomic_cmpxchg(self, ptr, old, val, ordering, crossthread=True): 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, res = self.builder.atomic_cmpxchg(ptr.value, old.value, val.value,
ordering, crossthread) ordering, crossthread)
return CTemp(self, res) return CTemp(self, res)
def atomic_xchg(self, ptr, val, ordering, crossthread=True): 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, res = self.builder.atomic_xchg(ptr.value, val.value,
ordering, crossthread) ordering, crossthread)
return CTemp(self, res) return CTemp(self, res)
def atomic_add(self, ptr, val, ordering, crossthread=True): 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, res = self.builder.atomic_add(ptr.value, val.value,
ordering, crossthread) ordering, crossthread)
return CTemp(self, res) return CTemp(self, res)
def atomic_sub(self, ptr, val, ordering, crossthread=True): 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, res = self.builder.atomic_sub(ptr.value, val.value,
ordering, crossthread) ordering, crossthread)
return CTemp(self, res) return CTemp(self, res)
def atomic_and(self, ptr, val, ordering, crossthread=True): 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, res = self.builder.atomic_and(ptr.value, val.value,
ordering, crossthread) ordering, crossthread)
return CTemp(self, res) return CTemp(self, res)
def atomic_nand(self, ptr, val, ordering, crossthread=True): 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, res = self.builder.atomic_nand(ptr.value, val.value,
ordering, crossthread) ordering, crossthread)
return CTemp(self, res) return CTemp(self, res)
def atomic_or(self, ptr, val, ordering, crossthread=True): 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, res = self.builder.atomic_or(ptr.value, val.value,
ordering, crossthread) ordering, crossthread)
return CTemp(self, res) return CTemp(self, res)
def atomic_xor(self, ptr, val, ordering, crossthread=True): 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, res = self.builder.atomic_xor(ptr.value, val.value,
ordering, crossthread) ordering, crossthread)
return CTemp(self, res) return CTemp(self, res)
def atomic_max(self, ptr, val, ordering, crossthread=True): 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, res = self.builder.atomic_max(ptr.value, val.value,
ordering, crossthread) ordering, crossthread)
return CTemp(self, res) return CTemp(self, res)
def atomic_min(self, ptr, val, ordering, crossthread=True): 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, res = self.builder.atomic_min(ptr.value, val.value,
ordering, crossthread) ordering, crossthread)
return CTemp(self, res) return CTemp(self, res)
def atomic_umax(self, ptr, val, ordering, crossthread=True): 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, res = self.builder.atomic_umax(ptr.value, val.value,
ordering, crossthread) ordering, crossthread)
return CTemp(self, res) return CTemp(self, res)
def atomic_umin(self, ptr, val, ordering, crossthread=True): 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, res = self.builder.atomic_umin(ptr.value, val.value,
ordering, crossthread) ordering, crossthread)
return CTemp(self, res) return CTemp(self, res)
def atomic_load(self, ptr, ordering, align=1, crossthread=True): 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) res = self.builder.atomic_load(ptr.value, ordering, align, crossthread)
return CTemp(self, res) return CTemp(self, res)
def atomic_store(self, val, ptr, ordering, align=1, crossthread=True): 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, res = self.builder.atomic_store(val.value, ptr.value, ordering,
align, crossthread) align, crossthread)
return CTemp(self, res) return CTemp(self, res)
def fence(self, ordering, crossthread=True): def fence(self, ordering, crossthread=True):
'''insert memory fence
'''
res = self.builder.fence(ordering, crossthread) res = self.builder.fence(ordering, crossthread)
return CTemp(self, res) return CTemp(self, res)
def alignment(self, ty): def alignment(self, ty):
'''get minimum alignment of `ty`
'''
return self.target_data.abi_alignment(ty) return self.target_data.abi_alignment(ty)
def unreachable(self): def unreachable(self):
'''insert instruction that causes segfault some platform (Intel),
or no-op on others.
It has no defined semantic.
'''
self.builder.unreachable() self.builder.unreachable()
class _DeclareCDef(object): 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): def __init__(self, cdef):
self.cdef = cdef self.cdef = cdef
@ -433,6 +656,9 @@ class _DeclareCDef(object):
return func return func
class CDeclare(object): class CDeclare(object):
'''create a function from name, type and pointer to use with
`CBuilder.depends`
'''
def __init__(self, name, ty, ptr): def __init__(self, name, ty, ptr):
self._name = name self._name = name
self._type = ty self._type = ty
@ -448,33 +674,51 @@ class CDeclare(object):
return self._name return self._name
class CDefinition(CBuilder): class CDefinition(CBuilder):
''' '''represents function definition
Inherit this class to for defining functions
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 _name_ = '' # name of the function; should overide in subclass
_retty_ = types.void # return type; can 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); can overide in subclass
def __new__(cls, *args, **kws): def __new__(cls, *args, **kws):
try: if cls.is_generic():
fnsp = getattr(cls, 'specialize') # Call specialize if it is defined.
except AttributeError:
pass
else:
cls = type('%s_Specialized' % cls.__name__, (cls,), {}) cls = type('%s_Specialized' % cls.__name__, (cls,), {})
fnsp(*args, **kws) cls.specialize(*args, **kws)
obj = object.__new__(_DeclareCDef) obj = object.__new__(_DeclareCDef)
obj.__init__(cls) obj.__init__(cls)
return obj return obj
@classmethod
def is_generic(cls):
'''Is this a generic definition?
'''
return hasattr(cls, 'specialize')
@classmethod @classmethod
def define(cls, module): 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_]) functype = lc.Type.function(cls._retty_, [v for k, v in cls._argtys_])
name = cls._name_ name = cls._name_
if not 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) func = module.get_or_insert_function(functype, name=name)
if not func.is_declaration: # already defined? if not func.is_declaration: # already defined?
raise NameError(func) raise NameError(func)
@ -490,25 +734,24 @@ class CDefinition(CBuilder):
return func return func
def body(self): def body(self):
''' '''overide this function to define the body.
Overide this function to define the body.
''' '''
raise NotImplementedError raise NotImplementedError
class CValue(object): class CValue(object):
''' '''
= Signess = Signedness
Since LLVM type does not provide signess attribute. This information ----------
Since LLVM type does not provide signedness attribute. This information
is provided in the CValue.unsigned attribute. The default value is is provided in the CValue.unsigned attribute. The default value is
`None`, meaning that this attribute is not set. `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 # attribute for for integer values.
unsigned = None
_BINOP_MAP = { _BINOP_MAP = {
# op-name : (signed int, unsigned int, real) # op-name : (signed int, unsigned int, real)
@ -542,6 +785,8 @@ class CValue(object):
self.parent = parent self.parent = parent
def _use_binop(self, op): def _use_binop(self, op):
'''implements binary operations
'''
def wrapped(rhs): def wrapped(rhs):
self._ensure_same_type(rhs) self._ensure_same_type(rhs)
binop = self._BINOP_MAP[op] binop = self._BINOP_MAP[op]
@ -560,6 +805,8 @@ class CValue(object):
return wrapped return wrapped
def _use_bitwise(self, op): def _use_bitwise(self, op):
'''implements bitwise operations
'''
def wrapped(rhs): def wrapped(rhs):
self._ensure_same_type(rhs) self._ensure_same_type(rhs)
if not self.is_int: if not self.is_int:
@ -608,6 +855,10 @@ class CValue(object):
return self._use_bitwise('xor')(rhs) return self._use_bitwise('xor')(rhs)
def _ensure_same_type(self, val): 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: if self.type != val.type:
errmsg = "Type mismatch: %s != %s" errmsg = "Type mismatch: %s != %s"
raise TypeError(errmsg % (self.type, val.type)) raise TypeError(errmsg % (self.type, val.type))
@ -621,6 +872,10 @@ class CValue(object):
return _is_real(self.type) return _is_real(self.type)
def cast(self, ty, unsigned=False): def cast(self, ty, unsigned=False):
'''cast to another type
If `ty == self.type`, then pass thru
'''
make = lambda X: CTemp(self.parent, X) make = lambda X: CTemp(self.parent, X)
if self.type == ty: if self.type == ty:
return self # pass thru return self # pass thru
@ -651,6 +906,8 @@ class CValue(object):
raise TypeError(errmsg % (self.type, ty)) raise TypeError(errmsg % (self.type, ty))
def _cmp_op(self, name): def _cmp_op(self, name):
'''implements comparison operations
'''
def wrapped(rhs): def wrapped(rhs):
make = lambda X: CTemp(self.parent, X) make = lambda X: CTemp(self.parent, X)
self._ensure_same_type(rhs) self._ensure_same_type(rhs)
@ -700,6 +957,10 @@ class CValue(object):
raise TypeError("Must be a pointer; got %s" % self.type) raise TypeError("Must be a pointer; got %s" % self.type)
def __getitem__(self, idx): def __getitem__(self, idx):
'''implement access indexing
Uses GEP.
'''
self._ensure_is_pointer() self._ensure_is_pointer()
if not isinstance(idx, CValue): if not isinstance(idx, CValue):
idx = self.parent.constant(types.int, idx) idx = self.parent.constant(types.int, idx)
@ -708,15 +969,26 @@ class CValue(object):
return CVar(self.parent, ptr) return CVar(self.parent, ptr)
def load(self, volatile=False): def load(self, volatile=False):
'''memory load for pointer types
'''
self._ensure_is_pointer() self._ensure_is_pointer()
loaded = self.parent.builder.load(self.value, volatile=volatile) loaded = self.parent.builder.load(self.value, volatile=volatile)
return CTemp(self.parent, loaded) return CTemp(self.parent, loaded)
def store(self, val, volatile=False): def store(self, val, volatile=False):
'''memory store for pointer types
'''
self._ensure_is_pointer() self._ensure_is_pointer()
self.parent.builder.store(val.value, self.value, volatile=volatile) self.parent.builder.store(val.value, self.value, volatile=volatile)
def atomic_load(self, ordering, align=None, crossthread=True): 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() self._ensure_is_pointer()
if align is None: if align is None:
align = self.parent.alignment(self.type.pointee) align = self.parent.alignment(self.type.pointee)
@ -725,6 +997,13 @@ class CValue(object):
return CTemp(self.parent, inst) return CTemp(self.parent, inst)
def atomic_store(self, value, ordering, align=None, crossthread=True): 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() self._ensure_is_pointer()
if align is None: if align is None:
align = self.parent.alignment(self.type.pointee) align = self.parent.alignment(self.type.pointee)
@ -732,6 +1011,10 @@ class CValue(object):
align=align, crossthread=crossthread) align=align, crossthread=crossthread)
def atomic_cmpxchg(self, old, new, ordering, crossthread=True): 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() self._ensure_is_pointer()
inst = self.parent.builder.atomic_cmpxchg(self.value, old.value, inst = self.parent.builder.atomic_cmpxchg(self.value, old.value,
new.value, ordering, new.value, ordering,
@ -740,11 +1023,17 @@ class CValue(object):
class CFunc(CValue): class CFunc(CValue):
'''Wraps function pointer
'''
def __init__(self, parent, func): def __init__(self, parent, func):
super(CFunc, self).__init__(parent) super(CFunc, self).__init__(parent)
self.function = func self.function = func
def __call__(self, *args): def __call__(self, *args):
'''Call the function with the given arguments
*args : variable arguments of CValue instances
'''
arg_values = _list_values(args) arg_values = _list_values(args)
ftype = self.function.type.pointee ftype = self.function.type.pointee
for i, (exp, got) in enumerate(zip(ftype.args, arg_values)): for i, (exp, got) in enumerate(zip(ftype.args, arg_values)):
@ -764,6 +1053,8 @@ class CFunc(CValue):
return self.function.type return self.function.type
class CTemp(CValue): class CTemp(CValue):
'''Wraps temporary values
'''
def __init__(self, parent, value): def __init__(self, parent, value):
super(CTemp, self).__init__(parent) super(CTemp, self).__init__(parent)
self.value = value self.value = value
@ -773,6 +1064,10 @@ class CTemp(CValue):
return self.value.type return self.value.type
class CVar(CValue): class CVar(CValue):
'''Wraps variables
Similar to C variables.
'''
def __init__(self, parent, ptr): def __init__(self, parent, ptr):
super(CVar, self).__init__(parent) super(CVar, self).__init__(parent)
@ -827,6 +1122,8 @@ class CVar(CValue):
return self.parent.builder.load(self.ptr) return self.parent.builder.load(self.ptr)
def assign(self, val): def assign(self, val):
'''assign new value to the variable
'''
self._ensure_same_type(val) self._ensure_same_type(val)
self.parent.builder.store(val.value, self.ptr) self.parent.builder.store(val.value, self.ptr)
@ -835,9 +1132,13 @@ class CVar(CValue):
return self.ptr.type.pointee return self.ptr.type.pointee
def reference(self): def reference(self):
'''get a pointer reference of the variable
'''
return CTemp(self.parent, self.ptr) return CTemp(self.parent, self.ptr)
def as_struct(self, cstruct_class, volatile=False): def as_struct(self, cstruct_class, volatile=False):
'''load a pointer to a structure and assume a structure interface
'''
if _is_pointer(self.type): if _is_pointer(self.type):
ptr = self.parent.builder.load(self.ptr, volatile=volatile) ptr = self.parent.builder.load(self.ptr, volatile=volatile)
return cstruct_class(self.parent, ptr) return cstruct_class(self.parent, ptr)
@ -845,6 +1146,10 @@ class CVar(CValue):
return cstruct_class(self.parent, self.ptr) return cstruct_class(self.parent, self.ptr)
class CArray(CValue): class CArray(CValue):
'''wraps a array
Similar to C arrays
'''
def __init__(self, parent, base): def __init__(self, parent, base):
super(CArray, self).__init__(parent) super(CArray, self).__init__(parent)
self.base_ptr = base self.base_ptr = base
@ -860,18 +1165,17 @@ class CArray(CValue):
def type(self): def type(self):
return self.base_ptr.type 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): 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 @classmethod
def llvm_type(cls): def llvm_type(cls):
return lc.Type.struct([v for k, v in cls._fields_]) 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_): for i, (fd, _) in enumerate(self._fields_):
gep = self.parent.builder.gep(ptr, [makeind(0), makeind(i)]) gep = self.parent.builder.gep(ptr, [makeind(0), makeind(i)])
gep.name = "%s.%s" % (type(self).__name__, fd) 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)) setattr(self, fd, CVar(self.parent, gep))
def reference(self): def reference(self):
return CTemp(self.parent, self.ptr) return CTemp(self.parent, self.ptr)
class CExternal(object): 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): def __init__(self, cbuilder):
is_func = lambda x: isinstance(x, lc.FunctionType) is_func = lambda x: isinstance(x, lc.FunctionType)
non_magic = lambda s: not ( s.startswith('__') and s.endswith('__') ) non_magic = lambda s: not ( s.startswith('__') and s.endswith('__') )

View file

@ -1,3 +1,7 @@
'''
This is mostly a convenience module for testing with ctypes.
'''
from llvm.core import Type, Module from llvm.core import Type, Module
import llvm.ee as le import llvm.ee as le
import ctypes as ct import ctypes as ct
@ -29,6 +33,8 @@ MAP_CTYPES = {
} }
class CExecutor(object): class CExecutor(object):
'''a convenient class for creating ctype functions from LLVM modules
'''
def __init__(self, mod_or_engine): def __init__(self, mod_or_engine):
if isinstance(mod_or_engine, Module): if isinstance(mod_or_engine, Module):
self.engine = le.EngineBuilder.new(mod_or_engine).opt(3).create() self.engine = le.EngineBuilder.new(mod_or_engine).opt(3).create()
@ -36,6 +42,14 @@ class CExecutor(object):
self.engine = mod_or_engine self.engine = mod_or_engine
def get_ctype_function(self, fn, *typeinfo): 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): if len(typeinfo)==1 and isinstance(typeinfo[0], str):
types = [ MAP_CTYPES[s.strip()] for s in typeinfo[0].split(',') ] types = [ MAP_CTYPES[s.strip()] for s in typeinfo[0].split(',') ]
if not types: if not types:

8
llvm_cbuilder/libc.py Normal file
View file

@ -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

View file

@ -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()