astnodes.py 23.4 KB
Newer Older
1
2
from typing import Any, List, Optional, Sequence, Set, Union

3
import jinja2
4
import sympy as sp
5
6

from pystencils.data_types import TypedSymbol, cast_func, create_type
7
from pystencils.field import Field
8
9
from pystencils.kernelparameters import (FieldPointerSymbol, FieldShapeSymbol,
                                         FieldStrideSymbol)
Martin Bauer's avatar
Martin Bauer committed
10
from pystencils.sympyextensions import fast_subs
Martin Bauer's avatar
Martin Bauer committed
11
12

NodeOrExpr = Union['Node', sp.Expr]
13
14


15
class Node:
Martin Bauer's avatar
Martin Bauer committed
16
    """Base class for all AST nodes."""
Martin Bauer's avatar
Martin Bauer committed
17

Martin Bauer's avatar
Martin Bauer committed
18
    def __init__(self, parent: Optional['Node'] = None):
19
20
        self.parent = parent

Martin Bauer's avatar
Martin Bauer committed
21
22
23
    @property
    def args(self) -> List[NodeOrExpr]:
        """Returns all arguments/children of this node."""
Martin Bauer's avatar
Martin Bauer committed
24
        raise NotImplementedError()
25
26

    @property
Martin Bauer's avatar
Martin Bauer committed
27
28
    def symbols_defined(self) -> Set[sp.Symbol]:
        """Set of symbols which are defined by this node."""
Martin Bauer's avatar
Martin Bauer committed
29
        raise NotImplementedError()
30
31

    @property
Martin Bauer's avatar
Martin Bauer committed
32
33
    def undefined_symbols(self) -> Set[sp.Symbol]:
        """Symbols which are used but are not defined inside this node."""
34
        raise NotImplementedError()
35

Martin Bauer's avatar
Martin Bauer committed
36
    def subs(self, subs_dict) -> None:
Martin Bauer's avatar
Martin Bauer committed
37
        """Inplace! substitute, similar to sympy's but modifies the AST inplace."""
38
        for a in self.args:
Martin Bauer's avatar
Martin Bauer committed
39
            a.subs(subs_dict)
40

41
42
43
44
    @property
    def func(self):
        return self.__class__

Martin Bauer's avatar
Martin Bauer committed
45
46
    def atoms(self, arg_type) -> Set[Any]:
        """Returns a set of all descendants recursively, which are an instance of the given type."""
47
48
        result = set()
        for arg in self.args:
Martin Bauer's avatar
Martin Bauer committed
49
            if isinstance(arg, arg_type):
50
                result.add(arg)
Martin Bauer's avatar
Martin Bauer committed
51
            result.update(arg.atoms(arg_type))
52
53
54
        return result


55
class Conditional(Node):
Martin Bauer's avatar
Martin Bauer committed
56
57
58
59
60
61
62
63
64
65
66
    """Conditional that maps to a 'if' statement in C/C++.

    Try to avoid using this node inside of loops, since currently this construction can not be vectorized.
    Consider using assignments with sympy.Piecewise in this case.

    Args:
        condition_expr: sympy relational expression
        true_block: block which is run if conditional is true
        false_block: optional block which is run if conditional is false
    """

Martin Bauer's avatar
Martin Bauer committed
67
68
    def __init__(self, condition_expr: sp.Basic, true_block: Union['Block', 'SympyAssignment'],
                 false_block: Optional['Block'] = None) -> None:
Martin Bauer's avatar
Martin Bauer committed
69
70
        super(Conditional, self).__init__(parent=None)

Martin Bauer's avatar
Martin Bauer committed
71
        self.condition_expr = condition_expr
Martin Bauer's avatar
Martin Bauer committed
72
73

        def handle_child(c):
74
75
76
77
78
79
80
            if c is None:
                return None
            if not isinstance(c, Block):
                c = Block([c])
            c.parent = self
            return c

Martin Bauer's avatar
Martin Bauer committed
81
82
        self.true_block = handle_child(true_block)
        self.false_block = handle_child(false_block)
83

Martin Bauer's avatar
Martin Bauer committed
84
85
    def subs(self, subs_dict):
        self.true_block.subs(subs_dict)
Martin Bauer's avatar
Martin Bauer committed
86
        if self.false_block:
Martin Bauer's avatar
Martin Bauer committed
87
88
            self.false_block.subs(subs_dict)
        self.condition_expr = self.condition_expr.subs(subs_dict)
89
90
91

    @property
    def args(self):
Martin Bauer's avatar
Martin Bauer committed
92
93
94
        result = [self.condition_expr, self.true_block]
        if self.false_block:
            result.append(self.false_block)
95
96
97
        return result

    @property
Martin Bauer's avatar
Martin Bauer committed
98
    def symbols_defined(self):
99
100
101
        return set()

    @property
Martin Bauer's avatar
Martin Bauer committed
102
    def undefined_symbols(self):
Martin Bauer's avatar
Martin Bauer committed
103
104
105
106
        result = self.true_block.undefined_symbols
        if self.false_block:
            result.update(self.false_block.undefined_symbols)
        result.update(self.condition_expr.atoms(sp.Symbol))
107
108
109
        return result

    def __str__(self):
Martin Bauer's avatar
Martin Bauer committed
110
        return 'if:({!s}) '.format(self.condition_expr)
111
112

    def __repr__(self):
Martin Bauer's avatar
Martin Bauer committed
113
        return 'if:({!r}) '.format(self.condition_expr)
114

115
116
117
118
119
120
121
122
    def replace_by_true_block(self):
        """Replaces the conditional by its True block"""
        self.parent.replace(self, [self.true_block])

    def replace_by_false_block(self):
        """Replaces the conditional by its False block"""
        self.parent.replace(self, [self.false_block] if self.false_block else [])

123

124
125
class KernelFunction(Node):

126
127
128
129
130
131
132
133
134
135
136
    class Parameter:
        """Function parameter.

        Each undefined symbol in a `KernelFunction` node becomes a parameter to the function.
        Parameters are either symbols introduced by the user that never occur on the left hand side of an
        Assignment, or are related to fields/arrays passed to the function.

        A parameter consists of the typed symbol (symbol property). For field related parameters this is a symbol
        defined in pystencils.kernelparameters.
        If the parameter is related to one or multiple fields, these fields are referenced in the fields property.
        """
137

138
139
140
        def __init__(self, symbol, fields):
            self.symbol = symbol  # type: TypedSymbol
            self.fields = fields  # type: Sequence[Field]
141

142
        def __repr__(self):
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
            return repr(self.symbol)

        @property
        def is_field_stride(self):
            return isinstance(self.symbol, FieldStrideSymbol)

        @property
        def is_field_shape(self):
            return isinstance(self.symbol, FieldShapeSymbol)

        @property
        def is_field_pointer(self):
            return isinstance(self.symbol, FieldPointerSymbol)

        @property
        def is_field_parameter(self):
            return self.is_field_pointer or self.is_field_shape or self.is_field_stride

        @property
        def field_name(self):
            return self.fields[0].name
164

Martin Bauer's avatar
Martin Bauer committed
165
    def __init__(self, body, ghost_layers=None, function_name="kernel", backend=""):
166
167
        super(KernelFunction, self).__init__()
        self._body = body
168
        body.parent = self
Martin Bauer's avatar
Martin Bauer committed
169
        self.function_name = function_name
170
        self._body.parent = self
Martin Bauer's avatar
Martin Bauer committed
171
        self.compile = None
Martin Bauer's avatar
Martin Bauer committed
172
        self.ghost_layers = ghost_layers
173
        # these variables are assumed to be global, so no automatic parameter is generated for them
Martin Bauer's avatar
Martin Bauer committed
174
        self.global_variables = set()
175
        self.backend = backend
Martin Bauer's avatar
Martin Bauer committed
176
        self.instruction_set = None  # used in `vectorize` function to tell the backend which i.s. (SSE,AVX) to use
177
178

    @property
Martin Bauer's avatar
Martin Bauer committed
179
    def symbols_defined(self):
180
181
182
        return set()

    @property
Martin Bauer's avatar
Martin Bauer committed
183
    def undefined_symbols(self):
184
185
186
187
188
189
        return set()

    @property
    def body(self):
        return self._body

Martin Bauer's avatar
Martin Bauer committed
190
191
192
193
194
    @body.setter
    def body(self, value):
        self._body = value
        self._body.parent = self

195
196
    @property
    def args(self):
Jan Hoenig's avatar
Jan Hoenig committed
197
        return [self._body]
198

199
    @property
200
    def fields_accessed(self) -> Set['ResolvedFieldAccess']:
201
        """Set of Field instances: fields which are accessed inside this kernel function"""
Martin Bauer's avatar
Martin Bauer committed
202
        return set(o.field for o in self.atoms(ResolvedFieldAccess))
203

204
205
206
207
208
    def get_parameters(self) -> Sequence['KernelFunction.Parameter']:
        """Returns list of parameters for this function.

        This function is expensive, cache the result where possible!
        """
209
        field_map = {f.name: f for f in self.fields_accessed}
210
211
212
213
214
215
216

        def get_fields(symbol):
            if hasattr(symbol, 'field_name'):
                return field_map[symbol.field_name],
            elif hasattr(symbol, 'field_names'):
                return tuple(field_map[fn] for fn in symbol.field_names)
            return ()
217

218
219
        argument_symbols = self._body.undefined_symbols - self.global_variables
        parameters = [self.Parameter(symbol, get_fields(symbol)) for symbol in argument_symbols]
220
221
        if hasattr(self, 'indexing'):
            parameters += [self.Parameter(s, []) for s in self.indexing.symbolic_parameters()]
222
223
        parameters.sort(key=lambda p: p.symbol.name)
        return parameters
224

225
    def __str__(self):
226
227
        params = [p.symbol for p in self.get_parameters()]
        return '{0} {1}({2})\n{3}'.format(type(self).__name__, self.function_name, params,
Michael Kuron's avatar
Michael Kuron committed
228
                                          ("\t" + "\t".join(str(self.body).splitlines(True))))
229
230

    def __repr__(self):
231
232
        params = [p.symbol for p in self.get_parameters()]
        return '{0} {1}({2})'.format(type(self).__name__, self.function_name, params)
233

234

Martin Bauer's avatar
Martin Bauer committed
235
236
237
238
239
240
241
242
243
244
245
246
247
248
class SkipIteration(Node):
    @property
    def args(self):
        return []

    @property
    def symbols_defined(self):
        return set()

    @property
    def undefined_symbols(self):
        return set()


249
class Block(Node):
Martin Bauer's avatar
Martin Bauer committed
250
251
252
    def __init__(self, nodes: List[Node]):
        super(Block, self).__init__()
        self._nodes = nodes
253
        self.parent = None
254
255
256
257
258
259
260
        for n in self._nodes:
            n.parent = self

    @property
    def args(self):
        return self._nodes

Martin Bauer's avatar
Martin Bauer committed
261
262
263
264
    def subs(self, subs_dict) -> None:
        for a in self.args:
            a.subs(subs_dict)

Martin Bauer's avatar
Martin Bauer committed
265
    def insert_front(self, node):
266
267
268
        node.parent = self
        self._nodes.insert(0, node)

Martin Bauer's avatar
Martin Bauer committed
269
270
271
    def insert_before(self, new_node, insert_before):
        new_node.parent = self
        idx = self._nodes.index(insert_before)
272
273

        # move all assignment (definitions to the top)
Martin Bauer's avatar
Martin Bauer committed
274
        if isinstance(new_node, SympyAssignment) and new_node.is_declaration:
Martin Bauer's avatar
Martin Bauer committed
275
276
277
278
279
280
            while idx > 0:
                pn = self._nodes[idx - 1]
                if isinstance(pn, LoopOverCoordinate) or isinstance(pn, Conditional):
                    idx -= 1
                else:
                    break
Martin Bauer's avatar
Martin Bauer committed
281
        self._nodes.insert(idx, new_node)
Martin Bauer's avatar
Martin Bauer committed
282

283
    def append(self, node):
284
285
286
287
288
289
290
        if isinstance(node, list) or isinstance(node, tuple):
            for n in node:
                n.parent = self
                self._nodes.append(n)
        else:
            node.parent = self
            self._nodes.append(node)
291

Martin Bauer's avatar
Martin Bauer committed
292
    def take_child_nodes(self):
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
        tmp = self._nodes
        self._nodes = []
        return tmp

    def replace(self, child, replacements):
        idx = self._nodes.index(child)
        del self._nodes[idx]
        if type(replacements) is list:
            for e in replacements:
                e.parent = self
            self._nodes = self._nodes[:idx] + replacements + self._nodes[idx:]
        else:
            replacements.parent = self
            self._nodes.insert(idx, replacements)

    @property
Martin Bauer's avatar
Martin Bauer committed
309
    def symbols_defined(self):
310
311
        result = set()
        for a in self.args:
Martin Bauer's avatar
Martin Bauer committed
312
            result.update(a.symbols_defined)
313
314
315
        return result

    @property
Martin Bauer's avatar
Martin Bauer committed
316
    def undefined_symbols(self):
317
        result = set()
Martin Bauer's avatar
Martin Bauer committed
318
        defined_symbols = set()
319
        for a in self.args:
Martin Bauer's avatar
Martin Bauer committed
320
321
            result.update(a.undefined_symbols)
            defined_symbols.update(a.symbols_defined)
Martin Bauer's avatar
Martin Bauer committed
322
        return result - defined_symbols
323

324
    def __str__(self):
Martin Bauer's avatar
Martin Bauer committed
325
        return "Block " + ''.join('{!s}\n'.format(node) for node in self._nodes)
326
327

    def __repr__(self):
Martin Bauer's avatar
Martin Bauer committed
328
        return "Block"
329

330
331

class PragmaBlock(Block):
Martin Bauer's avatar
Martin Bauer committed
332
333
    def __init__(self, pragma_line, nodes):
        super(PragmaBlock, self).__init__(nodes)
Martin Bauer's avatar
Martin Bauer committed
334
        self.pragma_line = pragma_line
Martin Bauer's avatar
Martin Bauer committed
335
        for n in nodes:
Martin Bauer's avatar
Martin Bauer committed
336
337
338
            n.parent = self

    def __repr__(self):
Martin Bauer's avatar
Martin Bauer committed
339
        return self.pragma_line
340
341
342
343


class LoopOverCoordinate(Node):
    LOOP_COUNTER_NAME_PREFIX = "ctr"
Martin Bauer's avatar
Martin Bauer committed
344
    BlOCK_LOOP_COUNTER_NAME_PREFIX = "_blockctr"
345

Martin Bauer's avatar
Martin Bauer committed
346
    def __init__(self, body, coordinate_to_loop_over, start, stop, step=1, is_block_loop=False):
Martin Bauer's avatar
Martin Bauer committed
347
        super(LoopOverCoordinate, self).__init__(parent=None)
348
        self.body = body
349
        body.parent = self
Martin Bauer's avatar
Martin Bauer committed
350
        self.coordinate_to_loop_over = coordinate_to_loop_over
351
352
353
354
        self.start = start
        self.stop = stop
        self.step = step
        self.body.parent = self
Martin Bauer's avatar
Martin Bauer committed
355
        self.prefix_lines = []
Martin Bauer's avatar
Martin Bauer committed
356
        self.is_block_loop = is_block_loop
357

Martin Bauer's avatar
Martin Bauer committed
358
    def new_loop_with_different_body(self, new_body):
Martin Bauer's avatar
Martin Bauer committed
359
360
        result = LoopOverCoordinate(new_body, self.coordinate_to_loop_over, self.start, self.stop,
                                    self.step, self.is_block_loop)
Martin Bauer's avatar
Martin Bauer committed
361
        result.prefix_lines = [l for l in self.prefix_lines]
362
363
        return result

Martin Bauer's avatar
Martin Bauer committed
364
365
    def subs(self, subs_dict):
        self.body.subs(subs_dict)
366
        if hasattr(self.start, "subs"):
Martin Bauer's avatar
Martin Bauer committed
367
            self.start = self.start.subs(subs_dict)
368
        if hasattr(self.stop, "subs"):
Martin Bauer's avatar
Martin Bauer committed
369
            self.stop = self.stop.subs(subs_dict)
370
        if hasattr(self.step, "subs"):
Martin Bauer's avatar
Martin Bauer committed
371
            self.step = self.step.subs(subs_dict)
372

373
374
    @property
    def args(self):
375
376
        result = [self.body]
        for e in [self.start, self.stop, self.step]:
Martin Bauer's avatar
Martin Bauer committed
377
378
            if hasattr(e, "args"):
                result.append(e)
379
380
        return result

381
382
383
384
385
386
387
388
389
390
    def replace(self, child, replacement):
        if child == self.body:
            self.body = replacement
        elif child == self.start:
            self.start = replacement
        elif child == self.step:
            self.step = replacement
        elif child == self.stop:
            self.stop = replacement

391
    @property
Martin Bauer's avatar
Martin Bauer committed
392
393
    def symbols_defined(self):
        return {self.loop_counter_symbol}
394
395

    @property
Martin Bauer's avatar
Martin Bauer committed
396
397
    def undefined_symbols(self):
        result = self.body.undefined_symbols
Martin Bauer's avatar
Martin Bauer committed
398
399
400
        for possible_symbol in [self.start, self.stop, self.step]:
            if isinstance(possible_symbol, Node) or isinstance(possible_symbol, sp.Basic):
                result.update(possible_symbol.atoms(sp.Symbol))
Martin Bauer's avatar
Martin Bauer committed
401
        return result - {self.loop_counter_symbol}
402

Martin Bauer's avatar
Martin Bauer committed
403
    @staticmethod
Martin Bauer's avatar
Martin Bauer committed
404
405
    def get_loop_counter_name(coordinate_to_loop_over):
        return "%s_%s" % (LoopOverCoordinate.LOOP_COUNTER_NAME_PREFIX, coordinate_to_loop_over)
Martin Bauer's avatar
Martin Bauer committed
406

Martin Bauer's avatar
Martin Bauer committed
407
408
409
410
    @staticmethod
    def get_block_loop_counter_name(coordinate_to_loop_over):
        return "%s_%s" % (LoopOverCoordinate.BlOCK_LOOP_COUNTER_NAME_PREFIX, coordinate_to_loop_over)

411
    @property
Martin Bauer's avatar
Martin Bauer committed
412
    def loop_counter_name(self):
Martin Bauer's avatar
Martin Bauer committed
413
414
415
416
        if self.is_block_loop:
            return LoopOverCoordinate.get_block_loop_counter_name(self.coordinate_to_loop_over)
        else:
            return LoopOverCoordinate.get_loop_counter_name(self.coordinate_to_loop_over)
Martin Bauer's avatar
Martin Bauer committed
417

418
    @staticmethod
Martin Bauer's avatar
Martin Bauer committed
419
    def is_loop_counter_symbol(symbol):
420
421
422
        prefix = LoopOverCoordinate.LOOP_COUNTER_NAME_PREFIX
        if not symbol.name.startswith(prefix):
            return None
Martin Bauer's avatar
Martin Bauer committed
423
        if symbol.dtype != create_type('int'):
424
            return None
Martin Bauer's avatar
Martin Bauer committed
425
        coordinate = int(symbol.name[len(prefix) + 1:])
426
427
        return coordinate

Martin Bauer's avatar
Martin Bauer committed
428
    @staticmethod
Martin Bauer's avatar
Martin Bauer committed
429
430
    def get_loop_counter_symbol(coordinate_to_loop_over):
        return TypedSymbol(LoopOverCoordinate.get_loop_counter_name(coordinate_to_loop_over), 'int')
431

Martin Bauer's avatar
Martin Bauer committed
432
433
434
435
    @staticmethod
    def get_block_loop_counter_symbol(coordinate_to_loop_over):
        return TypedSymbol(LoopOverCoordinate.get_block_loop_counter_name(coordinate_to_loop_over), 'int')

436
    @property
Martin Bauer's avatar
Martin Bauer committed
437
    def loop_counter_symbol(self):
Martin Bauer's avatar
Martin Bauer committed
438
439
440
441
        if self.is_block_loop:
            return self.get_block_loop_counter_symbol(self.coordinate_to_loop_over)
        else:
            return self.get_loop_counter_symbol(self.coordinate_to_loop_over)
442
443

    @property
Martin Bauer's avatar
Martin Bauer committed
444
    def is_outermost_loop(self):
Martin Bauer's avatar
Martin Bauer committed
445
446
        from pystencils.transformations import get_next_parent_of_type
        return get_next_parent_of_type(self, LoopOverCoordinate) is None
447
448

    @property
Martin Bauer's avatar
Martin Bauer committed
449
    def is_innermost_loop(self):
Martin Bauer's avatar
Martin Bauer committed
450
        return len(self.atoms(LoopOverCoordinate)) == 0
451

452
    def __str__(self):
Martin Bauer's avatar
Martin Bauer committed
453
454
455
        return 'for({!s}={!s}; {!s}<{!s}; {!s}+={!s})\n{!s}'.format(self.loop_counter_name, self.start,
                                                                    self.loop_counter_name, self.stop,
                                                                    self.loop_counter_name, self.step,
456
                                                                    ("\t" + "\t".join(str(self.body).splitlines(True))))
457
458

    def __repr__(self):
Martin Bauer's avatar
Martin Bauer committed
459
460
461
        return 'for({!s}={!s}; {!s}<{!s}; {!s}+={!s})'.format(self.loop_counter_name, self.start,
                                                              self.loop_counter_name, self.stop,
                                                              self.loop_counter_name, self.step)
462

463
464

class SympyAssignment(Node):
Martin Bauer's avatar
Martin Bauer committed
465
466
    def __init__(self, lhs_symbol, rhs_expr, is_const=True):
        super(SympyAssignment, self).__init__(parent=None)
Martin Bauer's avatar
Martin Bauer committed
467
        self._lhs_symbol = lhs_symbol
Martin Bauer's avatar
Martin Bauer committed
468
        self.rhs = rhs_expr
Martin Bauer's avatar
Martin Bauer committed
469
        self._is_const = is_const
Martin Bauer's avatar
Martin Bauer committed
470
471
472
473
474
475
476
477
        self._is_declaration = self.__is_declaration()

    def __is_declaration(self):
        if isinstance(self._lhs_symbol, cast_func):
            return False
        if any(isinstance(self._lhs_symbol, c) for c in (Field.Access, sp.Indexed, TemporaryMemoryAllocation)):
            return False
        return True
478
479
480

    @property
    def lhs(self):
Martin Bauer's avatar
Martin Bauer committed
481
        return self._lhs_symbol
482
483

    @lhs.setter
Martin Bauer's avatar
Martin Bauer committed
484
    def lhs(self, new_value):
Martin Bauer's avatar
Martin Bauer committed
485
        self._lhs_symbol = new_value
Martin Bauer's avatar
Martin Bauer committed
486
        self._is_declaration = self.__is_declaration()
487

Martin Bauer's avatar
Martin Bauer committed
488
489
490
    def subs(self, subs_dict):
        self.lhs = fast_subs(self.lhs, subs_dict)
        self.rhs = fast_subs(self.rhs, subs_dict)
491

492
493
    @property
    def args(self):
Martin Bauer's avatar
Martin Bauer committed
494
        return [self._lhs_symbol, self.rhs]
495
496

    @property
Martin Bauer's avatar
Martin Bauer committed
497
    def symbols_defined(self):
Martin Bauer's avatar
Martin Bauer committed
498
        if not self._is_declaration:
499
            return set()
Martin Bauer's avatar
Martin Bauer committed
500
        return {self._lhs_symbol}
501
502

    @property
Martin Bauer's avatar
Martin Bauer committed
503
    def undefined_symbols(self):
504
        result = self.rhs.atoms(sp.Symbol)
505
        # Add loop counters if there a field accesses
Martin Bauer's avatar
Martin Bauer committed
506
        loop_counters = set()
507
508
509
        for symbol in result:
            if isinstance(symbol, Field.Access):
                for i in range(len(symbol.offsets)):
Martin Bauer's avatar
Martin Bauer committed
510
511
                    loop_counters.add(LoopOverCoordinate.get_loop_counter_symbol(i))
        result.update(loop_counters)
Martin Bauer's avatar
Martin Bauer committed
512
        result.update(self._lhs_symbol.atoms(sp.Symbol))
513
514
515
        return result

    @property
Martin Bauer's avatar
Martin Bauer committed
516
    def is_declaration(self):
Martin Bauer's avatar
Martin Bauer committed
517
        return self._is_declaration
518
519

    @property
Martin Bauer's avatar
Martin Bauer committed
520
    def is_const(self):
Martin Bauer's avatar
Martin Bauer committed
521
        return self._is_const
522

Jan Hoenig's avatar
Jan Hoenig committed
523
524
    def replace(self, child, replacement):
        if child == self.lhs:
525
526
            replacement.parent = self
            self.lhs = replacement
Jan Hoenig's avatar
Jan Hoenig committed
527
528
529
530
531
532
        elif child == self.rhs:
            replacement.parent = self
            self.rhs = replacement
        else:
            raise ValueError('%s is not in args of %s' % (replacement, self.__class__))

533
    def __repr__(self):
534
        return repr(self.lhs) + " ← " + repr(self.rhs)
535

Martin Bauer's avatar
Martin Bauer committed
536
537
538
    def _repr_html_(self):
        printed_lhs = sp.latex(self.lhs)
        printed_rhs = sp.latex(self.rhs)
Martin Bauer's avatar
Martin Bauer committed
539
        return "${printed_lhs} \\leftarrow {printed_rhs}$".format(printed_lhs=printed_lhs, printed_rhs=printed_rhs)
Martin Bauer's avatar
Martin Bauer committed
540

541

Martin Bauer's avatar
Martin Bauer committed
542
class ResolvedFieldAccess(sp.Indexed):
Martin Bauer's avatar
Martin Bauer committed
543
    def __new__(cls, base, linearized_index, field, offsets, idx_coordinate_values):
544
545
        if not isinstance(base, sp.IndexedBase):
            base = sp.IndexedBase(base, shape=(1,))
Martin Bauer's avatar
Martin Bauer committed
546
        obj = super(ResolvedFieldAccess, cls).__new__(cls, base, linearized_index)
Martin Bauer's avatar
Martin Bauer committed
547
548
        obj.field = field
        obj.offsets = offsets
Martin Bauer's avatar
Martin Bauer committed
549
        obj.idx_coordinate_values = idx_coordinate_values
Martin Bauer's avatar
Martin Bauer committed
550
551
552
553
554
        return obj

    def _eval_subs(self, old, new):
        return ResolvedFieldAccess(self.args[0],
                                   self.args[1].subs(old, new),
Martin Bauer's avatar
Martin Bauer committed
555
                                   self.field, self.offsets, self.idx_coordinate_values)
Martin Bauer's avatar
Martin Bauer committed
556

Martin Bauer's avatar
Martin Bauer committed
557
558
559
560
561
    def fast_subs(self, substitutions):
        if self in substitutions:
            return substitutions[self]
        return ResolvedFieldAccess(self.args[0].subs(substitutions),
                                   self.args[1].subs(substitutions),
Martin Bauer's avatar
Martin Bauer committed
562
                                   self.field, self.offsets, self.idx_coordinate_values)
Martin Bauer's avatar
Martin Bauer committed
563
564

    def _hashable_content(self):
Martin Bauer's avatar
Martin Bauer committed
565
        super_class_contents = super(ResolvedFieldAccess, self)._hashable_content()
Martin Bauer's avatar
Martin Bauer committed
566
        return super_class_contents + tuple(self.offsets) + (repr(self.idx_coordinate_values), hash(self.field))
Martin Bauer's avatar
Martin Bauer committed
567
568

    @property
Martin Bauer's avatar
Martin Bauer committed
569
    def typed_symbol(self):
Martin Bauer's avatar
Martin Bauer committed
570
571
572
573
        return self.base.label

    def __str__(self):
        top = super(ResolvedFieldAccess, self).__str__()
Martin Bauer's avatar
Martin Bauer committed
574
        return "%s (%s)" % (top, self.typed_symbol.dtype)
Martin Bauer's avatar
Martin Bauer committed
575
576

    def __getnewargs__(self):
Martin Bauer's avatar
Martin Bauer committed
577
        return self.base, self.indices[0], self.field, self.offsets, self.idx_coordinate_values
Martin Bauer's avatar
Martin Bauer committed
578
579


580
class TemporaryMemoryAllocation(Node):
Martin Bauer's avatar
Martin Bauer committed
581
582
583
584
585
586
587
588
589
    """Node for temporary memory buffer allocation.

    Always allocates aligned memory.

    Args:
        typed_symbol: symbol used as pointer (has to be typed)
        size: number of elements to allocate
        align_offset: the align_offset's element is aligned
    """
590

Martin Bauer's avatar
Martin Bauer committed
591
    def __init__(self, typed_symbol: TypedSymbol, size, align_offset):
Martin Bauer's avatar
Martin Bauer committed
592
593
        super(TemporaryMemoryAllocation, self).__init__(parent=None)
        self.symbol = typed_symbol
594
        self.size = size
Martin Bauer's avatar
Martin Bauer committed
595
596
        self.headers = ['<stdlib.h>']
        self._align_offset = align_offset
597
598

    @property
Martin Bauer's avatar
Martin Bauer committed
599
600
    def symbols_defined(self):
        return {self.symbol}
601
602

    @property
Martin Bauer's avatar
Martin Bauer committed
603
    def undefined_symbols(self):
604
605
606
607
        if isinstance(self.size, sp.Basic):
            return self.size.atoms(sp.Symbol)
        else:
            return set()
608
609
610

    @property
    def args(self):
611
        return [self.symbol]
612

Martin Bauer's avatar
Martin Bauer committed
613
614
615
616
617
618
    def offset(self, byte_alignment):
        """Number of ELEMENTS to skip for a pointer that is aligned to byte_alignment."""
        np_dtype = self.symbol.dtype.base_type.numpy_dtype
        assert byte_alignment % np_dtype.itemsize == 0
        return -self._align_offset % (byte_alignment / np_dtype.itemsize)

619
620

class TemporaryMemoryFree(Node):
Martin Bauer's avatar
Martin Bauer committed
621
    def __init__(self, alloc_node):
Martin Bauer's avatar
Martin Bauer committed
622
        super(TemporaryMemoryFree, self).__init__(parent=None)
Martin Bauer's avatar
Martin Bauer committed
623
624
625
626
627
628
629
630
        self.alloc_node = alloc_node

    @property
    def symbol(self):
        return self.alloc_node.symbol

    def offset(self, byte_alignment):
        return self.alloc_node.offset(byte_alignment)
631
632

    @property
Martin Bauer's avatar
Martin Bauer committed
633
    def symbols_defined(self):
634
635
636
        return set()

    @property
Martin Bauer's avatar
Martin Bauer committed
637
    def undefined_symbols(self):
638
639
640
641
642
        return set()

    @property
    def args(self):
        return []
Martin Bauer's avatar
Martin Bauer committed
643
644
645
646
647


def early_out(condition):
    from pystencils.cpu.vectorization import vec_all
    return Conditional(vec_all(condition), Block([SkipIteration()]))
648
649
650
651
652
653
654
655
656
657
658


class DestructuringBindingsForFieldClass(Node):
    """
    Defines all variables needed for describing a field (shape, pointer, strides)
    """
    CLASS_TO_MEMBER_DICT = {
        FieldPointerSymbol: "data",
        FieldShapeSymbol: "shape",
        FieldStrideSymbol: "stride"
    }
659
660
661
662
663
664
    CLASS_NAME_TEMPLATE = jinja2.Template("Field<{{ dtype }}, {{ ndim }}>")

    @property
    def fields_accessed(self) -> Set['ResolvedFieldAccess']:
        """Set of Field instances: fields which are accessed inside this kernel function"""
        return set(o.field for o in self.atoms(ResolvedFieldAccess))
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684

    def __init__(self, body):
        super(DestructuringBindingsForFieldClass, self).__init__()
        self.headers = ['<Field.h>']
        self.body = body

    @property
    def args(self) -> List[NodeOrExpr]:
        """Returns all arguments/children of this node."""
        return set()

    @property
    def symbols_defined(self) -> Set[sp.Symbol]:
        """Set of symbols which are defined by this node."""
        undefined_field_symbols = {s for s in self.body.undefined_symbols
                                   if isinstance(s, (FieldPointerSymbol, FieldShapeSymbol, FieldStrideSymbol))}
        return undefined_field_symbols

    @property
    def undefined_symbols(self) -> Set[sp.Symbol]:
685
        field_map = {f.name: f for f in self.fields_accessed}
686
687
688
        undefined_field_symbols = self.symbols_defined
        corresponding_field_names = {s.field_name for s in undefined_field_symbols if hasattr(s, 'field_name')}
        corresponding_field_names |= {s.field_names[0] for s in undefined_field_symbols if hasattr(s, 'field_names')}
689
690
        return {TypedSymbol(f, self.CLASS_NAME_TEMPLATE.render(dtype=field_map[f].dtype, ndim=field_map[f].ndim) + '&')
                for f in corresponding_field_names} | \
691
692
693
694
695
696
697
698
699
700
701
702
            (self.body.undefined_symbols - undefined_field_symbols)

    def subs(self, subs_dict) -> None:
        """Inplace! substitute, similar to sympy's but modifies the AST inplace."""
        self.body.subs(subs_dict)

    @property
    def func(self):
        return self.__class__

    def atoms(self, arg_type) -> Set[Any]:
        return self.body.atoms(arg_type) | {s for s in self.symbols_defined if isinstance(s, arg_type)}