# Grid: a square quadtree, and the parallel walks over it.
# ========================================================
#
# A Grid of depth d holds 2^d * 2^d values. The four fields keep the
# screen order that Bend's Image uses, so a board maps onto a frame
# without a transpose.
#
# The grid is Data, so a `+` copy costs one reference count. That is
# what lets every leaf of a parallel walk read the whole previous
# board at the same time.
import Base
type Quad is Data:
QTL{}
QTR{}
QBL{}
QBR{}
type Grid is Kind(a):
GLeaf{val: A}
GQuad{tl: Grid, tr: Grid, bl: Grid, br: Grid}
# Picks a quadrant from one bit of x and one bit of y.
def Quad.of(bx: Bool, by: Bool) -> Quad:
match bx by:
case False{} False{}:
QTL{}
case True{} False{}:
QTR{}
case False{} True{}:
QBL{}
case True{} True{}:
QBR{}
# The route from the root of a depth-d grid down to cell (x, y).
# Bit d-1 of x and y picks the first turn, bit 0 the last.
def Grid.path(d: Nat, +x: U32, +y: U32) -> List<&2, Quad>:
match d:
case 0n:
Nil{}
case Succ{+p}:
bx = U32.is_ne(U32.and(U32.shrn(x, p), 1), 0)
by = U32.is_ne(U32.and(U32.shrn(y, p), 1), 0)
Quad.of(bx, by) <> Grid.path(p, x, y)
# Reads the value at the end of a path.
# A path shorter than the grid stops at the top-left corner below it.
def Grid.get(-A: Data, g: Grid<&2, A>, path: List<&2, Quad>) -> A:
match g path:
case GLeaf{v} _:
v
case GQuad{tl, tr, bl, br} Nil{}:
Grid.get(A, tl, Nil{})
case GQuad{tl, tr, bl, br} Con{QTL{}, rest}:
Grid.get(A, tl, rest)
case GQuad{tl, tr, bl, br} Con{QTR{}, rest}:
Grid.get(A, tr, rest)
case GQuad{tl, tr, bl, br} Con{QBL{}, rest}:
Grid.get(A, bl, rest)
case GQuad{tl, tr, bl, br} Con{QBR{}, rest}:
Grid.get(A, br, rest)
# Writes v at the end of a path. Only the nodes on the path change.
# A path shorter than the grid stops where Grid.get stops, at the
# top-left corner below it. That is what makes `read_back` hold for
# every path, not only for one of the right length.
def Grid.set(-A: Data, g: Grid<&2, A>, path: List<&2, Quad>, v: A) -> Grid<&2, A>:
match g path:
case GLeaf{old} _:
GLeaf{v}
case GQuad{tl, tr, bl, br} Nil{}:
GQuad{Grid.set(A, tl, Nil{}, v), tr, bl, br}
case GQuad{tl, tr, bl, br} Con{QTL{}, rest}:
GQuad{Grid.set(A, tl, rest, v), tr, bl, br}
case GQuad{tl, tr, bl, br} Con{QTR{}, rest}:
GQuad{tl, Grid.set(A, tr, rest, v), bl, br}
case GQuad{tl, tr, bl, br} Con{QBL{}, rest}:
GQuad{tl, tr, Grid.set(A, bl, rest, v), br}
case GQuad{tl, tr, bl, br} Con{QBR{}, rest}:
GQuad{tl, tr, bl, Grid.set(A, br, rest, v)}
# Builds a depth-d grid from a function of the payload and the cell
# position. The four quadrants run in parallel and are the same size,
# which is what Bend's fork-join scheduler wants.
def Grid.init(
~A: Data, ~P: Data, ~f: @+q: P -> @+u: U32 -> @+v: U32 -> A,
d: Nat, +p: P, +x: U32, +y: U32
) -> Grid<&2, A>:
match d:
case 0n:
GLeaf{f(p, x, y)}
case Succ{+k}:
+h = U32.shln(1, k)
a b c e =
Grid.init(~A, ~P, ~f, k, p, x, y)
Grid.init(~A, ~P, ~f, k, p, (x + h : U32), y)
Grid.init(~A, ~P, ~f, k, p, x, (y + h : U32))
Grid.init(~A, ~P, ~f, k, p, (x + h : U32), (y + h : U32))
GQuad{a, b, c, e}
# Maps every value. The four quadrants run in parallel.
def Grid.map(~A: Data, ~B: Data, ~f: @c: A -> B, g: Grid<&2, A>) -> Grid<&2, B>:
match g:
case GLeaf{v}:
GLeaf{f(v)}
case GQuad{tl, tr, bl, br}:
a b c e =
Grid.map(~A, ~B, ~f, tl)
Grid.map(~A, ~B, ~f, tr)
Grid.map(~A, ~B, ~f, bl)
Grid.map(~A, ~B, ~f, br)
GQuad{a, b, c, e}
# Adds a number up over every value. The four quadrants run in parallel.
def Grid.sum(~A: Data, ~f: @c: A -> U32, g: Grid<&2, A>) -> U32:
match g:
case GLeaf{v}:
f(v)
case GQuad{tl, tr, bl, br}:
a b c e =
Grid.sum(~A, ~f, tl)
Grid.sum(~A, ~f, tr)
Grid.sum(~A, ~f, bl)
Grid.sum(~A, ~f, br)
(a + b + c + e : U32)