The debug-info arm and the structural printer are each a separate path from everything the suite was exercising: `outputs ~dev:true` goes through the cells, not through DWARF, and no program printed a Vec or an allocator. That is NEXT.md's landed item 2 exactly — field_addr took only Types.Named, so the printer's Option arm had never run and would have died on the first (Option T) pointed at it. Both arms work; both are now reached, and the DWARF row asserts the composite's size as well as its name, because an element count that disagreed with `lay` would print plausible values for the wrong fields. Printing a Vec did not work: `println` checked its argument as an ordinary read, so it moved, and every printing of a Vec would have been its last. Printing is a borrow — the walk goes over the value and keeps nothing. And `vec-new` with an explicitly named null allocator no longer substitutes the heap for it. Adopting the context for a *zeroed* Vec is the documented rule; quietly substituting for an allocator the program named is the same "released the region / never made one" collapse free-all already traps for, except silent and found later as a leak. The no-allocator-named case never arrives as null — the checker passes flan_context_allocator(), which always answers one.
110 lines
3.9 KiB
Plaintext
110 lines
3.9 KiB
Plaintext
;;;; (Vec T) — spec-memory.md, "The four container types" and "Allocators".
|
|
;;;;
|
|
;;;; ptr + len + cap + allocator, owning and move-only, over one type-erased
|
|
;;;; runtime. The element type appears nowhere in that runtime: size_of and
|
|
;;;; align_of are produced at the call site, which without generics is simply
|
|
;;;; the concrete call site. So this file being two element types with one
|
|
;;;; runtime behind them is the whole claim.
|
|
|
|
(defstruct Point [x i32 y i32])
|
|
|
|
;;; Ownership transfers on the call. The caller's binding is dead after this,
|
|
;;; which is what the refusal cases in test_acceptance assert.
|
|
(defn consume [v (Vec i32)] i32
|
|
(let [n (len v)]
|
|
(free v)
|
|
n))
|
|
|
|
;;; A Vec is returned by moving it out, so the callee's binding is the
|
|
;;; caller's. Nothing is released at function exit — there is no scope-end
|
|
;;; anything in this language.
|
|
(defn make [n i32] (Vec i32)
|
|
(let [v (vec-new i32)]
|
|
(dotimes [i n] (push v (* i i)))
|
|
v))
|
|
|
|
(defn sum [xs [i32]] i32
|
|
(let [total 0]
|
|
(dotimes [i (len xs)] (set total (+ total (at xs i))))
|
|
total))
|
|
|
|
(defn main [] i32
|
|
(let [v (vec-new i32)]
|
|
(println (len v)) ; 0
|
|
(push v 10)
|
|
(push v 20)
|
|
(push v 30)
|
|
(println (len v)) ; 3
|
|
(println (at v 0)) ; 10
|
|
(println (at v 2)) ; 30
|
|
;; A Vec element is a place, and the same bounds and epoch check stands
|
|
;; behind the value form and the place form.
|
|
(set (at v 1) 99)
|
|
(println (at v 1)) ; 99
|
|
|
|
;; as-slice is a non-owning view: it copies ptr+len and never the
|
|
;; elements, and it carries no allocator, so nothing can be freed through
|
|
;; one. [at] and [len] over it are the array operations, unchanged.
|
|
(println (sum (as-slice v))) ; 139
|
|
(println (len (as-slice v 1 3))) ; 2
|
|
(println (at (as-slice v 1 3) 0)) ; 99
|
|
|
|
;; clone is the only copy: assignment moves. The copy is independent, and
|
|
;; freeing it leaves the original alone.
|
|
(let [w (clone v)]
|
|
(set (at w 0) -1)
|
|
(println (at w 0)) ; -1
|
|
(println (at v 0)) ; 10
|
|
(free w))
|
|
|
|
;; reserve does not change the length, only the capacity, so a reserve
|
|
;; that succeeds is invisible except that the pushes after it do not grow.
|
|
(reserve v 64)
|
|
(println (len v)) ; 3
|
|
(push v 40)
|
|
(println (len v)) ; 4
|
|
|
|
;; The structural printer reaches both new types. Neither is followed: a
|
|
;; Vec's elements are printed through (as-slice v), which says at the call
|
|
;; site that it borrowed, and an allocator's contents are the runtime's and
|
|
;; its address is not stable across runs.
|
|
(println v) ; <vec>
|
|
(println context/allocator) ; <allocator>
|
|
|
|
(free v))
|
|
|
|
;; A second element type over the same runtime, and a struct element, so
|
|
;; that size_of and align_of are doing work rather than both being 4.
|
|
(let [ps (vec-new Point)]
|
|
(push ps (Point {:x 1 :y 2}))
|
|
(push ps (Point {:x 3 :y 4}))
|
|
(println (len ps)) ; 2
|
|
(println (.y (at ps 1))) ; 4
|
|
(free ps))
|
|
|
|
;; A Vec made against an explicit allocator records it, so free and clone
|
|
;; never need it named again. An arena cannot free one block, so this free
|
|
;; keeps the block — releasing it is free-all's job, and that is the
|
|
;; difference the capability set exists to state.
|
|
(let [a (arena-new 4096)]
|
|
(let [v (vec-new i32 a)]
|
|
(push v 7)
|
|
(println (at v 0)) ; 7
|
|
(free v))
|
|
(println (can-free? a)) ; false
|
|
(free-all a)
|
|
(arena-destroy a))
|
|
|
|
;; The pushes go into whatever the context names, with nothing passed.
|
|
(let [a (arena-new 4096)]
|
|
(with-allocator a
|
|
(let [v (vec-new i32)]
|
|
(push v 5)
|
|
(push v 6)
|
|
(println (+ (at v 0) (at v 1))) ; 11
|
|
(free v)))
|
|
(arena-destroy a))
|
|
|
|
(println (consume (make 5))) ; 5
|
|
0)
|