|
15 | 15 | __all__ = ['memcpy_prefetch', 'tasking'] |
16 | 16 |
|
17 | 17 |
|
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 | | - |
72 | 18 | def async_trigger(c, dims): |
73 | 19 | """ |
74 | 20 | Return the Dimension in `c`'s IterationSpace that triggers the |
@@ -134,11 +80,15 @@ def callback(self, clusters, prefix): |
134 | 80 | if d is not dim: |
135 | 81 | continue |
136 | 82 | 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 |
142 | 92 | protected = self._schedule_waitlocks(c0, d, clusters, locks, syncs) |
143 | 93 | self._schedule_withlocks(c0, d, protected, locks, syncs) |
144 | 94 |
|
@@ -252,7 +202,7 @@ def memcpy_prefetch(clusters, key0, sregistry): |
252 | 202 | continue |
253 | 203 |
|
254 | 204 | 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) |
256 | 206 | elif d.is_Custom and is_integer(c.ispace[d].size): |
257 | 207 | _actions_from_init(c, d, actions) |
258 | 208 | else: |
@@ -308,25 +258,25 @@ def _actions_from_sync(c, d, actions): |
308 | 258 | ) |
309 | 259 |
|
310 | 260 |
|
311 | | -def _actions_from_update_memcpy(c, d, clusters, actions, sregistry, bounds): |
| 261 | +def _actions_from_update_memcpy(c, d, bounds, clusters, actions, sregistry): |
312 | 262 | pd = d.root # E.g., `vd -> time` |
313 | 263 | direction = c.ispace[pd].direction |
314 | 264 |
|
315 | 265 | e = c.exprs[0] |
316 | | - function = e.rhs.function |
| 266 | + f = e.rhs.function |
317 | 267 | target = e.lhs.function |
318 | 268 |
|
319 | 269 | fetch = e.rhs.indices[d] |
320 | 270 | fshift = {Forward: 1, Backward: -1}.get(direction, 0) |
321 | | - findex = next_index(fetch, pd, fshift) |
| 271 | + findex = eval_next_index(fetch, pd, fshift) |
322 | 272 |
|
323 | 273 | # 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 |
327 | 277 | fd_max = bounds.setdefault(d, v * (nslot - 1)) |
328 | 278 | else: |
329 | | - fd_max = bounds.setdefault(d, function.dimension_shape[d] - 1) |
| 279 | + fd_max = bounds.setdefault(d, f.dimension_shape[d] - 1) |
330 | 280 |
|
331 | 281 | # If fetching into e.g. `ub[t1]` we might need to prefetch into e.g. `ub[t0]` |
332 | 282 | tindex0 = e.lhs.indices[d] |
@@ -355,23 +305,26 @@ def _actions_from_update_memcpy(c, d, clusters, actions, sregistry, bounds): |
355 | 305 | expr = uxreplace(e, {tindex0: tindex, fetch: findex}) |
356 | 306 |
|
357 | 307 | 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) |
358 | 313 |
|
359 | 314 | 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, |
361 | 316 | d_min=0, d_max=fd_max) |
362 | 317 |
|
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 |
367 | 320 | # Check valid tindex first |
368 | 321 | guards = c.guards.impose(d, guard1) |
369 | | - # THen check valid access |
| 322 | + # Then check valid access |
370 | 323 | guards = guards.impose(vdnext, guard0) |
371 | 324 |
|
372 | 325 | syncs = {d: [ |
373 | 326 | 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) |
375 | 328 | ]} |
376 | 329 | syncs = {**c.syncs, **syncs} |
377 | 330 |
|
@@ -409,3 +362,65 @@ def __init__(self, drop=False, syncs=None, insert=None): |
409 | 362 |
|
410 | 363 | def wraps_memcpy(cluster): |
411 | 364 | 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