Conversation
Add dev/bend2/buffer_refcount, a Bend 2 model of the reference-counting and ownership protocol of buffers in memory-core (ArrowBuf, ReferenceManager/BufferLedger, AllocationManager, BufferAllocator and Accountant), with its laws and machine-checked proofs. main.bend models one allocator with a list of allocations (id, size, refcount, freed flag), an allocated-bytes counter, a limit and a closed flag, and four requests: buffer(size), retain(n), release(n), close(). It runs a sample trace and compiles to JavaScript and to a native binary. LAWS.bend states 21 laws taken from the Javadoc and Preconditions messages of memory-core: the accounting invariant (allocated bytes equal the live bytes, and the freed flag agrees with a zero refcount) holds at start and after every request; release(0), release past zero, release or retain on a freed or unknown buffer are errors that leave the state unchanged; release to zero answers true, frees and subtracts the size; release that keeps memory lowers the count by exactly n; retain adds exactly n; over-limit allocations are rejected without accounting changes; buffer(0) is the empty buffer; close() fails with an outstanding buffer, succeeds otherwise, is idempotent, and rejects later requests; plus three anti-vacuity traces. Four more laws are kept as comments with NOT PROVEN / NOT EXPRESSIBLE / NOT MODELLED notes (ownership transfer between allocators, atomicity, long overflow, bounds checking). PROOF.bend proves all 21 laws; bend PROOF.bend prints "All terms check." in 0.3 s. Six injected bugs (silent over-release, forgotten releaseBytes, retain reviving a freed buffer, forgotten release0, ignored limit, close never reporting a leak) are each rejected by the checker. The README records the law-to-Javadoc mapping, how to run, the mutation results and three observations about BufferLedger.retain and BaseAllocator.close. No Java code is changed. Claude-Session: https://claude.ai/code/session_01BuBn3Vo2xksBqywiB98BS1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What's Changed
Adds
dev/bend2/buffer_refcount/, a proof of concept that uses the Bend 2 language to state and machine-check the reference-counting and ownership protocol of buffers inmemory-core(ArrowBuf,ReferenceManager/BufferLedger,AllocationManager,BufferAllocator/BaseAllocator/Accountant). No Java code is changed.main.bend: the model. One allocator owns a list of allocations (id, size, refcount, freed flag) with an allocated-bytes counter, a limit and a closed flag. Requests:buffer(size),retain(n),release(n),close(), each with the checks the Java makes in the order it makes them.mainruns a sample trace; the file compiles to JavaScript (26 KB) and to a native binary (1.1 MB), both verified to run.LAWS.bend: 21 laws, each quoting the Javadoc orPreconditionssentence it comes from.PROOF.bend: proofs of all 21 laws plus the lemma library they need.bend PROOF.bendprintsAll terms check.in 0.3 s.README.md: what is modelled, the law-to-Javadoc mapping, how to run, results, and observations about the Java.Laws proven (21)
inv_start,inv_kept): the allocated-bytes counter equals the sum of sizes of live allocations, and every freed flag equals "refcount is 0", at start and after every request. This is the accounting invariant (law 3 of the brief) and "memory is freed exactly when the refcount reaches zero" (law 2) as one inductive invariant.release(0)is an error; releasing more than is held ("RefCnt has gone negative") is an error that changes nothing; a release on a freed or unknown buffer is an error, not a silent no-op; release to zero answerstrue, marks the memory freed and subtracts the size from the counter; a release that keeps memory answersfalse, keeps the buffer live, lowers the count by exactlynand leaves the counter alone.retain(0)is an error; retain on a freed or unknown buffer is an error and does not revive it; retain on a live buffer adds exactlynand does not touch the counter.buffer(0)is the empty buffer.close()with an outstanding buffer is an error; with none it succeeds and closes; the two leak checks inBaseAllocator.closeagree under the invariant; after close,buffer/retain/releasefail and change nothing; a secondclose()is a no-op.buffer; release; closefrees and closes cleanly;buffer; retain; release; closekeeps one reference and reports the leak;buffer; release; releaseis an error.Laws commented out in
LAWS.bendtransfer_preserves_total(ownership transfer between two allocators, law 7): NOT PROVEN. The model has one allocator and one ledger per chunk; a faithful transfer needs theAllocationManagermap from allocators to ledgers, an owning ledger per chunk andforceAllocate/releaseByteson two accountants. Left as follow-up work.release_is_atomic: NOT EXPRESSIBLE. Bend is pure and a step is a function; theAtomicIntegerFieldUpdater/synchronizedbehaviour has no interleaving to quantify over without an explicit scheduler model.alloc_overflow_rejected: NOT EXPRESSIBLE. Bend hasNat,U32andF32only; thelongoverflow branch ofAccountant.allocatehas no model.read_within_capacity(ArrowBuf.checkBytes): NOT MODELLED. Expressible, but this model has no per-buffer capacity or indexes.Mutation tests
Six bugs were injected into
main.bendone at a time, the checker run, and the file restored (byte-identical). All six are rejected:okinstead of throwingrelease_below_zero_is_errorreleaseBytes(counter not decremented)retainon a freed buffer revives itrelease0forgotten)buffer(size)ignores the limitalloc_over_limit_rejected(confirmed by adapting the invariant proof under the mutation)close()never reports a leakclose_with_live_buffer_is_errorObservations about the Java (documented in the README, nothing changed)
BufferLedger.retain(int)does not callassertOpen(), unlikereleaseandnewArrowBuf; the model'sclosed_rejectslaw is stricter than the code forretain.retain(int)andrelease(int)update the count and then check the precondition, so a failing call mutates the count before throwing. The model states the intended contract (state unchanged on error).BaseAllocator.close()setsisClosedbefore checking for leaks, so a leaked buffer can never be released after a failed close (with assertions on). The model reproduces this.How to run
🤖 Generated with Claude Code
https://claude.ai/code/session_015wBzfARVa1g7nkB1muT5Sj
Generated by Claude Code