At a callable boundary, a write-capable slice is unique unless
you opt out. Two readonly slices may share.
That is the language. Everything else is a call site.
// writer + reader. Obligation: in ⊥ out.
scale(out: writeonly []r32, in: readonly []r32, s: r32) {
for i in 0..out.len {
out[i] = in[i] * s
}
}
And the call sites that must accept or reject. That is the whole v0 suite.
// scale.rf — writer + reader. Obligation: in ⊥ out.
scale(out: writeonly []r32, in: readonly []r32, s: r32) {
for i in 0..out.len {
out[i] = in[i] * s
}
}
// dot.rf — reader + reader. No obligation.
dot(a: readonly []r32, b: readonly []r32) -> r32 {
sum: r32 = 0
for i in 0..a.len {
sum = sum + a[i] * b[i]
}
return sum
}
// add.rf — writer + writer. Obligation: a ⊥ b.
add(a: readwrite []r32, b: readwrite []r32) {
for i in 0..a.len {
a[i] = a[i] + b[i]
}
}
// add_overlap.rf — same body, obligation cancelled.
#allow alias(a, b)
add_overlap(a: readwrite []r32, b: readwrite []r32) {
for i in 0..a.len {
a[i] = a[i] + b[i]
}
}
scale
writer + reader
in ⊥ out
dot
reader + reader
no obligation
add
writer + writer
a ⊥ b
add_overlap
writer + writer
#allow alias
Cases 2 and 5 are the hypothesis. Case 3 is the control that proves you did not make every pair unique.
scale(ys, xs, 2)
different allocs, writer + reader
scale(xs, xs, 2)
same alloc, writer + reader
_ = dot(xs, xs)
same alloc, reader + reader
add(xs, ys)
different allocs, writer + writer
add(xs, xs)
same alloc, writer + writer
add_overlap(xs, xs)
same alloc, explicit opt-out
// scale_inplace(xs, 2)
one name, in-place — a single readwrite, no pair
main() {
xs: []r32 = alloc(r32, 4)
ys: []r32 = alloc(r32, 4)
// 1. different allocs, writer + reader → accept
scale(ys, xs, 2)
// 2. same alloc, writer + reader → REJECT
scale(xs, xs, 2)
// 3. same alloc, reader + reader → accept
_ = dot(xs, xs)
// 4. different allocs, writer + writer → accept
add(xs, ys)
// 5. same alloc, writer + writer → REJECT
add(xs, xs)
// 6. same alloc, explicit opt-out → accept
add_overlap(xs, xs)
// 7. one name, in-place → accept (no pair)
// (would be: scale_inplace(xs, 2) with a single readwrite)
}
2 and 5 are compile errors that name the overlapping pair.
1, 3, 4, and 6 compile. When the proof succeeds,
scale's x86 does not reload in[i] after out[i] = ….
C will compile scale(xs, xs, n, 2) with restrict
and call it your problem. rf must refuse it.
scale(out: writeonly []r32, in: readonly []r32, s: r32)
scale(ys, xs, 2) // accept: different allocs
scale(xs, xs, 2) // REJECT: names the overlapping pair
void scale(float *restrict out, const float *restrict in, int n, float s)
scale(ys, xs, n, 2); // compiles
scale(xs, xs, n, 2); // also compiles
void scale(float *restrict out, const float *restrict in, int n, float s) {
for (int i = 0; i < n; i++) out[i] = in[i] * s;
}
float dot(const float *a, const float *b, int n) {
float sum = 0;
for (int i = 0; i < n; i++) sum += a[i] * b[i];
return sum;
}
void add(float *restrict a, float *restrict b, int n) {
for (int i = 0; i < n; i++) a[i] += b[i];
}
void add_overlap(float *a, float *b, int n) { /* no restrict */
for (int i = 0; i < n; i++) a[i] += b[i];
}
rf isn't public yet. Join the waitlist and we'll let you know when the first compiler drops.