From 3ad1a3ae5be1588fd05aeb2022ea2f33c0d39eaf Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Keck <Jean-Baptiste.Keck@imag.fr> Date: Thu, 18 May 2017 17:59:40 +0200 Subject: [PATCH] custom atomics --- hysop/__init__.py | 12 +- .../device/codegen/base/cl_extensions.py | 11 +- .../device/codegen/base/struct_codegen.py | 4 +- .../device/codegen/base/union_codegen.py | 7 +- .../backend/device/codegen/base/variables.py | 103 +++++++++++------ .../codegen/functions/custom_atomics.py | 109 ++++++++++++++++++ .../codegen/functions/stretching_rhs.py | 10 +- .../backend/device/codegen/unions/__init__.py | 0 .../device/codegen/unions/float_int.py | 71 ++++++++++++ hysop/backend/device/codegen/unions/xaa | 73 ++++++++++++ hysop/backend/device/opencl/opencl_types.py | 25 +++- 11 files changed, 370 insertions(+), 55 deletions(-) create mode 100644 hysop/backend/device/codegen/functions/custom_atomics.py create mode 100644 hysop/backend/device/codegen/unions/__init__.py create mode 100644 hysop/backend/device/codegen/unions/float_int.py create mode 100644 hysop/backend/device/codegen/unions/xaa diff --git a/hysop/__init__.py b/hysop/__init__.py index 19f86c98e..b5afb0684 100644 --- a/hysop/__init__.py +++ b/hysop/__init__.py @@ -17,16 +17,16 @@ __SCALES_ENABLED__ = "ON" is "ON" __OPTIMIZE__ = "OFF" is "ON" __VERBOSE__ = True -__DEBUG__ = False -__TRACE__ = False -__KERNEL_DEBUG__ = True -__PROFILE__ = False +__DEBUG__ = "ON" in ["2", "3"] +__TRACE__ = "ON" in ["5"] +__KERNEL_DEBUG__ = "ON" in ["4", "3"] +__PROFILE__ = "OFF" in ["0", "1"] __ENABLE_LONG_TESTS__ = "OFF" is "ON" # OpenCL -__DEFAULT_PLATFORM_ID__ = 1 -__DEFAULT_DEVICE_ID__ = 1 +__DEFAULT_PLATFORM_ID__ = 0 +__DEFAULT_DEVICE_ID__ = 0 diff --git a/hysop/backend/device/codegen/base/cl_extensions.py b/hysop/backend/device/codegen/base/cl_extensions.py index 3dc403c7c..26120e309 100644 --- a/hysop/backend/device/codegen/base/cl_extensions.py +++ b/hysop/backend/device/codegen/base/cl_extensions.py @@ -30,8 +30,15 @@ class ClExtCodeGen(OpenClCodeGenerator): with self._codeblock_('pragma_extensions'): if ext_name not in _cl_extension_custom_declarations.keys(): with self._align_() as al: - base = '#pragma OPENCL EXTENSION {}$ : enable' - al.append(base.format(ext_name)) + base = \ +''' +#if {ext} + #pragma OPENCL EXTENSION {ext}$ : enable +#else + #error Your OpenCL device is missing the {ext} extension! +#endif +''' + al.append(base.format(ext=ext_name)) else: self.append(_cl_extension_custom_declarations[ext_name]); diff --git a/hysop/backend/device/codegen/base/struct_codegen.py b/hysop/backend/device/codegen/base/struct_codegen.py index 0b63f281a..12f6754de 100644 --- a/hysop/backend/device/codegen/base/struct_codegen.py +++ b/hysop/backend/device/codegen/base/struct_codegen.py @@ -20,7 +20,7 @@ class StructCodeGenerator(OpenClCodeGenerator): self.typedef = typedef self.dtype = np.dtype(dtype) - self.ctype = self.typedef if self.typedef else self.name + self.ctype = self.typedef if self.typedef else 'struct {}'.format(self.name) cl.tools.get_or_register_dtype(self.ctype,self.dtype) register_ctype_dtype(self.ctype,self.dtype) @@ -35,7 +35,7 @@ class StructCodeGenerator(OpenClCodeGenerator): def c_decl(self): dtype,cdecl = cl.tools.match_dtype_to_c_struct( \ - self.device,self.ctype,self.dtype,self.context) + self.device,self.ctype.replace('struct',''),self.dtype,self.context) return cdecl def gencode(self, comments, ctype_overrides): diff --git a/hysop/backend/device/codegen/base/union_codegen.py b/hysop/backend/device/codegen/base/union_codegen.py index e1eb66ca3..52cb7cfd0 100644 --- a/hysop/backend/device/codegen/base/union_codegen.py +++ b/hysop/backend/device/codegen/base/union_codegen.py @@ -20,7 +20,7 @@ class UnionCodeGenerator(OpenClCodeGenerator): self.typedef = typedef self.dtype = np.dtype(dtype) - self.ctype = self.typedef if (self.typedef is not None) else self.name + self.ctype = self.typedef if (self.typedef is not None) else 'union {}'.format(self.name) cl.tools.get_or_register_dtype(self.ctype,self.dtype) register_ctype_dtype(self.ctype,self.dtype) @@ -29,8 +29,11 @@ class UnionCodeGenerator(OpenClCodeGenerator): def c_decl(self): dtype,cdecl = cl.tools.match_dtype_to_c_struct( \ - self.device,self.ctype,self.dtype,self.context) + self.device,self.ctype.replace('union',''),self.dtype,self.context) return cdecl + + def fields(self): + return self.dtype.fields def gencode(self, comments, ctype_overrides): union_vars = re.compile('\s+((?:struct\s+)?\w+)\s+((?:\s*\**(?:\w+)(?:\[\d+\])*[,;])+)') diff --git a/hysop/backend/device/codegen/base/variables.py b/hysop/backend/device/codegen/base/variables.py index 7dcb0322a..e6a600693 100644 --- a/hysop/backend/device/codegen/base/variables.py +++ b/hysop/backend/device/codegen/base/variables.py @@ -1,8 +1,7 @@ -import re +from hysop.deps import np, re, copy import hysop.backend.device.opencl.opencl_types -from hysop.constants import np from hysop.backend.device.codegen.base.utils import VarDict from hysop.backend.device.opencl.opencl_types import OpenClTypeGen @@ -65,7 +64,7 @@ class CodegenVariable(object): value=None, svalue=None, const=False, add_impl_const=False, storage=None, nl=None, - ptr=False, restrict=False, + ptr=False, restrict=False, volatile=False, init=None, symbolic_mode=False,struct_var=None): @@ -100,19 +99,22 @@ class CodegenVariable(object): self.svalue = svalue self.storage = storage + self.volatile = volatile self.nl = nl if (nl is not None) else (storage is not None) self.ptr = ptr self.struct_var = struct_var self.symbolic_mode = symbolic_mode self.init=init - const = [const] if isinstance(const,bool) else list(const) - add_impl_const = [add_impl_const] if isinstance(add_impl_const,bool) else list(add_impl_const) - restrict = [restrict] if isinstance(restrict,bool) else list(restrict) + const = [const] if isinstance(const,bool) else list(const) + add_impl_const = [add_impl_const] if isinstance(add_impl_const,bool) \ + else list(add_impl_const) + restrict = [restrict] if isinstance(restrict,bool) else list(restrict) _len = max(max(len(const),len(add_impl_const)), len(restrict)) - const = np.asarray(const + (_len-len(const))*[False], dtype=bool) - add_impl_const = np.asarray(add_impl_const + (_len-len(add_impl_const))*[False], dtype=bool) - restrict = np.asarray(restrict + (_len-len(restrict))*[False], dtype=bool) + const = np.asarray(const + (_len-len(const))*[False], dtype=bool) + add_impl_const = np.asarray(add_impl_const + (_len-len(add_impl_const))*[False], + dtype=bool) + restrict = np.asarray(restrict + (_len-len(restrict))*[False], dtype=bool) if (const.size != add_impl_const.size) or (const.size != restrict.size): raise ValueError('const, add_impl_const, restrict size mismatch!') if not ptr and restrict.any(): @@ -120,7 +122,8 @@ class CodegenVariable(object): if not ptr and (const.size>1 or add_impl_const.size>1): raise ValueError('Non pointer types cannot have multiple const qualifiers!') if not ptr and np.logical_and(const, add_impl_const).any(): - raise ValueError('Variable {} is const and add_impl_const has been specified!'.format(name)) + raise ValueError('Variable {} is const and add_impl_const has been specified!'\ + .format(name)) #if (struct_var is not None) and (struct_var.const) and (not const[0]): #raise ValueError('Variable {} declared in const struct {} is not const!'.format(name,struct_var.name)) self.const = const @@ -128,7 +131,45 @@ class CodegenVariable(object): self.restrict = restrict + def full_ctype(self, const=None, impl=False): + const = self.const if (const is None) else const + ctype = self.ctype + + if not self.ptr: + if const or (impl and self.add_impl_const): + const = 'const ' + else: + const = '' + else: + const = ' const' if const else ''#' '*6 + ctype += '{} *'.format(const) + if impl and self.add_impl_const: + ctype += 'const ' + const='' + + restrict = 'restrict' if self.restrict else '' + storage = ('volatile ' if self.volatile else '') + storage += (self.storage + ' ') if self.storage else '' + return '{storage}{const}{ctype}{restrict}'.format(storage=storage, + restrict=restrict,const=const,ctype=ctype) + def argument(self,impl,const=None): + name = self.name + nl = '\n' if self.nl else '' + return '{} {name}{nl}'.format(self.full_ctype(impl=impl,const=const),name=name,nl=nl) + + + def alias(self, name, ctype, + const=None, volatile=None): + handle = copy.copy(self) + handle.ctype = ctype + handle.name=name + handle.const = const if (const is not None) else handle.const + handle.volatile = volatile if (volatile is not None) else handle.volatile + handle.init='({})({})'.format(handle.full_ctype(), self) + return handle + + def is_symbolic(self): return (self.value is None) or self.symbolic_mode def in_struct(self): @@ -172,6 +213,7 @@ class CodegenVariable(object): storage = storage if (storage is not None) else self.storage storage = '{} '.format(storage) if (storage is not None) else '' + storage = '{}{}'.format('volatile ' if self.volatile else '', storage) if const is not None: self.const = np.asarray(self.const,bool) @@ -182,7 +224,8 @@ class CodegenVariable(object): const = self.const if restrict is not None: - restrict = (restrict,)+(False,)*(self.restrict.size-1) if isinstance(restrict,bool) else restrict + restrict = (restrict,)+(False,)*(self.restrict.size-1) \ + if isinstance(restrict,bool) else restrict restrict = np.asarray(restrict,dtype=bool) restrict = np.logical_or(restrict, self.restrict) else: @@ -198,7 +241,8 @@ class CodegenVariable(object): for (s,c,r) in zip(sshape,const,restrict)] array_qualifiers = ''.join(qualifiers) else: - qualifiers = [('const' if c else '') + (' restrict' if r else '' ) for (c,r) in zip(const,restrict)] + qualifiers = [('const' if c else '') + (' restrict' if r else '' ) \ + for (c,r) in zip(const,restrict)] impl_const = ' const' if self.add_impl_const[0] else '' array_qualifiers = '{} *'.format(impl_const)+' *'.join(qualifiers) @@ -232,31 +276,6 @@ class CodegenVariable(object): codegen.append(code) return code - def argument(self,impl,const=None): - const = self.const if (const is None) else const - ctype = self.ctype - - if not self.ptr: - if const or (impl and self.add_impl_const): - const = 'const ' - else: - const = '' - else: - const = ' const' if const else ' '*6 - ctype += '{} *'.format(const) - if impl and self.add_impl_const: - ctype += 'const ' - const='' - - restrict = 'restrict' if self.restrict else '' - storage = (self.storage + ' ') if self.storage else '' - if self.storage=='__local': - storage+=' ' - name = self.name - nl = '\n' if self.nl else '' - - return '{storage}{const}{ctype}{restrict} {name}{nl}'.format(storage=storage,restrict=restrict,const=const,ctype=ctype,name=name,nl=nl) - def __call__(self): return self.sval() def __getitem__(self,ss): @@ -642,7 +661,15 @@ class CodegenStruct(CodegenVariable): if key in self.vars: return self.vars[key] else: - raise KeyError('Unknown field {} in struct {}.\nAvailable fields are: {}'.format(key,self.name,self.vars.keys())) + msg='Unknown field {} in struct {}.\nAvailable fields are: {}' + msg=msg.format(key,self.name,self.vars.keys()) + raise KeyError(msg) + + def __getattr__(self, name): + if name in self.vars: + return self.__getitem__(name) + else: + raise AttributeError def genvars(self,struct,var_overrides): if var_overrides is None: diff --git a/hysop/backend/device/codegen/functions/custom_atomics.py b/hysop/backend/device/codegen/functions/custom_atomics.py new file mode 100644 index 000000000..b1283303e --- /dev/null +++ b/hysop/backend/device/codegen/functions/custom_atomics.py @@ -0,0 +1,109 @@ + + +from hysop.backend.device.codegen.base.opencl_codegen import OpenClCodeGenerator +from hysop.backend.device.codegen.base.function_codegen import OpenClFunctionCodeGenerator +from hysop.backend.device.codegen.base.variables import CodegenVariable, CodegenVectorClBuiltin +from hysop.backend.device.opencl.opencl_types import OpenClTypeGen +from hysop.backend.device.codegen.base.utils import WriteOnceDict, ArgDict + +class CustomAtomicFunction(OpenClFunctionCodeGenerator): + + def __init__(self,typegen,ftype,op, + storage='__global'): + + reqs,union = self.build_union(ftype,typegen) + + args = ArgDict() + args['p'] = CodegenVariable('p',ftype,add_impl_const=False,typegen=typegen, + ptr=True, volatile=True, storage=storage) + args['val'] = CodegenVariable('val',ftype,const=True,typegen=typegen) + + fname = 'custom_atomic_{}_op{}'.format(union.name,hex(hash(op))[2:6]) + super(CustomAtomicFunction,self).__init__(basename=fname, + output=ftype,args=args,typegen=typegen) + + self.op = op + self.ftype=ftype + self.storage=storage + + if tg.opencl_version_greater(1,0): + if ftype=='float': + atomic_cmpxchg='atomic_cmpxchg({ptr},{intcmp},{val});' + elif ftype=='double': + self.declare_cl_extension('cl_khr_int64_base_atomics') + atomic_cmpxchg='atom_cmpxchg({ptr},{intcmp},{val});' + else: + raise NotImplementedError('Unknown ftype {}.'.format(ftype)) + else: + if ftype=='float': + if storage=='__global': + self.declare_cl_extension('cl_khr_global_int32_base_atomics') + elif storage=='__local': + self.declare_cl_extension('cl_khr_local_int32_base_atomics') + else: + raise NotImplementedError('Unknown storage {}.'.format(storage)) + elif ftype=='double': + self.declare_cl_extension('cl_khr_int64_base_atomics') + else: + raise NotImplementedError('Unknown ftype {}.'.format(ftype)) + atomic_cmpxchg='atom_cmpxchg({ptr},{intcmp},{val});' + + self.update_requirements(reqs) + self.gencode(atomic_cmpxchg) + + def build_union(self, ftype, tg): + from hysop.backend.device.codegen.unions.float_int import FloatIntegerUnion + reqs = WriteOnceDict() + union = FloatIntegerUnion(ftype, tg) + reqs['union'] = union + return reqs, union + + def gencode(self, atomic_cmpxchg): + s = self + tg = s.typegen + op = s.op + + p = s.args['p'] + val = s.args['val'] + + union = self.reqs['union'] + old_val = union.build_codegen_variable('old_val') + new_val = union.build_codegen_variable('new_val') + ret_val = union.build_codegen_variable('ret_val') + alias = p.alias(name='ip', ctype=old_val.intval.ctype, + volatile=False) + with s._function_(): + old_val.declare(s) + new_val.declare(s) + ret_val.declare(s) + s.jumpline() + alias.declare(s) + with s._do_while_('{} != {}'.format(ret_val.intval, old_val.intval)): + load='{} = *{};'.format(old_val.floatval, p) + op='{} = {};'.format(new_val.floatval, op.format(old_val.floatval, val)) + cmpxchg = '{} = {}'.format(ret_val.intval, + atomic_cmpxchg.format(ptr=alias, intcmp=old_val.intval, + val=new_val.intval)) + s.append(load) + s.append(op) + s.append(cmpxchg) + s.jumpline() + s.append('return {};'.format(ret_val.floatval)) + + + + +if __name__ == '__main__': + from hysop.backend.device.codegen.base.test import _test_typegen + tg = _test_typegen('double') + cg = OpenClCodeGenerator('main',tg) + + h = CustomAtomicFunction(tg, 'float', '{} + {}', '__global' ) + f = CustomAtomicFunction(tg, 'double', '{} + {}', '__local' ) + cg.require(h.name,f) + cg.require(f.name,f) + + cg.edit() + + cg.test_compile() + diff --git a/hysop/backend/device/codegen/functions/stretching_rhs.py b/hysop/backend/device/codegen/functions/stretching_rhs.py index 140bebb15..fcb9f2bcb 100644 --- a/hysop/backend/device/codegen/functions/stretching_rhs.py +++ b/hysop/backend/device/codegen/functions/stretching_rhs.py @@ -49,11 +49,13 @@ class DirectionalStretchingRhsFunction(OpenClFunctionCodeGenerator): vtype = typegen.vtype(ftype,dim) - (args,basename) = self.build_prototype(typegen,dim,itype,ftype,vtype,order,direction,cached, - restrict,storage,vectorize_u,used_variables,formulation,is_conservative,is_periodic) + (args,basename) = self.build_prototype(typegen,dim,itype,ftype,vtype,order, + direction,cached,restrict,storage,vectorize_u,used_variables, + formulation,is_conservative,is_periodic) - reqs = self.build_requirements(typegen,dim,itype,ftype,vtype,order,direction,boundary,cached, - restrict,storage,vectorize_u,used_variables,is_conservative,is_periodic,args) + reqs = self.build_requirements(typegen,dim,itype,ftype,vtype,order,direction, + boundary,cached,restrict,storage,vectorize_u,used_variables, + is_conservative,is_periodic,args) super(DirectionalStretchingRhsFunction,self).__init__(basename=basename, output=vtype,typegen=typegen,inline=True, diff --git a/hysop/backend/device/codegen/unions/__init__.py b/hysop/backend/device/codegen/unions/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/hysop/backend/device/codegen/unions/float_int.py b/hysop/backend/device/codegen/unions/float_int.py new file mode 100644 index 000000000..643d85f6e --- /dev/null +++ b/hysop/backend/device/codegen/unions/float_int.py @@ -0,0 +1,71 @@ + + +from hysop.deps import np +from hysop.tools.types import check_instance +from hysop.backend.device.codegen.base.union_codegen import UnionCodeGenerator +from hysop.backend.device.opencl.opencl_types import OpenClTypeGen + +class FloatIntegerUnion(UnionCodeGenerator): + def __init__(self, ftype, typegen, typedef=None): + + name,dtype,comments = self.build_dtype(typegen, ftype) + + super(FloatIntegerUnion,self).__init__(name=name, + dtype=dtype, + typegen=typegen, + typedef=typedef, + comments=comments) + + @staticmethod + def build_dtype(typegen, ftype): + tg = typegen + + name = 'float_int' + + dtype = [] + if ftype == 'half': + name+='16' + dtype.append(('intval', np.int16)) + dtype.append(('uintval', np.uint16)) + dtype.append(('floatval', np.float16)) + elif ftype == 'float': + name+='32' + dtype.append(('intval', np.int32)) + dtype.append(('uintval', np.uint32)) + dtype.append(('floatval', np.float32)) + elif ftype == 'double': + name+='64' + dtype.append(('intval', np.int64)) + dtype.append(('uintval', np.uint64)) + dtype.append(('floatval', np.float64)) + else: + msg='Unknown ftype \'{}\', only half, float and double are supported.' + msg=msg.format(ftype) + raise ValueError(msg) + + comments = [ + 'Access value as a signed integer', + 'Access value as a unsigned integer', + 'Access value as a floating point', + ] + + return name, dtype, comments + + +if __name__ == '__main__': + from hysop.backend.device.codegen.base.opencl_codegen import OpenClCodeGenerator + from hysop.backend.device.codegen.base.test import _test_typegen + + tg = _test_typegen() + + u1 = FloatIntegerUnion('double', tg) + u2 = FloatIntegerUnion('float', tg, 'custom32') + + cg = OpenClCodeGenerator('test_generator',tg) + cg.declare_cl_extension('cl_khr_fp64') + cg.require('u1',u1) + cg.require('u2',u2) + + cg.edit() + + cg.test_compile() diff --git a/hysop/backend/device/codegen/unions/xaa b/hysop/backend/device/codegen/unions/xaa new file mode 100644 index 000000000..e1eb66ca3 --- /dev/null +++ b/hysop/backend/device/codegen/unions/xaa @@ -0,0 +1,73 @@ + +import pyopencl as cl +import pyopencl.tools + +import numpy as np +import re + +from hysop.backend.device.opencl.opencl_types import np_dtype +from hysop.backend.device.codegen.base.opencl_codegen import OpenClCodeGenerator +from hysop.backend.device.codegen.base.variables import VarDict, CodegenVariable, CodegenVector, CodegenStruct, CodegenVectorClBuiltin +from hysop.backend.device.codegen.base.variables import register_ctype_dtype + +class UnionCodeGenerator(OpenClCodeGenerator): + def __init__(self,name,dtype,typegen, + typedef=None,comments=None, + ctype_overrides=None, + custom_types={}): + + super(UnionCodeGenerator,self).__init__(name=name,typegen=typegen) + + self.typedef = typedef + self.dtype = np.dtype(dtype) + self.ctype = self.typedef if (self.typedef is not None) else self.name + + cl.tools.get_or_register_dtype(self.ctype,self.dtype) + register_ctype_dtype(self.ctype,self.dtype) + + self.gencode(comments, ctype_overrides) + + def c_decl(self): + dtype,cdecl = cl.tools.match_dtype_to_c_struct( \ + self.device,self.ctype,self.dtype,self.context) + return cdecl + + def gencode(self, comments, ctype_overrides): + union_vars = re.compile('\s+((?:struct\s+)?\w+)\s+((?:\s*\**(?:\w+)(?:\[\d+\])*[,;])+)') + lines = self.c_decl().split('\n') + + with self._union_(name=self.name,typedef=self.typedef): + with self._var_block_() as vb: + i=0 + for l in lines: + match = union_vars.match(l) + if match: + ctype = match.group(1) + variables = match.group(2).replace(';','').split(',') + if (ctype_overrides is not None) and (i in ctype_overrides.keys()): + ctype = ctype_overrides[i] + if comments is not None: + vb.decl_var(ctype,','.join(variables), comment=comments[i]) + else: + vb.decl_var(ctype,','.join(variables)) + i+=1 + + def build_codegen_variable(self, name, **kargs): + return CodegenStruct(varname=name, struct=self, **kargs) + +if __name__ == '__main__': + + from hysop.backend.device.codegen.base.test import _test_typegen + + dtype = [] + dtype.append( ('f0', np.float32 ) ) + dtype.append( ('d0', np.int32) ) + dtype.append( ('v0', np_dtype('float4')) ) + dtype.append( ('v1', np_dtype('int16')) ) + + scg = UnionCodeGenerator('TestUnion',dtype,typedef='TestUnion_s', + typegen=_test_typegen()) + scg.edit() + scg.test_compile() + + diff --git a/hysop/backend/device/opencl/opencl_types.py b/hysop/backend/device/opencl/opencl_types.py index 68ea14681..3a40d957a 100644 --- a/hysop/backend/device/opencl/opencl_types.py +++ b/hysop/backend/device/opencl/opencl_types.py @@ -1,6 +1,6 @@ from hysop import __KERNEL_DEBUG__ -from hysop.deps import sm, np, it, string +from hysop.deps import sm, np, it, string, re from hysop.backend.device.opencl import cl, clArray from hysop.tools.numerics import MPZ, MPQ, MPFR, F2Q @@ -329,6 +329,29 @@ class OpenClTypeGen(TypeGen): def cl_requirements(self): return [self.float_base_type_require[self.fbtype]]; + def opencl_version_greater(self, major, minor): + (cl_major, cl_minor) = self.opencl_version() + if cl_major < major: + return False + if (cl_major == major) and (cl_minor <= minor): + return False + return True + + def opencl_version(self): + assert (self.device is not None) + sversion = self.device.version.strip() + _regexp='OpenCL\s+(\d)\.(\d)' + regexp=re.compile(_regexp) + match=re.match(regexp,sversion) + if not match: + msg='Could not extract OpenCL version from device returned version \'{}\' ' + msg += 'and regular expression \'{}\'.' + msg=msg.format(sversion,_regexp) + raise RuntimeError(msg) + major = match.group(1) + minor = match.group(2) + return (major,minor) + def dtype_from_str(self,stype): stype = stype.replace('ftype', self.fbtype).replace('fbtype',self.fbtype) btype = basetype(stype) -- GitLab