@@ -303,7 +303,11 @@ def _optimize(self, clusters, descriptors):
303303 processed .append (c )
304304 continue
305305
306- key1 = lambda d : not d ._defines & v .dim ._defines # noqa: B023
306+ # Lift only along the buffer's own spatial Dimensions, not
307+ # unrelated ones (e.g. sparse points of an indirect read)
308+ key1 = lambda d : (not d ._defines & v .dim ._defines and # noqa: B023
309+ any (d ._defines & bd ._defines # noqa: B023
310+ for bd in v .bdims )) # noqa: B023
307311 dims = c .ispace .project (key1 ).itdims
308312 ispace = c .ispace .lift (dims , key0 ())
309313 processed .append (c .rebuild (ispace = ispace ))
@@ -465,30 +469,37 @@ def itdims(self):
465469 def ispace (self ):
466470 # The IterationSpace within which the buffer will be accessed
467471
468- # NOTE: The `key` is to avoid Clusters including `f` but not directly
469- # using it in an expression, such as HaloTouch Clusters
470- def key (c ):
471- bufferdim = any (i in c .ispace .dimensions for i in self .bdims )
472- xd_only = all (d ._defines & self .xd ._defines for d in c .ispace .dimensions )
473- return bufferdim or xd_only
474-
475472 ispaces = set ()
473+ indirect = set ()
476474 for c in self .clusters :
477- if not key (c ):
478- continue
479-
480- # Skip wild clusters (e.g. HaloTouch Clusters)
475+ # Skip wild clusters (e.g. HaloTouch Clusters), which include `f`
476+ # but do not directly use it in an expression
481477 if c .is_wild :
482478 continue
483479
484- # Iterations space and buffering dims
485- edims = [d for d in self .bdims if d not in c .ispace .dimensions ]
486- if not edims :
487- ispaces .add (c .ispace )
488- else :
489- # Add all missing buffering dimensions and reorder to
490- # avoid duplicates with different ordering
491- ispaces .add (c .ispace .insert (self .dim , edims ).reorder ())
480+ bufferdim = any (i in c .ispace .dimensions for i in self .bdims )
481+ xd_only = all (d ._defines & self .xd ._defines for d in c .ispace .dimensions )
482+
483+ if bufferdim or xd_only :
484+ # `c` iterates (at least some of) the buffer's spatial dims
485+ edims = [d for d in self .bdims if d not in c .ispace .dimensions ]
486+ if not edims :
487+ ispaces .add (c .ispace )
488+ else :
489+ # Add all missing buffering dimensions and reorder to
490+ # avoid duplicates with different ordering
491+ ispaces .add (c .ispace .insert (self .dim , edims ).reorder ())
492+ elif ((self .f in c .scope .reads or self .f in c .scope .writes ) and
493+ self .dim .root in c .ispace .dimensions ):
494+ # `c` accesses `f` indirectly (e.g. interpolation), so it doesn't
495+ # iterate `bdims`; span the buffer's own Dimensions instead
496+ tispace = c .ispace .project (lambda i : i ._defines & self .dim .root ._defines )
497+ indirect .add (tispace .insert (self .dim , list (self .bdims )))
498+
499+ # Indirect accessors define the ispace only for a read-only streamed
500+ # buffer, where nothing iterates the buffer's own Dimensions directly
501+ if not ispaces :
502+ ispaces = indirect
492503
493504 if len (ispaces ) > 1 :
494505 # Best effort to make buffering work in the presence of multiple
@@ -609,7 +620,11 @@ def last_idx(self):
609620 mapper = {}
610621 func = vmax if self .is_forward_buffering else vmin
611622 for c in self .lastwrite + self .firstread :
612- indices = extract_indices (self .f , self .dim , [c ])
623+ # Consider all Clusters sharing `c`'s guards, so the leading edge is
624+ # found even when `f` is accessed at different offsets across them
625+ # (e.g. `f` and `f.forward` in separate Eqs)
626+ group = [c1 for c1 in self .clusters if c1 .guards == c .guards ]
627+ indices = extract_indices (self .f , self .dim , group )
613628 idx = func (* [Vector (i ) for i in indices ])[0 ]
614629 mapper [c ] = idx
615630
0 commit comments