Advertisement
EelcoHoogendoorn

numpycuda_parsing.py

Aug 28th, 2012
175
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 13.08 KB | None | 0 0
  1.  
  2. """
  3. module containing all the pyparsing stuff
  4.  
  5.  
  6. """
  7.  
  8. import numpy as np
  9.  
  10. from pyparsing import *
  11.  
  12. import pycuda.tools
  13. dtype_to_ctype = {k:v for k,v in pycuda.compyte.dtypes.DTYPE_TO_NAME.iteritems() if isinstance(k, str)}
  14.  
  15.  
  16.  
  17. #some general grammar definitions
  18.  
  19. dtype_term = oneOf(' '.join(dtype_to_ctype.keys())).setResultsName('dtype')
  20.  
  21.  
  22. identifier = Word(alphas+'_', alphanums+'_').setResultsName('identifier')
  23. dummy = Word(alphas.lower(),exact=1)
  24.  
  25.  
  26. def sign_wrap(expr): return Combine(Optional(Literal('-')) + expr)
  27. positive_integer = Word(nums)
  28. integer = sign_wrap(positive_integer)
  29.  
  30. positive_floating = Combine( Optional( positive_integer) + '.' + Optional(positive_integer))
  31.  
  32. floating = sign_wrap(positive_floating)
  33.  
  34. number = Or([integer, floating])
  35.  
  36. colon = Literal(':')
  37.  
  38. dimension = Or([positive_integer, colon, dummy])
  39. shape_expression  = nestedExpr('[',']',     delimitedList(dimension) ).setResultsName('shape')
  40. input_argument = dtype_term + Optional(shape_expression).setResultsName('shape') + identifier
  41.  
  42. default_value = Suppress( Literal('=')) + number.setResultsName('default')
  43. dimension = Or([positive_integer, dummy])
  44. shape_expression  = nestedExpr('[',']',     delimitedList(dimension) ).setResultsName('shape')
  45. output_argument = input_argument + Optional(default_value)
  46.  
  47. def argument_list(argument): return nestedExpr(content= delimitedList(Group( argument)))
  48. decl_grammar = argument_list(output_argument).setResultsName('outputs') +'<<' + shape_expression + '<<' + argument_list(input_argument).setResultsName('inputs') + colon
  49.  
  50.  
  51.  
  52.  
  53.  
  54.  
  55.  
  56.  
  57. class ArgumentDeclaration(object):
  58.  
  59.     """argument declaration"""
  60.     def __init__(self, identifier, dtype, shape = ()):
  61.         self.identifier = identifier
  62.         self.shape = shape
  63.         self.dtype = dtype
  64.  
  65.     @property
  66.     def ctype(self): return dtype_to_ctype[self.dtype]
  67.     @property
  68.     def is_scalar(self): return self.ndim == 0
  69.     @property
  70.     def is_array(self): return not self.is_scalar
  71.     @property
  72.     def ndim(self): return len(self.shape)
  73.  
  74.     @property
  75.     def argument_string(self):
  76.         base_argument = '{mutable}{type}{ptr} const {restrict}{identifier}'
  77.         shape_argument = 'unsigned const {identifier}_shape_{dimension}'
  78.         return ', '.join(
  79.                 [base_argument.format(mutable='const '*self.immutable, type=self.ctype, ptr='*'*self.is_array, restrict='__restrict__ '*self.is_array, identifier=self.identifier)]
  80.                 +
  81.                 [shape_argument.format(identifier=self.identifier, dimension=dimension)
  82.                     for dimension, size in enumerate(self.shape) if size == ':']
  83.             )
  84.     @property
  85.     def shape_string(self):
  86.         shape_argument = 'unsigned const {identifier}_shape_{dimension} = {size};'
  87.         constant_shape_arguments = [
  88.             shape_argument.format(identifier=self.identifier, dimension=dimension, size = size if isinstance(size, np.uint32) else 'dummy_{}'.format(size))
  89.                 for dimension, size in enumerate(self.shape) if not size == ':']
  90.         return '\n'.join(constant_shape_arguments)
  91.     @property
  92.     def stride_string(self):
  93.         def terms():
  94.             stride_template  = '{identifier}_stride_{dimension}'
  95.             shape_template = '{identifier}_shape_{dimension}'
  96.             prev = stride_template.format(identifier=self.identifier, dimension=len(self.shape)-1)
  97.             yield 'unsigned const {identifier} = {stride};'.format(identifier = prev ,stride = 1)
  98.             for i, size in reversed(list(enumerate( self.shape[:-1]))):
  99.                 this = stride_template.format(identifier=self.identifier, dimension=i)
  100.                 size = shape_template.format(identifier=self.identifier, dimension=i+1)
  101.                 yield 'unsigned const {this} = {prev} * {size};'.format(this=this, prev=prev ,size = size)
  102.                 prev = this
  103.             #add total element size as well, for good measure, even though not used anywhere atm
  104.             size = shape_template.format(identifier=self.identifier, dimension=0)
  105.             yield 'unsigned const {identifier}_size = {prev} * {size};'.format(identifier=self.identifier, prev=prev ,size=size)
  106.         return '\n'.join(term for term in terms())
  107.  
  108.  
  109. class InputDeclaration(ArgumentDeclaration):
  110.     immutable = True
  111. class OutputDeclaration(ArgumentDeclaration):
  112.     immutable = False
  113.  
  114.     def __init__(self, identifier, dtype, default = None, shape = None):
  115.         self.identifier = identifier
  116.         self.dtype = dtype
  117.         self.shape = (np.uint32(1),) if not shape else shape   #scalar outputs are upcast to singleton arrays
  118.         self.default = getattr(np, dtype)( default) if default else default
  119.  
  120.  
  121. from collections import OrderedDict
  122. class KernelDeclaration(object):
  123.     """
  124.    holds all info defining a kernel declaration
  125.    plus data structures to facilitate runtime argument parsing
  126.    at runtime, create dummy dict
  127.    as we scan argument (value, decl) pairs, we check dummy[decl.shape[i]]==value.shape[i]
  128.    if not set, set it
  129.    this gives dummy:size dict we can add to kwargs
  130.    when we concat all colons and dummies, we should have sufficient arguments
  131.  
  132.    build list of expected arguments?
  133.    """
  134.     def __init__(self, decl):
  135.         #store pyparsing result in a format conductive to further processing
  136.         self.shape =  decl.shape
  137.         self.inputs  = [InputDeclaration(**dict(arg)) for arg in decl.inputs[0]]
  138.         self.outputs = [OutputDeclaration(**dict(arg)) for arg in decl.outputs[0]]
  139.  
  140.         self.dummies = set()
  141.         def shape_scrubbing(terms):
  142.             """postprocess shape objects"""
  143.             for term in terms:
  144.                 try:
  145.                     shape = term.shape[0]        #take root of nestedexpr
  146.                     newshape = []
  147.                     for size in shape:
  148.                         try:
  149.                             #dimension known at compile time
  150.                             size = np.uint32(size)
  151.                         except:
  152.                             #runtime specified dimension
  153.                             if size == ':':
  154.                                 pass
  155.                             else:
  156.                                 self.dummies.add(size)
  157.                         newshape.append(size)
  158.  
  159.                     term.shape = tuple(newshape)
  160.                 except:
  161.                     pass
  162.         shape_scrubbing([self])
  163.         shape_scrubbing(self.inputs)
  164.         shape_scrubbing(self.outputs)
  165.  
  166.         self.arguments = OrderedDict()
  167.         for arg in self.inputs:
  168.             self.arguments[arg.identifier] = arg
  169.         for arg in self.outputs:        #overwrite in/out params with their output version; that is as planned, but need a consistency check here
  170.             self.arguments[arg.identifier] = arg
  171.  
  172.  
  173.     @property
  174.     def identifiers(self):
  175.         return self.arguments.keys()
  176.  
  177.     @property
  178.     def dummy_string(self):
  179.         return ', '.join('unsigned const dummy_{}'.format(dummy) for dummy in self.dummies)
  180.     @property
  181.     def argument_string(self):
  182.         args = [arg.argument_string for arg in self.arguments.itervalues()]
  183.         dummy = self.dummy_string
  184.         return ',\n'.join(args + [dummy])
  185.     @property
  186.     def shape_string(self):
  187.         shape_argument = 'unsigned const kernel_shape_{dimension} = {size};'
  188.         shape_arguments = [
  189.             shape_argument.format(dimension=dimension, size = size if isinstance(size, np.uint32) else 'dummy_{}'.format(size))
  190.                 for dimension, size in enumerate(self.shape) if not size == ':'] #either from constant or dummy
  191.         return '\n'.join(shape_arguments)
  192.     @property
  193.     def init_string(self):
  194.         kernel =  self.shape_string
  195.         shapes =  '\n'.join([arg.shape_string  for arg in self.arguments.itervalues() if arg.is_array])
  196.         strides = '\n'.join([arg.stride_string for arg in self.arguments.itervalues() if arg.is_array])
  197.         return '\n\n'.join([kernel, shapes, strides])
  198.  
  199. def parsing(source):
  200.     """split source into declaration and body"""
  201.     grammar = Group(decl_grammar       ).setResultsName('decl') + \
  202.               Word(printables+' \t\r\n').setResultsName('body')
  203.  
  204.     r = grammar.parseString(source)
  205.  
  206.     decl = KernelDeclaration(r.decl)
  207.  
  208.     body = r.body
  209.  
  210.     #proceed to transform the body according to the declaration
  211.     body = replace_typing(body)
  212.     body = replace_shape_syntax( body, decl )
  213.     body = replace_array_syntax( body, decl )
  214.     body = replace_for_syntax(body, decl)
  215.  
  216.     return decl, body
  217.  
  218.  
  219.  
  220.  
  221.  
  222.  
  223.  
  224. def replace_typing(source):
  225.     """
  226.    replace numpy types with c-types. this could be more efficient and intelligent...
  227.    we do not do any semantic analysis here; simple find and replace
  228.    but that should suffice, no?
  229.    """
  230.     type_grammar = dtype_term.copy()
  231.     type_grammar.setParseAction(lambda s,l,t: dtype_to_ctype[t[0]])
  232.     return type_grammar.transformString(source)
  233.  
  234.  
  235. def replace_shape_syntax(source, decl):
  236.     """
  237.    replace arrayidentifier.shape[ndim] syntax with C named variables
  238.    silently fails to replace some wrong syntax, like misspelled shape;
  239.    dont worry, the cuda compiler is sure to complain about it :)
  240.    would it be sufficient and currect to catch all instances of 'arrayidentifier.'+whatever,
  241.    that fail to match the whole syntax?
  242.    """
  243.     arrayidentifier = (Word(alphanums+'_')).setResultsName('identifier') # + Optional( Word(alphanums))
  244.     positive_integer = Word(nums)
  245.     shape_expr = arrayidentifier + Suppress( Literal('.shape')) + nestedExpr('[',']', positive_integer).setResultsName('dimension')
  246.  
  247.     def replace(s,l,t):
  248.         """if match is correct, replace numpy syntax with c-compatible syntax"""
  249.         identifier = t.identifier
  250.         dimensions = t.dimension[0]
  251.         if not len(dimensions)==1: raise Exception('only simple shape indexing allows')
  252.         dimension = dimensions[0]
  253.         try:
  254.             arg = decl.arguments[identifier]
  255.         except KeyError:
  256.             raise ParseFatalException("array '{identifier}' is not defined".format(identifier=identifier))
  257.         try:
  258.             size = arg.shape[int(dimension)]
  259.         except Exception:
  260.             raise ParseFatalException('{identifier}.shape[{dimension}] is invalid'.format(identifier=identifier, dimension=dimension))
  261.  
  262.         return '{identifier}_shape_{dimension}'.format(identifier=identifier, dimension=dimension)
  263.     shape_expr.setParseAction(replace)
  264.  
  265.     return shape_expr.transformString(source)
  266.  
  267.  
  268. def replace_array_syntax(source, decl):
  269.     """
  270.    replace weave.blitz style array indexing with inner product over strides
  271.    we could optionally insert bounds checking code here as well, as a debugging aid
  272.    should we allow for partial indexing? not sure; disallowed atm
  273.    """
  274.     arrayidentifier = oneOf(' '.join(decl.identifiers)).setResultsName('identifier')
  275.     index = Or([identifier, positive_integer])
  276.     index_expr = arrayidentifier + nestedExpr('(',')', delimitedList( index)).setResultsName('indices')
  277.  
  278.     def replace(s,l,t):
  279.         """if match is correct, replace numpy syntax with c-compatible syntax"""
  280.         identifier = t.identifier
  281.         indices = t.indices[0]
  282.  
  283.         try:
  284.             arg = decl.arguments[identifier]
  285.         except KeyError:
  286.             raise ParseFatalException("array '{identifier}' is not defined".format(identifier=identifier))
  287.  
  288.         if not len(indices)==arg.ndim:
  289.             raise Exception("indexing '{identifier}' requires {ndim} arguments".format(identifier=identifier, ndim=arg.ndim))
  290.  
  291.  
  292.         offset = '+'.join(
  293.             '{identifier}_stride_{i}*{idx}'.format(identifier=identifier, i=i, idx=idx)
  294.                 for i,idx in enumerate(indices))
  295.         return '{identifier}[{offset}]'.format(identifier=identifier, offset=offset)
  296.     index_expr.setParseAction(replace)
  297.  
  298.     return index_expr.transformString(source)
  299.  
  300. def replace_for_syntax(source, arg_info):
  301.     """
  302.    replace: 'for (id in start:stop:step)'
  303.    with:    'for (int id=start; start<end; id+=step)'
  304.    rather trivial syntactic sugar indeed
  305.    we could implement an unrolling mechanism here too,
  306.    in case all params are known at compile time, and loop is small?
  307.    """
  308.  
  309.     index = Or([sign_wrap(identifier), integer])
  310.     colon = Suppress(Literal(':'))
  311.     range = index.setResultsName('start') + colon + index.setResultsName('stop') + Optional(Combine(colon + index), '1').setResultsName('step')
  312.     loop_expr = Literal('for') + '(' + identifier.setResultsName('index') + Literal('in') + range + ')'
  313.  
  314.     def replace(s,l,t):
  315.         return 'for (int {index}={start}; {index} < {stop}; {index}+={step})'.format(**dict(t))
  316.     loop_expr.setParseAction(replace)
  317.  
  318.     return loop_expr.transformString(source)
  319.  
  320.  
  321.  
  322. def replace_output_reference_syntax(source):
  323.     """
  324.    scalar output args are passed by reference; they are actually shape=(1,) arrays
  325.    we might need them by reference for atomic operations
  326.    perhaps just add a & in those cases; treat it as a value type by placing a * everywhere
  327.    in the body of code. does cuda C handle &*ptr==ptr correctly?
  328.    """
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement