Skip to content

A bang cannot borrow an Array: the editable voxel world reads its tree at 13 → 26 ms, the same frame reads a flat buffer at 1.3 → 1.4 #885

Description

@AdrielSantana

Summary

Follow-up to #836, after the Ownership section (thanks: that one owned use was the whole cost). The borrowed tree read took the edited world from 100–110 ms to 26 ms at 512², but the read still doubles the frame once the world has edits (13 → 26 ms), and the same frame drawn from a flat buffer costs 1.3 ms edited or not. An Array is that flat buffer, and the runtime's get opens no half, but a bang cannot borrow one: w is "consumed more than once" on the two sides of a parallel let, +w is "expected Data, observed Type", and the guide says so ("An Array has one owner, so it cannot go down a fork tree: build lists"). The question is whether a read-only borrow of an Array by a bang — host-owned, borrowed by the bang and returned, as the scene is — fits the runtime, or is ruled out by something I do not see.

What I did

The program is gfx/05_craft.bend in AdrielSantana/metal-bending: a 32×32×32 editable voxel world, fork!(7n) over 4×4 tiles at 512², the world a terrain function plus a quadtree of edits, read by one recursive wget that matches and recurses into one field (the guide's rule; in the emitted C the read is term_peek only). Windowless bench: five frames, the camera turning 0.01 rad a frame, a total(9n) checksum per frame, the ten checksums identical across every row below; "built" is 300 blocks placed in one corner. Apple M5; Bend 2.0.16 and 2.0.17 give the same numbers.

512², Metal untouched 300 blocks placed
the read that returned the chosen node (Bool.pick), before #836's answer 15 ms 100–110 ms
borrowed recursive walk (term_peek only) 13 ms 25–29 ms

What remains in the edited world is the walk itself: five dependent reads per column crossing (five levels for 32×32 columns), where an untouched world is one WNone match plus the terrain function.

To see what a flat read is worth, I ran the same pixel function on WebGPU (the leaf translated by hand into WGSL, on the runtime shape described in the "WebGPU, measured" section of #866), with the world as a 1024-word buffer, one word of 32 height bits per column, 0xFFFFFFFF for "not edited, compute the terrain". The frame's checksum, computed as total(9n) does (a collapsed 2×2 tile counts once), equals the Bend program's on both worlds — 751552256 untouched, 351470114 built — so it is the same picture:

512², WebGPU, flat buffer untouched 300 blocks placed
the same pixel function 1.34 ms 1.40 ms

So in the Bend build the tree read is most of the frame and all of the edit cost; a flat indexed read makes edits free, as in a conventional voxel engine.

What the checker says

import Base

def at(+i: U32, wv: Array<U32> & U32) -> U32:
  (w2, v) = wv
  v

def sum4(+k: Nat, w: Array<U32>, +i: U32) -> U32:
  match k:
    case 0n:
      at(i, Array.get(U32, w, i))
    case 1n+j:
      a b = sum4(j, w, (i * 2 : U32)) sum4(j, w, (i * 2 + 1 : U32))
      (a + b : U32)

def main() -> IO(Unit):
  do IO<Unit>:
    +w : Array<U32> = Array.new(U32, 10n, 3)
    IO.print(U32.show(sum4!(4n, w, 0)))
- expected : w
- observed : w (consumed more than once)

With +w: Array<U32>:

- expected : Data
- observed : Type

Both are right by the guide's rules (at drops w2, which is what a read-only use would want anyway).

The question

The Ownership section says the compiler borrows a boxed parameter "(not an Array)" that a def only matches or passes on, and the emitter's own note says get, set, swap, size and new open no half, so an array is a flat block that lanes could index. Is a read-only borrow of an Array inside a bang — the bang takes the block's address, every lane reads Array.get in place with no count and no split, nothing writes it until the bang returns it to the host, the way bend3d's scene is host-owned, borrowed and returned — something the runtime could support, or is there a reason it cannot (the block classes, the CUDA fault-per-page case, the checker's Type/Data split)? If it cannot, is the guide's per-tile list the intended shape for a DDA too, where a ray crosses a few dozen columns and the list would be walked per crossing? A sentence in the guide either way settles where an editable voxel world stops in Bend today; the numbers above are the case.

Written by Claude (Anthropic) with Adriel Santana driving; the numbers are from his M5.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions