Skip to content

Commit f2815a7

Browse files
committed
misc: cleanup and add comments
1 parent 18b43da commit f2815a7

7 files changed

Lines changed: 234 additions & 184 deletions

File tree

devito/ir/clusters/algorithms.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from devito.tools import (
2121
DefaultOrderedDict, Stamp, as_mapper, flatten, is_integer, split, timed_pass, toposort
2222
)
23-
from devito.types import Array, Eq, Symbol, Temp
23+
from devito.types import Array, ConditionalDimension, Eq, Symbol, Temp
2424
from devito.types.dimension import BOTTOM, ModuloDimension
2525

2626
__all__ = ['clusterize']
@@ -260,11 +260,12 @@ def guard(clusters):
260260
# the purpose of protecting from OOB accesses
261261
cds = [d for d in cds if not d.indirect]
262262
modes = [cd.relation for cd in cds]
263-
if modes.count('strict') > 1:
263+
strict = ConditionalDimension._STRICT
264+
if modes.count(strict) > 1:
264265
raise CompilationError("Only one `strict` condition"
265266
"can be used in an equation")
266-
elif 'strict' in modes:
267-
mode = 'strict'
267+
elif strict in modes:
268+
mode = strict
268269
else:
269270
mode = sympy.And if sympy.And in modes else sympy.Or
270271

@@ -302,7 +303,7 @@ def guard(clusters):
302303

303304
# Combination `mode` is And by default.
304305
# If all conditions are Or then Or combination `mode` is used.
305-
if mode == 'strict':
306+
if mode == strict:
306307
guards = {d: v[0] for d, v in guards.items()}
307308
else:
308309
guards = {d: mode(*v, evaluate=False) for d, v in guards.items()}

devito/ir/equations/algorithms.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@ def generate_conditionals(expr, input_expr, ordering):
356356
for d in ordering:
357357
if not d.is_Conditional:
358358
continue
359+
359360
if d.condition is None:
360361
conditionals[d] = GuardFactor(d)
361362
else:
@@ -364,16 +365,16 @@ def generate_conditionals(expr, input_expr, ordering):
364365
cond = d.relation(cond, GuardFactor(d))
365366
conditionals[d] = cond
366367

367-
# Merge conditionals when possible. E.g if we have an implicit_dim
368-
# and there is a dimension with the same parent, we can merge
369-
# their conditions
368+
# Merge conditionals when possible. E.g., if an implicit_dim shares
369+
# its parent Dimension with another ConditionalDimension, the two
370+
# conditions can be merged into a single guard.
370371
for d in input_expr.implicit_dims:
371372
if d not in conditionals:
372373
continue
373374
for cd in list(conditionals):
374-
if cd.parent == d.parent and cd != d:
375+
if cd.parent == d.parent and cd is not d:
375376
cond = conditionals.pop(d)
376-
if d.relation == 'strict':
377+
if d.relation == ConditionalDimension._STRICT:
377378
conditionals[cd] = conditionals[d] = cond
378379
else:
379380
mode = cd.relation and d.relation
@@ -384,7 +385,7 @@ def generate_conditionals(expr, input_expr, ordering):
384385
for d, cond in conditionals.items():
385386
# Replace dimension with index
386387
index = d.index
387-
if d.condition is not None and d in expr.free_symbols:
388+
if d.condition is not None and expr.has(d):
388389
index = index - relational_min(cond, d.parent)
389390
shift = relational_shift(cond, d.parent)
390391
expr = uxreplace(expr, {d: IntDiv(index, d.symbolic_factor) + shift})

devito/ir/support/guards.py

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -32,34 +32,6 @@
3232
]
3333

3434

35-
@singledispatch
36-
def bound_index(expr, dim, dir):
37-
if dir == Forward:
38-
return expr._subs(dim, dim + 1)
39-
else:
40-
return expr._subs(dim, dim - 1)
41-
42-
43-
@bound_index.register(Expr)
44-
def _(expr, dim, dir):
45-
if not expr.args:
46-
if dir == Forward:
47-
return expr._subs(dim, dim + 1)
48-
else:
49-
return expr._subs(dim, dim - 1)
50-
return expr.func(*[bound_index(a, dim, dir) for a in expr.args])
51-
52-
53-
@bound_index.register(IntDiv)
54-
def _(expr, dim, dir):
55-
v = dim.symbolic_factor
56-
p0 = dim.root
57-
if dir == Forward:
58-
return Mul((((p0 + 1) + v - 1) / v), v, evaluate=False)
59-
else:
60-
return (p0 - 1) - abs(p0 - 1) % v
61-
62-
6335
class AbstractGuard:
6436
pass
6537

@@ -176,7 +148,7 @@ def __new__(cls, d, index, direction,
176148
assert isinstance(direction, IterationDirection)
177149

178150
# Always take the next index in the iteration direction
179-
next_index = bound_index(index, d, direction)
151+
next_index = eval_next_index(index, d, direction)
180152

181153
# The direction might be forward but accessing c - d
182154
# making the access backward w.r.t
@@ -601,3 +573,39 @@ def pairwise_or(*guards):
601573
@_uxreplace_handle.register(BaseGuardBoundNext)
602574
def _(expr, args, kwargs):
603575
return expr.func(expr.d, expr.index, expr.direction, **kwargs)
576+
577+
578+
@singledispatch
579+
def eval_next_index(expr, dim, dir):
580+
"""
581+
Evaluate `expr` at the next iteration point along `dim` in the given
582+
`dir`-ection. The "next" point is obtained by substituting `dim` with
583+
`dim + 1` for `Forward` and `dim - 1` for `Backward`.
584+
585+
For `IntDiv` expressions encoding subsampling (`dim.root // factor`),
586+
the result is rounded to the next valid coarse-grained slot.
587+
"""
588+
if dir == Forward:
589+
return expr._subs(dim, dim + 1)
590+
else:
591+
return expr._subs(dim, dim - 1)
592+
593+
594+
@eval_next_index.register(Expr)
595+
def _(expr, dim, dir):
596+
if not expr.args:
597+
if dir == Forward:
598+
return expr._subs(dim, dim + 1)
599+
else:
600+
return expr._subs(dim, dim - 1)
601+
return expr.func(*[eval_next_index(a, dim, dir) for a in expr.args])
602+
603+
604+
@eval_next_index.register(IntDiv)
605+
def _(expr, dim, dir):
606+
v = dim.symbolic_factor
607+
p0 = dim.root
608+
if dir == Forward:
609+
return Mul((((p0 + 1) + v - 1) / v), v, evaluate=False)
610+
else:
611+
return (p0 - 1) - abs(p0 - 1) % v

devito/passes/clusters/asynchrony.py

Lines changed: 89 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -15,60 +15,6 @@
1515
__all__ = ['memcpy_prefetch', 'tasking']
1616

1717

18-
@singledispatch
19-
def next_index(expr, dim, dir):
20-
return expr._subs(dim, dim + dir)
21-
22-
23-
@next_index.register(Expr)
24-
def _(expr, dim, dir):
25-
if not expr.args:
26-
return expr._subs(dim, dim + dir)
27-
return expr.func(*[next_index(a, dim, dir) for a in expr.args])
28-
29-
30-
@next_index.register(IntDiv)
31-
def _(expr, dim, dir):
32-
"""
33-
Handle forward and backward fetches separately to handle non-canonical index
34-
expressions of the form:
35-
36-
t//factor + cond(t)
37-
38-
where ``cond(t)`` is a piecewise correction term.
39-
40-
The forward fetch advances to the next coarse-grained slot while evaluating
41-
the correction at the next time point:
42-
43-
t//factor + cond(t)
44-
-> (t//factor + 1) + cond(t + 1)
45-
46-
The backward fetch is not, in general, the inverse transformation obtained by
47-
replacing ``+1`` with ``-1``. The correction may already be applied at the
48-
current time point, causing the forward and backward fetches to be asymmetric.
49-
50-
For example, with ``factor=2`` and ``cond(t) := (t == a)``, the index at
51-
``t=a=3`` is:
52-
53-
3//2 + 1 = 2
54-
55-
while the previous index is:
56-
57-
2//2 + 0 = 1
58-
59-
A symmetric backward transformation would instead yield:
60-
61-
3//2 - 1 + 0 = 0
62-
"""
63-
if expr.lhs._defines & dim._defines:
64-
if dir == 1:
65-
return expr + dir
66-
else:
67-
return expr._subs(dim, dim + dir)
68-
else:
69-
return expr
70-
71-
7218
def async_trigger(c, dims):
7319
"""
7420
Return the Dimension in `c`'s IterationSpace that triggers the
@@ -134,11 +80,15 @@ def callback(self, clusters, prefix):
13480
if d is not dim:
13581
continue
13682
g = c0.guards.get(d)
137-
if g is not None and (not g.has(Mod) and d in retrieve_terminals(g)) \
138-
and not wraps_memcpy(c0):
139-
# Explicit compute guards need no pipeline; memcpy clusters
140-
# still need WithLock for the copy-back sync
141-
continue
83+
# Explicit compute guards need no pipeline; memcpy clusters
84+
# still need WithLock for the copy-back sync
85+
if g is not None and not wraps_memcpy(c0):
86+
# An "explicit" guard is a plain reference to `d` (e.g., `d == K`)
87+
# without subsampling -- subsampling guards (containing `Mod`)
88+
# still require the standard async pipeline
89+
explicit = not g.has(Mod) and d in retrieve_terminals(g)
90+
if explicit:
91+
continue
14292
protected = self._schedule_waitlocks(c0, d, clusters, locks, syncs)
14393
self._schedule_withlocks(c0, d, protected, locks, syncs)
14494

@@ -252,7 +202,7 @@ def memcpy_prefetch(clusters, key0, sregistry):
252202
continue
253203

254204
if c.properties.is_prefetchable(d._defines):
255-
_actions_from_update_memcpy(c, d, clusters, actions, sregistry, bounds)
205+
_actions_from_update_memcpy(c, d, bounds, clusters, actions, sregistry)
256206
elif d.is_Custom and is_integer(c.ispace[d].size):
257207
_actions_from_init(c, d, actions)
258208
else:
@@ -308,25 +258,25 @@ def _actions_from_sync(c, d, actions):
308258
)
309259

310260

311-
def _actions_from_update_memcpy(c, d, clusters, actions, sregistry, bounds):
261+
def _actions_from_update_memcpy(c, d, bounds, clusters, actions, sregistry):
312262
pd = d.root # E.g., `vd -> time`
313263
direction = c.ispace[pd].direction
314264

315265
e = c.exprs[0]
316-
function = e.rhs.function
266+
f = e.rhs.function
317267
target = e.lhs.function
318268

319269
fetch = e.rhs.indices[d]
320270
fshift = {Forward: 1, Backward: -1}.get(direction, 0)
321-
findex = next_index(fetch, pd, fshift)
271+
findex = eval_next_index(fetch, pd, fshift)
322272

323273
# Maximum allowed access along d
324-
if function.dimensions[d].is_Conditional:
325-
nslot = function.dimension_shape[d]
326-
v = function.dimensions[d].symbolic_factor
274+
if f.dimensions[d].is_Conditional:
275+
nslot = f.dimension_shape[d]
276+
v = f.dimensions[d].symbolic_factor
327277
fd_max = bounds.setdefault(d, v * (nslot - 1))
328278
else:
329-
fd_max = bounds.setdefault(d, function.dimension_shape[d] - 1)
279+
fd_max = bounds.setdefault(d, f.dimension_shape[d] - 1)
330280

331281
# If fetching into e.g. `ub[t1]` we might need to prefetch into e.g. `ub[t0]`
332282
tindex0 = e.lhs.indices[d]
@@ -355,23 +305,26 @@ def _actions_from_update_memcpy(c, d, clusters, actions, sregistry, bounds):
355305
expr = uxreplace(e, {tindex0: tindex, fetch: findex})
356306

357307
ispace = c.ispace.augment({pd: tindex}) if tindex is not tindex0 else c.ispace
308+
# Insert a VirtualDimension nested under `pd` so that the access guard
309+
# (`guard0`) below can be evaluated only after the tindex bound guard
310+
# (`guard1`) has already been validated
311+
vdnext = VirtualDimension(name=f'vdnext_{d.name}', parent=pd)
312+
ispace = ispace.insert(pd, vdnext)
358313

359314
guard0 = c.guards.get(d, true)._subs(fetch, findex)
360-
guard1 = GuardBoundNext(function.indices[d], e.rhs.indices[d], direction,
315+
guard1 = GuardBoundNext(f.indices[d], e.rhs.indices[d], direction,
361316
d_min=0, d_max=fd_max)
362317

363-
# First guard1 then if guard1 is valid we can safely evaluate guard0
364-
# that will have valid indices into f
365-
vdnext = VirtualDimension(name=f'vdnext_{d.name}', parent=pd)
366-
ispace = ispace.insert(pd, vdnext)
318+
# First guard1; then, if guard1 is valid, we can safely evaluate guard0,
319+
# which will then have valid indices into f
367320
# Check valid tindex first
368321
guards = c.guards.impose(d, guard1)
369-
# THen check valid access
322+
# Then check valid access
370323
guards = guards.impose(vdnext, guard0)
371324

372325
syncs = {d: [
373326
ReleaseLock(handle, target),
374-
PrefetchUpdate(handle, target, tindex, function, findex, d, 1, e.rhs)
327+
PrefetchUpdate(handle, target, tindex, f, findex, d, 1, e.rhs)
375328
]}
376329
syncs = {**c.syncs, **syncs}
377330

@@ -409,3 +362,65 @@ def __init__(self, drop=False, syncs=None, insert=None):
409362

410363
def wraps_memcpy(cluster):
411364
return len(cluster.exprs) == 1 and is_memcpy(cluster.exprs[0])
365+
366+
367+
@singledispatch
368+
def eval_next_index(expr, dim, dir):
369+
"""
370+
Evaluate `expr` at the next iteration point along `dim` in the given
371+
`dir`-ection, where `dir` is `+1` for forward and `-1` for backward.
372+
373+
For `IntDiv` subexpressions encoding subsampling, forward and backward
374+
fetches are treated asymmetrically since piecewise correction terms may
375+
already be applied at the current point.
376+
"""
377+
return expr._subs(dim, dim + dir)
378+
379+
380+
@eval_next_index.register(Expr)
381+
def _(expr, dim, dir):
382+
if not expr.args:
383+
return expr._subs(dim, dim + dir)
384+
return expr.func(*[eval_next_index(a, dim, dir) for a in expr.args])
385+
386+
387+
@eval_next_index.register(IntDiv)
388+
def _(expr, dim, dir):
389+
"""
390+
Handle forward and backward fetches separately to handle non-canonical index
391+
expressions of the form:
392+
393+
t//factor + cond(t)
394+
395+
where ``cond(t)`` is a piecewise correction term.
396+
397+
The forward fetch advances to the next coarse-grained slot while evaluating
398+
the correction at the next time point:
399+
400+
t//factor + cond(t)
401+
-> (t//factor + 1) + cond(t + 1)
402+
403+
The backward fetch is not, in general, the inverse transformation obtained by
404+
replacing ``+1`` with ``-1``. The correction may already be applied at the
405+
current time point, causing the forward and backward fetches to be asymmetric.
406+
407+
For example, with ``factor=2`` and ``cond(t) := (t == a)``, the index at
408+
``t=a=3`` is:
409+
410+
3//2 + 1 = 2
411+
412+
while the previous index is:
413+
414+
2//2 + 0 = 1
415+
416+
A symmetric backward transformation would instead yield:
417+
418+
3//2 - 1 + 0 = 0
419+
"""
420+
if expr.lhs._defines & dim._defines:
421+
if dir == 1:
422+
return expr + dir
423+
else:
424+
return expr._subs(dim, dim + dir)
425+
else:
426+
return expr

0 commit comments

Comments
 (0)