From ca3402de558ed7f4c44a57bd6ac7dc2eb1c112e1 Mon Sep 17 00:00:00 2001 From: Martin Bauer <martin.bauer@fau.de> Date: Tue, 15 Nov 2016 09:35:32 +0100 Subject: [PATCH] Attempt to generate kerncraft compatible code --- ast.py | 7 ++++ backends/cbackend.py | 8 +++-- cpu/kerncraft.py | 70 +++++++++++++++++++++++++++++++++++++++ cpu/kernelcreation.py | 6 ++-- gpucuda/kernelcreation.py | 2 +- llvm/kernelcreation.py | 62 ++++++++++++++++++++++++++++++++++ transformations.py | 18 ++++++---- 7 files changed, 160 insertions(+), 13 deletions(-) create mode 100644 cpu/kerncraft.py create mode 100644 llvm/kernelcreation.py diff --git a/ast.py b/ast.py index d5aa77482..54c9f0c48 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 96c9a8584..1020d2160 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 000000000..40238c8f2 --- /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 dcc3b151b..9bfd1ea9e 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 2313e518d..a443929eb 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 000000000..2fd0245a0 --- /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 6f92f08e2..fbbf2e7e5 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 -- GitLab