diff --git a/ast.py b/ast.py index d5aa7748281d704c5142e3448742f338ac525a4a..54c9f0c4881aea8c96d100777510ba78562e7831 100644 --- a/ast.py +++ b/ast.py @@ -334,6 +334,13 @@ class SympyAssignment(Node): @property def undefinedSymbols(self): result = self.rhs.atoms(sp.Symbol) + # Add loop counters if there a field accesses + loopCounters = set() + for symbol in result: + if isinstance(symbol, Field.Access): + for i in range(len(symbol.offsets)): + loopCounters.add(LoopOverCoordinate.getLoopCounterSymbol(i)) + result.update(loopCounters) result.update(self._lhsSymbol.atoms(sp.Symbol)) return result diff --git a/backends/cbackend.py b/backends/cbackend.py index 96c9a8584b93a83514e38018ccd747cdd57e1e15..1020d216076d4be1c420979cdfdae308db1cfdaf 100644 --- a/backends/cbackend.py +++ b/backends/cbackend.py @@ -52,9 +52,13 @@ class PrintNode(CustomCppCode): class CBackend: - def __init__(self, cuda=False): + def __init__(self, cuda=False, sympyPrinter=None): self.cuda = cuda - self.sympyPrinter = CustomSympyPrinter() + if sympyPrinter is None: + self.sympyPrinter = CustomSympyPrinter() + else: + self.sympyPrinter = sympyPrinter + self._indent = " " def __call__(self, node): diff --git a/cpu/kerncraft.py b/cpu/kerncraft.py new file mode 100644 index 0000000000000000000000000000000000000000..40238c8f27d68f783ac3966c00b4d5fd08a825a6 --- /dev/null +++ b/cpu/kerncraft.py @@ -0,0 +1,70 @@ +from pystencils.transformations import makeLoopOverDomain, typingFromSympyInspection, \ + typeAllEquations, moveConstantsBeforeLoop, getOptimalLoopOrdering +import pystencils.ast as ast +from pystencils.backends.cbackend import CBackend, CustomSympyPrinter +from pystencils import TypedSymbol + + +def createKerncraftCode(listOfEquations, typeForSymbol=None, ghostLayers=None): + """ + Creates an abstract syntax tree for a kernel function, by taking a list of update rules. + + Loops are created according to the field accesses in the equations. + + :param listOfEquations: list of sympy equations, containing accesses to :class:`pystencils.field.Field`. + Defining the update rules of the kernel + :param typeForSymbol: a map from symbol name to a C type specifier. If not specified all symbols are assumed to + be of type 'double' except symbols which occur on the left hand side of equations where the + right hand side is a sympy Boolean which are assumed to be 'bool' . + :param ghostLayers: a sequence of pairs for each coordinate with lower and upper nr of ghost layers + if None, the number of ghost layers is determined automatically and assumed to be equal for a + all dimensions + + :return: :class:`pystencils.ast.KernelFunction` node + """ + if not typeForSymbol: + typeForSymbol = typingFromSympyInspection(listOfEquations, "double") + + fieldsRead, fieldsWritten, assignments = typeAllEquations(listOfEquations, typeForSymbol) + allFields = fieldsRead.union(fieldsWritten) + + optimalLoopOrder = getOptimalLoopOrdering(allFields) + cstyleLoopOrder = list(range(len(optimalLoopOrder))) + + body = ast.Block(assignments) + code = makeLoopOverDomain(body, "kerncraft", ghostLayers=ghostLayers, loopOrder=cstyleLoopOrder) + moveConstantsBeforeLoop(code) + loopBody = code.body + + printer = CBackend(sympyPrinter=ArraySympyPrinter()) + + FIXED_SIZES = ("XS", "YS", "ZS", "E1S", "E2S") + + result = "" + for field in allFields: + sizesPermutation = [FIXED_SIZES[i] for i in field.layout] + suffix = "".join("[%s]" % (size,) for size in sizesPermutation) + result += "%s%s;\n" % (field.name, suffix) + + # add parameter definitions + for s in loopBody.undefinedSymbols: + if isinstance(s, TypedSymbol): + result += "%s %s;\n" % (s.dtype, s.name) + + for element in loopBody.args: + result += printer(element) + result += "\n" + return result + + +class ArraySympyPrinter(CustomSympyPrinter): + + def _print_Access(self, fieldAccess): + """""" + Loop = ast.LoopOverCoordinate + coordinateValues = [Loop.getLoopCounterSymbol(i) + offset for i, offset in enumerate(fieldAccess.offsets)] + coordinateValues += list(fieldAccess.index) + permutedCoordinates = [coordinateValues[i] for i in fieldAccess.field.layout] + + suffix = "".join("[%s]" % (self._print(a)) for a in permutedCoordinates) + return "%s%s" % (self._print(fieldAccess.field.name), suffix) diff --git a/cpu/kernelcreation.py b/cpu/kernelcreation.py index dcc3b151b9b07834f1847c03c8e44582f281a53b..9bfd1ea9e69b035d1ad7e07f921807f6e757b425 100644 --- a/cpu/kernelcreation.py +++ b/cpu/kernelcreation.py @@ -45,14 +45,14 @@ def createKernel(listOfEquations, functionName="kernel", typeForSymbol=None, spl readOnlyFields = set([f.name for f in fieldsRead - fieldsWritten]) body = ast.Block(assignments) - code = makeLoopOverDomain(body, functionName, iterationSlice=iterationSlice, ghostLayers=ghostLayers) + loopOrder = getOptimalLoopOrdering(allFields) + code = makeLoopOverDomain(body, functionName, iterationSlice=iterationSlice, + ghostLayers=ghostLayers, loopOrder=loopOrder) if splitGroups: typedSplitGroups = [[typeSymbol(s) for s in splitGroup] for splitGroup in splitGroups] splitInnerLoop(code, typedSplitGroups) - loopOrder = getOptimalLoopOrdering(allFields) - basePointerInfo = [['spatialInner0'], ['spatialInner1']] basePointerInfos = {field.name: parseBasePointerInfo(basePointerInfo, loopOrder, field) for field in allFields} diff --git a/gpucuda/kernelcreation.py b/gpucuda/kernelcreation.py index 2313e518d9e53f6cf97f51f755b0d74e4e9a2e08..a443929eb6ba1aaf2b7f7e94217f295c486bec39 100644 --- a/gpucuda/kernelcreation.py +++ b/gpucuda/kernelcreation.py @@ -44,7 +44,7 @@ def createCUDAKernel(listOfEquations, functionName="kernel", typeForSymbol=defau coordMapping, getCallParameters = getLinewiseCoordinates(list(fieldsRead)[0], requiredGhostLayers) allFields = fieldsRead.union(fieldsWritten) basePointerInfo = [['spatialInner0']] - basePointerInfos = {f.name: parseBasePointerInfo(basePointerInfo, [0, 1, 2], f) for f in allFields} + basePointerInfos = {f.name: parseBasePointerInfo(basePointerInfo, [2, 1, 0], f) for f in allFields} resolveFieldAccesses(code, readOnlyFields, fieldToFixedCoordinates={'src': coordMapping, 'dst': coordMapping}, fieldToBasePointerInfo=basePointerInfos) # add the function which determines #blocks and #threads as additional member to KernelFunction node diff --git a/llvm/kernelcreation.py b/llvm/kernelcreation.py new file mode 100644 index 0000000000000000000000000000000000000000..2fd0245a0d15ca4b04f5f59507a32b7ca2e04196 --- /dev/null +++ b/llvm/kernelcreation.py @@ -0,0 +1,62 @@ +import sympy as sp +from pystencils.transformations import resolveFieldAccesses, makeLoopOverDomain, typingFromSympyInspection, \ + typeAllEquations, getOptimalLoopOrdering, parseBasePointerInfo, moveConstantsBeforeLoop, splitInnerLoop +from pystencils.typedsymbol import TypedSymbol +from pystencils.field import Field +import pystencils.ast as ast + + +def createKernel(listOfEquations, functionName="kernel", typeForSymbol=None, splitGroups=(), + iterationSlice=None, ghostLayers=None): + """ + Creates an abstract syntax tree for a kernel function, by taking a list of update rules. + + Loops are created according to the field accesses in the equations. + + :param listOfEquations: list of sympy equations, containing accesses to :class:`pystencils.field.Field`. + Defining the update rules of the kernel + :param functionName: name of the generated function - only important if generated code is written out + :param typeForSymbol: a map from symbol name to a C type specifier. If not specified all symbols are assumed to + be of type 'double' except symbols which occur on the left hand side of equations where the + right hand side is a sympy Boolean which are assumed to be 'bool' . + :param splitGroups: Specification on how to split up inner loop into multiple loops. For details see + transformation :func:`pystencils.transformation.splitInnerLoop` + :param iterationSlice: if not None, iteration is done only over this slice of the field + :param ghostLayers: a sequence of pairs for each coordinate with lower and upper nr of ghost layers + if None, the number of ghost layers is determined automatically and assumed to be equal for a + all dimensions + + :return: :class:`pystencils.ast.KernelFunction` node + """ + if not typeForSymbol: + typeForSymbol = typingFromSympyInspection(listOfEquations, "double") + + def typeSymbol(term): + if isinstance(term, Field.Access) or isinstance(term, TypedSymbol): + return term + elif isinstance(term, sp.Symbol): + return TypedSymbol(term.name, typeForSymbol[term.name]) + else: + raise ValueError("Term has to be field access or symbol") + + fieldsRead, fieldsWritten, assignments = typeAllEquations(listOfEquations, typeForSymbol) + allFields = fieldsRead.union(fieldsWritten) + + readOnlyFields = set([f.name for f in fieldsRead - fieldsWritten]) + + body = ast.Block(assignments) + loopOrder = getOptimalLoopOrdering(allFields) + code = makeLoopOverDomain(body, functionName, iterationSlice=iterationSlice, + ghostLayers=ghostLayers, loopOrder=loopOrder) + + if splitGroups: + typedSplitGroups = [[typeSymbol(s) for s in splitGroup] for splitGroup in splitGroups] + splitInnerLoop(code, typedSplitGroups) + + basePointerInfo = [['spatialInner0'], ['spatialInner1']] + basePointerInfos = {field.name: parseBasePointerInfo(basePointerInfo, loopOrder, field) for field in allFields} + + resolveFieldAccesses(code, readOnlyFields, fieldToBasePointerInfo=basePointerInfos) + moveConstantsBeforeLoop(code) + + return code \ No newline at end of file diff --git a/transformations.py b/transformations.py index 6f92f08e2fffdcdf4044313b977da642575bc9e7..fbbf2e7e589ff248289ec17cc3e4316ae98e4611 100644 --- a/transformations.py +++ b/transformations.py @@ -9,7 +9,7 @@ from pystencils.slicing import normalizeSlice import pystencils.ast as ast -def makeLoopOverDomain(body, functionName, iterationSlice=None, ghostLayers=None): +def makeLoopOverDomain(body, functionName, iterationSlice=None, ghostLayers=None, loopOrder=None): """ Uses :class:`pystencils.field.Field.Access` to create (multiple) loops around given AST. :param body: list of nodes @@ -18,13 +18,16 @@ def makeLoopOverDomain(body, functionName, iterationSlice=None, ghostLayers=None :param ghostLayers: a sequence of pairs for each coordinate with lower and upper nr of ghost layers if None, the number of ghost layers is determined automatically and assumed to be equal for a all dimensions + :param loopOrder: loop ordering from outer to inner loop (optimal ordering is same as layout) :return: :class:`LoopOverCoordinate` instance with nested loops, ordered according to field layouts """ # find correct ordering by inspecting participating FieldAccesses fieldAccesses = body.atoms(Field.Access) fieldList = [e.field for e in fieldAccesses] fields = set(fieldList) - loopOrder = getOptimalLoopOrdering(fields) + + if loopOrder is None: + loopOrder = getOptimalLoopOrdering(fields) shapes = set([f.spatialShape for f in fields]) @@ -45,7 +48,7 @@ def makeLoopOverDomain(body, functionName, iterationSlice=None, ghostLayers=None currentBody = body lastLoop = None - for i, loopCoordinate in enumerate(loopOrder): + for i, loopCoordinate in enumerate(reversed(loopOrder)): if iterationSlice is None: begin = ghostLayers[loopCoordinate][0] end = shape[loopCoordinate] - ghostLayers[loopCoordinate][1] @@ -133,12 +136,13 @@ def parseBasePointerInfo(basePointerSpecification, loopOrder, field): - "<int>": specifying directly the coordinate :param basePointerSpecification: nested list with above specifications - :param loopOrder: list with ordering of loops from inner to outer + :param loopOrder: list with ordering of loops from outer to inner :param field: :return: list of tuples that can be passed to :func:`resolveFieldAccesses` """ result = [] specifiedCoordinates = set() + loopOrder = list(reversed(loopOrder)) for specGroup in basePointerSpecification: newGroup = [] @@ -461,7 +465,7 @@ def getOptimalLoopOrdering(fields): Determines the optimal loop order for a given set of fields. If the fields have different memory layout or different sizes an exception is thrown. :param fields: sequence of fields - :return: list of coordinate ids, where the first list entry should be the innermost loop + :return: list of coordinate ids, where the first list entry should be the outermost loop """ assert len(fields) > 0 refField = next(iter(fields)) @@ -473,7 +477,7 @@ def getOptimalLoopOrdering(fields): if len(layouts) > 1: raise ValueError("Due to different layout of the fields no optimal loop ordering exists " + str(layouts)) layout = list(layouts)[0] - return list(reversed(layout)) + return list(layout) def getLoopHierarchy(astNode): @@ -487,4 +491,4 @@ def getLoopHierarchy(astNode): node = getNextParentOfType(node, ast.LoopOverCoordinate) if node: result.append(node.coordinateToLoopOver) - return result \ No newline at end of file + return reversed(result) \ No newline at end of file