Skip to content

feat: Smarter reference assignment checks thanks to a sort of borrow checker #2354

@iwoplaza

Description

@iwoplaza

We're given the following mutable buffer:

const Boid = d.struct({
  pos: d.vec2f,
  vel: d.vec2f,
});

const boids = root.createMutable(d.arrayOf(Boid, 512));

Let's consider a situation where we create a local boid: Boid variable, set its properties in some non-trivial way, then assign it to a mutable buffer. As of writing this issue, we throw an error saying that references cannot be assigned.

function init() {
  'use gpu';
  
  const boid = Boid();
  
  // ...
  boid.pos.x = 123;
  // ...

  boids.$[0] = boid; // ❌ Error: 'boids[0] = boid' is invalid, because references cannot be assigned (...)
}

We do this to ensure that a certain guarantee is not broken:

Note

Expecting boids.$[0] and boid to point to the same reference after the assignment.

This is however very limiting, considering that mutating a reference that has been assigned to something is quite rare in our own examples, and we ourselves frequently reach this limitation and have to explicitly copy. What we can do instead, is forbid the code that follows an assignment to mutate a reference.

function init() {
  'use gpu';
  
  const boid = Boid();
  
  // ...
  boid.pos.x = 123;
  // ...

  boids.$[0] = boid; // ✅ This is okay, we let this assignment "consume" the reference
  
  fn(boid); // ✅ This is okay, non-pointer arguments are immutable

  boid.pos.x = 234; // ❌ Error: Cannot mutate an already assigned ("consumed") reference
}

What counts as consumption?

We need to allow up-to-one consumption of a resource, so that we don't end up with two things pointing to the same value.

  • foo = reference
  • return reference statements - since the caller of a function takes ownership of the returned value, we can't track it for further mutations. By counting return statements like consumptions, we don't allow an assigned value to be returned later on.
  • (any more?)
function init() {
  'use gpu';
  
  const boid = Boid();
  
  boids.$[0] = boid; // We let this assignment "consume" the reference

  return boid; // ❌ Error: Cannot consume a resource more than once
}

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions