forked from shoaibkamil/asp
-
Notifications
You must be signed in to change notification settings - Fork 1
/
stencil_kernel.py
259 lines (215 loc) · 8.79 KB
/
stencil_kernel.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
import numpy
import inspect
from stencil_grid import *
from stencil_struct import *
import asp.codegen.python_ast as ast
import asp.codegen.cpp_ast as cpp_ast
import asp.codegen.ast_tools as ast_tools
from asp.kernel import Kernel
from asp.util import *
class StencilKernel(Kernel):
def __init__(self, asp_module_factory=None):
# we want to raise an exception if there is no kernel()
# method defined.
try:
dir(self).index("kernel")
except ValueError:
raise Exception("No kernel method defined.")
# if the method is defined, let us introspect and find
# its AST
self.kernel_src = inspect.getsource(self.kernel)
self.kernel_ast = ast.parse(self.remove_indentation(self.kernel_src))
self.pure_python = False
self.pure_python_kernel = self.kernel
# replace kernel with shadow version
self.kernel = self.shadow_kernel
if asp_module_factory is None:
from asp.jit import asp_module
self.asp_module_factory = asp_module.ASPModule
else:
self.asp_module_factory = asp_module_factory
def remove_indentation(self, src):
return src.lstrip()
def add_libraries(self, mod):
"""Adds the necessary gcc includes, headers, and initialization calls
to use the numpy C API.
"""
if not StencilKernel.is_initialized(mod):
mod.add_library('numpy', [numpy.get_include() + '/numpy'])
mod.add_header('arrayobject.h')
mod.add_to_init([cpp_ast.Statement('import_array()')])
StencilKernel.mark_initialized(mod)
def shadow_kernel(self, *args):
if self.pure_python:
return self.pure_python_kernel(*args)
#FIXME: need to somehow match arg names to args
argnames = map(lambda x: str(x.id), self.kernel_ast.body[0].args.args)
argdict = dict(zip(argnames[1:], args))
#debug_print('Kernel arguments:\n' + str(argdict))
phase2 = StencilKernel.StencilProcessAST(argdict).visit(self.kernel_ast)
debug_print('Phase 2 AST:\n' + ast.dump(phase2))
phase3 = StencilKernel.StencilConvertAST(argdict).visit(phase2)
mod = self.asp_module_factory()
self.add_libraries(mod)
mod.add_function(phase3)
grid_data = [grid.data for grid in args]
mod.kernel(*grid_data)
# the actual Stencil AST Node
class StencilInteriorIter(ast.AST):
def __init__(self, grid, body, target):
self.grid = grid
self.body = body
self.target = target
self._fields = ('grid', 'body', 'target')
super(StencilKernel.StencilInteriorIter, self).__init__()
class StencilNeighborIter(ast.AST):
def __init__(self, grid, body, target, dist):
self.grid = grid
self.body = body
self.target = target
self.dist = dist
self._fields = ('grid', 'body', 'target', 'dist')
super (StencilKernel.StencilNeighborIter, self).__init__()
# separate files for different architectures
# class to convert from Python AST to an AST with special Stencil node
class StencilProcessAST(ast.NodeTransformer):
def __init__(self, argdict):
self.argdict = argdict
super(StencilKernel.StencilProcessAST, self).__init__()
def visit_For(self, node):
debug_print("visiting a For...\n")
# check if this is the right kind of For loop
if (node.iter.__class__.__name__ == "Call" and
node.iter.func.__class__.__name__ == "Attribute"):
debug_print("Found something to change...\n")
if (node.iter.func.attr == "interior_points"):
grid = self.visit(node.iter.func.value).id # do we need the name of the grid, or the obj itself?
target = self.visit(node.target)
body = map(self.visit, node.body)
newnode = StencilKernel.StencilInteriorIter(grid, body, target)
return newnode
elif (node.iter.func.attr == "neighbors"):
debug_print(ast.dump(node) + "\n")
target = self.visit(node.target)
body = map(self.visit, node.body)
grid = self.visit(node.iter.func.value).id
dist = self.visit(node.iter.args[1]).n
newnode = StencilKernel.StencilNeighborIter(grid, body, target, dist)
return newnode
else:
return node
else:
return node
class StencilConvertAST(ast_tools.ConvertAST):
def __init__(self, argdict):
self.argdict = argdict
self.dim_vars = []
super(StencilKernel.StencilConvertAST, self).__init__()
def gen_array_macro_definition(self, arg):
try:
array = self.argdict[arg]
defname = "_"+arg+"_array_macro"
params = "(" + ','.join(["_d"+str(x) for x in xrange(array.dim)]) + ")"
calc = "(_d%s)" % str(array.dim-1)
for x in range(1,array.dim):
calc = "(%s + %s * (_d%s))" % (calc, str(array.shape[x-1]), str(array.dim-x-1))
return cpp_ast.Define(defname+params, calc)
except KeyError:
return cpp_ast.Comment("Not found argument: " + arg)
def gen_array_macro(self, arg, point):
macro = "_%s_array_macro(%s)" % (arg, ",".join(map(str, point)))
return macro
def gen_dim_var(self, dim):
"""
Deterministically generates free variable names for loops over the given
dimension of the stencil grid. This method generates names like '_i',
'_j', and so on, wrapping around to '_ia', '_ib', etc... if necessary.
"""
characters, offset, dim = [], ord('i') - 1, int(dim + 1)
while dim > 0:
quotient, remainder = divmod(dim, ord('z') - offset + 1)
characters.append(chr(offset + remainder))
dim = quotient
offset = ord('a') - 1
variable = '_' + ''.join(characters)
self.dim_vars.append(variable)
return variable
def gen_array_unpack(self, grid_name, type_name=None):
type_name = 'double' if type_name is None else type_name
array_decl = "{0} *_my_{1} = ({0} *) PyArray_DATA({1})"
return cpp_ast.Statement(array_decl.format(type_name, grid_name))
# all arguments are PyObjects
def visit_arguments(self, node):
return [cpp_ast.Pointer(cpp_ast.Value("PyObject", self.visit(x))) for x in node.args[1:]]
def visit_StencilInteriorIter(self, node):
# should catch KeyError here
array = self.argdict[node.grid]
dim = len(array.shape)
# if dim == 2:
# dim1_var = self.gen_dim_var(0)
# dim2_var = self.gen_dim_var(1)
# start1 = "int %s = %s" % (dim1_var, str(array.ghost_depth))
# condition1 = "%s < %s" % (dim1_var, str(array.shape[0]-array.ghost_depth))
# update1 = "%s++" % dim1_var
# start2 = "int %s = %s" % (dim2_var, str(array.ghost_depth))
# condition2 = "%s < %s" % (dim2_var, str(array.shape[1]-array.ghost_depth))
# update2 = "%s++" % dim2_var
definitions = cpp_ast.Module()
# Define macros for linearizing n-D array indices.
definitions.extend(self.gen_array_macro_definition(v)
for v in self.argdict)
# If the programmer is using structs as stencil node values, generate
# struct definitions. Map data types to their names.
type_names, stencil_types = {}, {}
for variable, grid in self.argdict.items():
if grid.dtype not in type_names:
type_name, declarator = cpp_ast.cpp_type(grid.dtype)
type_names[grid.dtype] = type_name
if declarator is not None:
definitions.append(declarator)
stencil_types[variable] = type_names[grid.dtype]
# Add the array declarations.
for variable in self.argdict:
type_name = stencil_types[variable]
definitions.append(self.gen_array_unpack(variable, type_name))
# Iteratively generate the contents of the top-level loop.
loop_node = None
for d in xrange(dim):
dim_var = self.gen_dim_var(d)
start = "int %s = %s" % (dim_var, str(array.ghost_depth))
condition = "%s < %s" % (dim_var, str(array.shape[d]-array.ghost_depth))
update = "%s++" % dim_var
if d == 0:
loop_node = cpp_ast.For(start, condition, update, cpp_ast.Block())
cur_node = loop_node
else:
cur_node.body = cpp_ast.For(start, condition, update, cpp_ast.Block())
cur_node = cur_node.body
body = cpp_ast.Block()
body.append(cpp_ast.Value("int", self.visit(node.target)))
body.append(cpp_ast.Assign(self.visit(node.target),
self.gen_array_macro(node.grid, self.dim_vars)))
replaced_body = None
for gridname in self.argdict:
replacer = ast_tools.ASTNodeReplacer(ast.Name(gridname, None),
ast.Name('_my_' + gridname, None))
replaced_body = [replacer.visit(n) for n in node.body]
body.extend(self.visit(n) for n in replaced_body)
cur_node.body = body
ret_node = definitions
ret_node.append(cpp_ast.Pragma('omp parallel for'))
ret_node.append(loop_node)
return ret_node
def visit_StencilNeighborIter(self, node):
block = cpp_ast.Block()
target = self.visit(node.target)
block.append(cpp_ast.Value("int", target))
grid = self.argdict[node.grid]
debug_print(node.dist)
for neighbor in grid.neighbor_definition[node.dist]:
point = ['(%s + %d)' % (v, n) for v, n in zip(self.dim_vars, neighbor)]
array_index = self.gen_array_macro(node.grid, point)
block.append(cpp_ast.Assign(target, array_index))
block.extend([self.visit(z) for z in node.body])
debug_print('StencilNeighborIter block:\n' + str(block))
return block