Any typed container of numbers, bools, str, structs, arrays, slices or Vecs crosses into dyn as a view from any storage, and a dev build traps on a view whose frame returned or whose block was released.
This commit is contained in:
parent
f9dadce5d0
commit
7953376e4e
315
lib/check.ml
315
lib/check.ml
@ -354,7 +354,23 @@ type env = {
|
|||||||
mutable guard_next : bool;
|
mutable guard_next : bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_env () = {
|
(* The struct table [box] reads a struct's fields from when it describes one
|
||||||
|
for a view: [box] is called from places that hold no [ctx], and there is
|
||||||
|
one table per program. Set by [new_env]. *)
|
||||||
|
let view_structs : (string, Tast.structure) Hashtbl.t ref = ref (Hashtbl.create 1)
|
||||||
|
|
||||||
|
(* The global whose initialiser is being checked, and its form. A view taken
|
||||||
|
there of storage the initialiser itself built is gone the moment the
|
||||||
|
initialiser returns, so it is refused rather than left to the dev check:
|
||||||
|
nothing could ever read it. *)
|
||||||
|
let view_global_init : (string * Ast.reinit) option ref = ref None
|
||||||
|
|
||||||
|
let rec new_env () =
|
||||||
|
let env = new_env_record () in
|
||||||
|
view_structs := env.structs;
|
||||||
|
env
|
||||||
|
|
||||||
|
and new_env_record () = {
|
||||||
structs = Hashtbl.create 16;
|
structs = Hashtbl.create 16;
|
||||||
datas = Hashtbl.create 16;
|
datas = Hashtbl.create 16;
|
||||||
unions = Hashtbl.create 16;
|
unions = Hashtbl.create 16;
|
||||||
@ -3612,28 +3628,48 @@ let no_dyn_yet loc ~into t extra =
|
|||||||
"%s does not cross into %s yet%s"
|
"%s does not cross into %s yet%s"
|
||||||
(tyname loc t) (if into then "dyn" else "a written type") extra
|
(tyname loc t) (if into then "dyn" else "a written type") extra
|
||||||
|
|
||||||
(* M2 item 3: a typed container crossing into dyn as a view. The element set
|
|
||||||
is exactly the unboxable scalars — i64, f64, bool — and that is not a smaller
|
|
||||||
version of the same cut for the same reason: every other element type
|
|
||||||
would need [box] to run on IT too, and a string element's dyn form is a
|
|
||||||
pointer into the collector's heap, while a typed container's storage is
|
|
||||||
arena or stack memory the collector never scans. Writing that pointer
|
|
||||||
into memory nobody roots is a live reference the collector could free out
|
|
||||||
from under — the hazard runtime/flan_dyn.h's view section states at
|
|
||||||
length — and i64/f64/bool carry no pointer, so a view restricted to them
|
|
||||||
cannot manufacture it. It is a compile-time refusal here rather than a
|
|
||||||
run-time one because the element type is exactly what the checker already
|
|
||||||
knows at the crossing. The FLAN_VIEW_* constants are runtime/flan_dyn.h's;
|
|
||||||
this is the compiler's one copy of the same table. *)
|
|
||||||
let view_elem (t : Types.t) : int64 option =
|
|
||||||
match t with
|
|
||||||
| Types.Int Types.I64 -> Some 0L (* FLAN_VIEW_I64 *)
|
|
||||||
| Types.Float Types.F64 -> Some 1L (* FLAN_VIEW_F64 *)
|
|
||||||
| Types.Bool -> Some 2L (* FLAN_VIEW_BOOL *)
|
|
||||||
| _ -> None
|
|
||||||
|
|
||||||
let view_elem_lit loc (k : int64) =
|
(* A typed container crossing into dyn is a view, and the runtime needs to
|
||||||
mk loc (Types.Int Types.I32) (Tast.Int (k, Types.I32))
|
know what one element is: its descriptor, a prefix code runtime/flan_dyn.c
|
||||||
|
documents beside [desc_lay] and reads offsets out of by C's layout rule.
|
||||||
|
Every number, bool, str, struct of those, and fixed array, slice or Vec of
|
||||||
|
those can be described. [Error t] names the first type inside that cannot:
|
||||||
|
a dyn, a pointer, a function, an Option, a map, an enum or a data type.
|
||||||
|
None of those is refused for want of a descriptor letter — each is one a
|
||||||
|
dyn value cannot be read out of or written into without a meaning
|
||||||
|
nobody has decided. A str is read as a copy and never written, since a
|
||||||
|
dyn text is a collector pointer and typed storage is never scanned; a
|
||||||
|
[const] slice is refused because a dyn view can be written through. *)
|
||||||
|
let rec view_desc (t : Types.t) : (string, Types.t) result =
|
||||||
|
let ( let* ) = Result.bind in
|
||||||
|
match t with
|
||||||
|
| Types.Int k ->
|
||||||
|
Ok (match k with
|
||||||
|
| Types.I8 -> "b" | Types.U8 -> "B" | Types.I16 -> "h" | Types.U16 -> "H"
|
||||||
|
| Types.I32 -> "i" | Types.U32 -> "I" | Types.I64 -> "l" | Types.U64 -> "L")
|
||||||
|
| Types.Float Types.F32 -> Ok "f"
|
||||||
|
| Types.Float Types.F64 -> Ok "d"
|
||||||
|
| Types.Bool -> Ok "?"
|
||||||
|
| Types.String -> Ok "t"
|
||||||
|
| Types.Array (n, e) ->
|
||||||
|
let* d = view_desc e in
|
||||||
|
Ok (Printf.sprintf "a%Ld;%s" n d)
|
||||||
|
| Types.Slice (Types.Mut, e) -> let* d = view_desc e in Ok ("s" ^ d)
|
||||||
|
| Types.Vec e -> let* d = view_desc e in Ok ("v" ^ d)
|
||||||
|
| Types.Named n ->
|
||||||
|
(match Hashtbl.find_opt !view_structs n with
|
||||||
|
| None -> Error t
|
||||||
|
| Some st ->
|
||||||
|
let* fs =
|
||||||
|
List.fold_left
|
||||||
|
(fun acc (fl : Tast.field) ->
|
||||||
|
let* acc = acc in
|
||||||
|
let* d = view_desc fl.Tast.fty in
|
||||||
|
Ok ((fl.Tast.fname ^ ";" ^ d) :: acc))
|
||||||
|
(Ok []) st.Tast.fields
|
||||||
|
in
|
||||||
|
Ok ("{" ^ n ^ ";" ^ String.concat "" (List.rev fs) ^ "}"))
|
||||||
|
| _ -> Error t
|
||||||
|
|
||||||
(* A fix is spelled in the syntax of the file the mistake is in: the checker
|
(* A fix is spelled in the syntax of the file the mistake is in: the checker
|
||||||
sees one AST for both, so the location's file is the only thing left that
|
sees one AST for both, so the location's file is the only thing left that
|
||||||
@ -3711,99 +3747,48 @@ let view_refusal kind loc (e : Tast.expr) reason =
|
|||||||
Loc.failk kind loc "%s, and a dyn value is wanted here. %s. %s" subject
|
Loc.failk kind loc "%s, and a dyn value is wanted here. %s. %s" subject
|
||||||
reason fix
|
reason fix
|
||||||
|
|
||||||
let view_not_yet loc (e : Tast.expr) (elem : Types.t) =
|
let view_not_yet loc (e : Tast.expr) (inner : Types.t) =
|
||||||
view_refusal "check/dyn-not-yet" loc e
|
view_refusal "check/dyn-not-yet" loc e
|
||||||
(Printf.sprintf
|
(Printf.sprintf
|
||||||
"A dyn value can see into a typed container only when its elements \
|
"A dyn value sees into numbers, bools, str and structs, and arrays, \
|
||||||
are i64, f64 or bool, and these are %s"
|
slices and Vecs of those; a %s is none of these"
|
||||||
(tyname loc elem))
|
(tyname loc inner))
|
||||||
|
|
||||||
(* M2 item 3's second guard, added on review: a view's descriptor holds an
|
(* Whether a view's storage is the current function's own frame, which is
|
||||||
address into the container's own storage, chased fresh on every
|
what the runtime's dev check needs to be told: it then records this
|
||||||
operation, which is what makes a Vec's growth safe — but it is also what
|
activation and traps if the view is used after the call returns. A local,
|
||||||
makes a *dangling* container's storage a live hazard nothing catches
|
a parameter (copied into the frame, an array parameter too), a field or
|
||||||
until somebody reads through the view. A view returned from the function
|
an array element of one, a slice cut directly from a local array, and a
|
||||||
whose frame the Vec lived in, stashed in a global and read after that
|
temporary [box] has bound to a slot of its own are all the frame's. A
|
||||||
frame is gone, or left behind when a condition transfer unwinds it, are
|
slice's data, and anything reached through a [Ptr] or a global, is not:
|
||||||
all stack-use-after-return once box stopped refusing containers outright
|
there the runtime looks the address up in the allocation registry instead,
|
||||||
— reachable now for the first time, not a pre-existing hole this lane
|
and storage the registry does not know — a global, or a caller's local
|
||||||
merely inherited.
|
seen through a slice parameter — is not checked. *)
|
||||||
|
let rec frame_root (e : Tast.expr) : bool =
|
||||||
On the dynamic side Flan aims where Clojure and Common Lisp are: holding
|
let rec all_array ty = function
|
||||||
a value should not hand you garbage. Treating a view as a bare pointer and
|
| [] -> true
|
||||||
calling the lifetime the programmer's problem is the Odin answer, and
|
| _ :: rest ->
|
||||||
neither Odin nor C stops it — this guard is the trade going the other way,
|
(match ty with Types.Array (_, elem) -> all_array elem rest | _ -> false)
|
||||||
refused rather than merely documented.
|
in
|
||||||
|
|
||||||
What it is NOT is a proof. runtime/flan_dyn.h states the actual property:
|
|
||||||
the view is exactly as stale-safe as the thing it is a view of, no more
|
|
||||||
and no less. This guard narrows what a view can be taken of; it does not
|
|
||||||
make the underlying storage outlive anything. A global [[T]] slice whose
|
|
||||||
data was cut from a frame that has since returned still passes here, and
|
|
||||||
reading through the view then reads a dead frame. So this is a guard that
|
|
||||||
closes the routes the checker can see, not a guarantee that a dyn value
|
|
||||||
never dangles.
|
|
||||||
|
|
||||||
[permanent_root] asks whether an expression's own address — the one a
|
|
||||||
view's pointer will chase — is guaranteed to outlive every frame, which is
|
|
||||||
true of exactly one thing at this milestone: a global. A field of a
|
|
||||||
permanent value is permanent at the same fixed offset from it, and so is
|
|
||||||
an element of a permanent *array* — both are still inside the permanent
|
|
||||||
value's own storage. An element of a permanent *slice* is not: a slice is
|
|
||||||
ptr+len, so a global [[T]] holds only the two words, and the storage they
|
|
||||||
point at can be a frame that has already gone. The [At] arm below is where
|
|
||||||
that distinction is made, and it is made per index rather than once: an
|
|
||||||
[(at g i j)] is a single node carrying the whole index list, so the arm
|
|
||||||
steps the list the way [indexed] does and an array level at every step is
|
|
||||||
what it demands. Reading only the target's type would settle level zero
|
|
||||||
and let a slice at any later level through — which it did, and the
|
|
||||||
accepted program printed a returned frame's contents. A slice built directly from
|
|
||||||
[(slice T lo hi)] inherits the
|
|
||||||
permanence of the [T] it was cut from — unwrapped here because that is
|
|
||||||
the one shape still carrying the trace back to it; once a slice has been
|
|
||||||
bound to a name the trace is gone and it is refused; the spelling that
|
|
||||||
keeps it is to view the slice expression directly, the way this file's
|
|
||||||
own survey program does.
|
|
||||||
|
|
||||||
Everything else — a local, a parameter, a temporary, anything reached
|
|
||||||
through a [Ptr] — answers false. A [Ptr] is refused rather than trusted
|
|
||||||
because a heap-allocated block and a frame slot are the same type: a
|
|
||||||
[(Ptr (Vec i64))] taken from a heap allocation would be sound to view, but
|
|
||||||
the same type is what [(addr some-local)] answers too, and the checker
|
|
||||||
cannot tell the two apart. Admitting one admits the other, which is the
|
|
||||||
whole hazard this guard exists to close — so until a Flan type exists
|
|
||||||
that says "durably heap-owned" and a [Ptr] does not, a container reached
|
|
||||||
through one is refused rather than guessed at. An arena-held container is
|
|
||||||
not a separate case: an arena changes where a Vec's *elements* live, never
|
|
||||||
where its own header — the value a name is bound to — lives, so a Vec
|
|
||||||
grown from an arena is exactly as permanent as the binding that holds it,
|
|
||||||
already covered by the cases above. *)
|
|
||||||
let rec permanent_root (e : Tast.expr) : bool =
|
|
||||||
match e.Tast.e with
|
match e.Tast.e with
|
||||||
| Tast.Global _ -> true
|
| Tast.Local _ -> (match e.Tast.ty with Types.Slice _ -> false | _ -> true)
|
||||||
| Tast.Field (target, _) -> permanent_root target
|
| Tast.Field (target, _) ->
|
||||||
|
(match target.Tast.ty with Types.Named _ -> frame_root target | _ -> false)
|
||||||
| Tast.Prim (Tast.At, target :: idx) ->
|
| Tast.Prim (Tast.At, target :: idx) ->
|
||||||
(* [(at g i j)] is ONE node carrying every index, so the target's own type
|
all_array target.Tast.ty idx && frame_root target
|
||||||
is only level zero and asking about it alone misses a slice reached at
|
| Tast.Prim (Tast.Slice, [ target; _; _ ]) ->
|
||||||
any later level. Step the list the way [indexed] does — that walk is
|
(match target.Tast.ty with Types.Array _ -> frame_root target | _ -> false)
|
||||||
the definition of which levels exist — and require every level stepped
|
|
||||||
to be an array. *)
|
|
||||||
let rec all_array ty = function
|
|
||||||
| [] -> true
|
|
||||||
| _ :: rest ->
|
|
||||||
(match ty with
|
|
||||||
| Types.Array (_, elem) -> all_array elem rest
|
|
||||||
| _ -> false)
|
|
||||||
in
|
|
||||||
all_array target.Tast.ty idx && permanent_root target
|
|
||||||
| Tast.Prim (Tast.Slice, [ target; _; _ ]) -> permanent_root target
|
|
||||||
| _ -> false
|
| _ -> false
|
||||||
|
|
||||||
let view_not_permanent loc (e : Tast.expr) =
|
(* Whether [e] names storage that already has an address, so a view can
|
||||||
view_refusal "check/dyn-view-lifetime" loc e
|
point at it; anything else is a temporary [box] binds to a slot first. *)
|
||||||
"A dyn value can see into a typed container only when it is a global: a \
|
let rec view_place (e : Tast.expr) : bool =
|
||||||
local, a parameter or a temporary can be gone while the dyn value still \
|
match e.Tast.e with
|
||||||
points at it"
|
| Tast.Local _ | Tast.Global _ | Tast.Deref _ -> true
|
||||||
|
| Tast.Field (target, _) ->
|
||||||
|
(match target.Tast.ty with Types.Named _ -> view_place target | _ -> true)
|
||||||
|
| Tast.Prim (Tast.At, _ :: _ :: _) -> true
|
||||||
|
| _ -> false
|
||||||
|
|
||||||
(* A value handed out of [f] that points into [f]'s own frame: returned (the
|
(* A value handed out of [f] that points into [f]'s own frame: returned (the
|
||||||
last form's tails, or a [return]), or stored into a global or a field or
|
last form's tails, or a [return]), or stored into a global or a field or
|
||||||
@ -4062,7 +4047,7 @@ let refuse_frame_escapes (f : Tast.fn) =
|
|||||||
if returns then
|
if returns then
|
||||||
match List.rev f.Tast.body with x :: _ -> tails x | [] -> ()
|
match List.rev f.Tast.body with x :: _ -> tails x | [] -> ()
|
||||||
|
|
||||||
let box loc (e : Tast.expr) : Tast.expr =
|
let box ?ctx loc (e : Tast.expr) : Tast.expr =
|
||||||
let dyn sym args = rt loc Types.Dyn sym args in
|
let dyn sym args = rt loc Types.Dyn sym args in
|
||||||
match e.Tast.ty with
|
match e.Tast.ty with
|
||||||
| Types.Dyn -> e
|
| Types.Dyn -> e
|
||||||
@ -4084,28 +4069,70 @@ let box loc (e : Tast.expr) : Tast.expr =
|
|||||||
Loc.failk "check/dyn-unit" loc
|
Loc.failk "check/dyn-unit" loc
|
||||||
"() does not box into dyn. The absent dyn value is nil — write nil"
|
"() does not box into dyn. The absent dyn value is nil — write nil"
|
||||||
| Types.Never -> e
|
| Types.Never -> e
|
||||||
(* A view, not a copy: the box holds one word naming where the elements
|
(* A view, not a copy: the box holds a small record naming where the
|
||||||
live and what one of them is, and every read or write goes straight
|
storage is and what one element is (its descriptor, [view_desc]), and
|
||||||
through to the container's own storage — see runtime/flan_dyn.h's
|
every read or write goes straight through to the container's own
|
||||||
view section for the whole of the argument, including why the
|
storage — see runtime/flan_dyn.h's view section. A Vec's view points AT
|
||||||
descriptor points AT the container (a Vec's own header address)
|
the Vec's header and reads its pointer and length live, so a push
|
||||||
rather than snapshotting its ptr+len. That is what makes a push
|
through the view cannot go stale; a slice and a fixed array cannot grow,
|
||||||
through the view safe even though a Vec can grow and move: there is
|
so a snapshot taken at the crossing is sound for both. A struct's view
|
||||||
no snapshot for the growth to invalidate. A slice and a fixed array
|
is a map-like value: (get p :x), (set (get p :x) v), (put p :x v).
|
||||||
cannot grow, so a snapshot taken once at the crossing is sound for
|
|
||||||
both, and they share [flan_dyn_view_flat]. *)
|
A container, array or struct is handed over by address. One that is not
|
||||||
(* The element check runs before the lifetime one in all three arms, and
|
a place already — a call's result — is bound to a slot of its own
|
||||||
the order is load-bearing rather than incidental: the lifetime message
|
first, so the view points at storage that lives as long as the frame
|
||||||
says a global can be seen into, and for an element type no view can
|
rather than at a temporary the next statement reuses. [frame_root] then
|
||||||
carry — a string, an i32 — a global is refused too, so the wrong order
|
says whether the storage is this frame's, for the runtime's dev check. *)
|
||||||
hands the programmer a reason that is false for their case. Whichever
|
| Types.Vec _ | Types.Array _ | Types.Named _ | Types.Slice (Types.Mut, _)
|
||||||
refusal is unconditional wins. *)
|
when (match e.Tast.ty with
|
||||||
| Types.Vec elem ->
|
| Types.Named n -> Hashtbl.mem !view_structs n
|
||||||
(match view_elem elem with
|
| _ -> true) ->
|
||||||
| None -> view_not_yet loc e elem
|
let desc_of t =
|
||||||
| Some k ->
|
match view_desc t with
|
||||||
if not (permanent_root e) then view_not_permanent loc e
|
| Ok d -> mk loc Types.String (Tast.Str d)
|
||||||
else dyn "flan_dyn_view_vec" [ e; view_elem_lit loc k ])
|
| Error inner -> view_not_yet loc e inner
|
||||||
|
in
|
||||||
|
let i32 n = mk loc (Types.Int Types.I32) (Tast.Int (n, Types.I32)) in
|
||||||
|
let i64 n = mk loc dyn_i64 (Tast.Int (n, Types.I64)) in
|
||||||
|
let bind, e =
|
||||||
|
match ctx, e.Tast.ty with
|
||||||
|
| Some ctx, (Types.Vec _ | Types.Array _ | Types.Named _)
|
||||||
|
when not (view_place e) ->
|
||||||
|
let sl = fresh_slot ctx e.Tast.ty in
|
||||||
|
Some (sl, e), mk loc e.Tast.ty (Tast.Local sl)
|
||||||
|
| _ -> None, e
|
||||||
|
in
|
||||||
|
(match !view_global_init with
|
||||||
|
| Some (g, kind) when frame_root e ->
|
||||||
|
let fln = fln_source loc in
|
||||||
|
let ty = tyname loc e.Tast.ty in
|
||||||
|
Loc.failk "check/dyn-view-lifetime" loc
|
||||||
|
"%s is a dyn global, and its initialiser builds a %s that is gone \
|
||||||
|
once the initialiser returns, so a dyn view of it would outlive \
|
||||||
|
it. Give %s its type, as in %s"
|
||||||
|
g ty g
|
||||||
|
(let form =
|
||||||
|
match kind with Ast.Every -> "def" | Ast.Once -> "defonce" in
|
||||||
|
if fln then
|
||||||
|
Printf.sprintf "%s %s: %s = ..."
|
||||||
|
(match kind with Ast.Every -> "def" | Ast.Once -> "once") g ty
|
||||||
|
else Printf.sprintf "(%s %s %s ...)" form g ty)
|
||||||
|
| _ -> ());
|
||||||
|
let here = i32 (if frame_root e then 1L else 0L) in
|
||||||
|
let view =
|
||||||
|
match e.Tast.ty with
|
||||||
|
| Types.Slice (_, elem) ->
|
||||||
|
dyn "flan_dyn_view_slice" [ e; desc_of elem; here ]
|
||||||
|
| Types.Vec elem ->
|
||||||
|
dyn "flan_dyn_view_at" [ addr_of loc e; i64 0L; desc_of elem; i32 1L; here ]
|
||||||
|
| Types.Array (n, elem) ->
|
||||||
|
dyn "flan_dyn_view_at" [ addr_of loc e; i64 n; desc_of elem; i32 0L; here ]
|
||||||
|
| t ->
|
||||||
|
dyn "flan_dyn_view_at" [ addr_of loc e; i64 0L; desc_of t; i32 2L; here ]
|
||||||
|
in
|
||||||
|
(match bind with
|
||||||
|
| None -> view
|
||||||
|
| Some b -> mk loc Types.Dyn (Tast.Let ([ b ], [ view ])))
|
||||||
(* A dyn view is written through by (set (at d i) x), and nothing on the
|
(* A dyn view is written through by (set (at d i) x), and nothing on the
|
||||||
dyn side can tell a read-only one apart, so a [[const T]] does not
|
dyn side can tell a read-only one apart, so a [[const T]] does not
|
||||||
cross. *)
|
cross. *)
|
||||||
@ -4115,20 +4142,6 @@ let box loc (e : Tast.expr) : Tast.expr =
|
|||||||
[const %s] can only be read. A dyn view is taken of the writable \
|
[const %s] can only be read. A dyn view is taken of the writable \
|
||||||
storage it came from"
|
storage it came from"
|
||||||
(tyname loc e.Tast.ty) (tyname loc elem)
|
(tyname loc e.Tast.ty) (tyname loc elem)
|
||||||
| Types.Slice (Types.Mut, elem) ->
|
|
||||||
(match view_elem elem with
|
|
||||||
| None -> view_not_yet loc e elem
|
|
||||||
| Some k ->
|
|
||||||
if not (permanent_root e) then view_not_permanent loc e
|
|
||||||
else dyn "flan_dyn_view_flat" [ e; view_elem_lit loc k ])
|
|
||||||
| Types.Array (n, elem) ->
|
|
||||||
(match view_elem elem with
|
|
||||||
| None -> view_not_yet loc e elem
|
|
||||||
| Some k ->
|
|
||||||
if not (permanent_root e) then view_not_permanent loc e
|
|
||||||
else
|
|
||||||
dyn "flan_dyn_view_flat"
|
|
||||||
[ e; mk loc dyn_i64 (Tast.Int (n, Types.I64)); view_elem_lit loc k ])
|
|
||||||
(* No view for a map yet, and the suggestion is the *literal* rather than a
|
(* No view for a map yet, and the suggestion is the *literal* rather than a
|
||||||
constructor call: there is no [(map-new dyn)] — [map_new_types] wants a
|
constructor call: there is no [(map-new dyn)] — [map_new_types] wants a
|
||||||
key and a value, and [map_type] refuses dyn as a key — so naming one
|
key and a value, and [map_type] refuses dyn as a key — so naming one
|
||||||
@ -4144,7 +4157,7 @@ let box loc (e : Tast.expr) : Tast.expr =
|
|||||||
mis-lowering. *)
|
mis-lowering. *)
|
||||||
| Types.Named _ | Types.Enum _ | Types.Option _ | Types.Ptr _
|
| Types.Named _ | Types.Enum _ | Types.Option _ | Types.Ptr _
|
||||||
| Types.Alloc | Types.Fn _ | Types.CFn _ | Types.Var _ | Types.Len _
|
| Types.Alloc | Types.Fn _ | Types.CFn _ | Types.Var _ | Types.Len _
|
||||||
| Types.LArray _ ->
|
| Types.LArray _ | Types.Vec _ | Types.Array _ | Types.Slice _ ->
|
||||||
no_dyn_yet loc ~into:true e.Tast.ty ""
|
no_dyn_yet loc ~into:true e.Tast.ty ""
|
||||||
|
|
||||||
(* Every dyn an expectation opened ([expect]'s dyn arm), by the node that
|
(* Every dyn an expectation opened ([expect]'s dyn arm), by the node that
|
||||||
@ -4549,7 +4562,7 @@ let expect ctx loc ~want (got : Tast.expr) =
|
|||||||
match w, got.Tast.ty with
|
match w, got.Tast.ty with
|
||||||
| Types.Dyn, Types.Dyn -> got
|
| Types.Dyn, Types.Dyn -> got
|
||||||
| Types.Dyn, Types.Option t -> box_option ctx loc t got
|
| Types.Dyn, Types.Option t -> box_option ctx loc t got
|
||||||
| Types.Dyn, _ -> box loc got
|
| Types.Dyn, _ -> box ~ctx loc got
|
||||||
| Types.Option t, Types.Dyn when not (is_nil_lit got) ->
|
| Types.Option t, Types.Dyn when not (is_nil_lit got) ->
|
||||||
let opened = unbox_option ctx loc t got in
|
let opened = unbox_option ctx loc t got in
|
||||||
Opened.replace opened_by_want opened got;
|
Opened.replace opened_by_want opened got;
|
||||||
@ -16458,7 +16471,11 @@ let check_global env (d : Ast.decl) : Tast.global option =
|
|||||||
{ Tast.e = Tast.Uninit ty; ty; loc = d.Ast.dloc }
|
{ Tast.e = Tast.Uninit ty; ty; loc = d.Ast.dloc }
|
||||||
| Ast.Init v ->
|
| Ast.Init v ->
|
||||||
let c = ctx () in
|
let c = ctx () in
|
||||||
let v = check c ~want:ty v in
|
let v =
|
||||||
|
view_global_init := Some (n, kind);
|
||||||
|
Fun.protect ~finally:(fun () -> view_global_init := None)
|
||||||
|
(fun () -> check c ~want:ty v)
|
||||||
|
in
|
||||||
if Tast.const_init v && not lift_always then v
|
if Tast.const_init v && not lift_always then v
|
||||||
else lift_ginit c d.Ast.dloc n ty v
|
else lift_ginit c d.Ast.dloc n ty v
|
||||||
(* [settle_defvars] turned every one of these into a [Zeroed] or an
|
(* [settle_defvars] turned every one of these into a [Zeroed] or an
|
||||||
@ -17768,7 +17785,7 @@ let memory_class (sym : string) (args : Tast.expr list) =
|
|||||||
| "flan_dyn_map_new_class" ->
|
| "flan_dyn_map_new_class" ->
|
||||||
gc "allocates: a class instance is a dyn map on the collector's heap, \
|
gc "allocates: a class instance is a dyn map on the collector's heap, \
|
||||||
with the class's name in its header"
|
with the class's name in its header"
|
||||||
| "flan_dyn_view_vec" | "flan_dyn_view_flat" ->
|
| "flan_dyn_view_slice" | "flan_dyn_view_at" ->
|
||||||
gc "allocates: a typed container crossing into dyn takes a view record \
|
gc "allocates: a typed container crossing into dyn takes a view record \
|
||||||
on the collector's heap — the elements are not copied, the record is"
|
on the collector's heap — the elements are not copied, the record is"
|
||||||
| "flan_dyn_from_i64" when (match args with [ x ] -> int_may_spill x | _ -> true) ->
|
| "flan_dyn_from_i64" when (match args with [ x ] -> int_may_spill x | _ -> true) ->
|
||||||
|
|||||||
25
lib/emit.ml
25
lib/emit.ml
@ -250,7 +250,8 @@ module Rt = struct
|
|||||||
before the call. Null until the first. *)
|
before the call. Null until the first. *)
|
||||||
let flanframe =
|
let flanframe =
|
||||||
{ sname = "flanframe";
|
{ sname = "flanframe";
|
||||||
fields = [ "prev", Ptr; "info", Ptr; "slots", Ptr; "at", Ptr ] }
|
fields = [ "prev", Ptr; "info", Ptr; "slots", Ptr; "at", Ptr;
|
||||||
|
"serial", I64 ] }
|
||||||
|
|
||||||
let align_up n a = (n + a - 1) / a * a
|
let align_up n a = (n + a - 1) / a * a
|
||||||
|
|
||||||
@ -4045,14 +4046,9 @@ and prim f (e : Tast.expr) (p : Tast.prim) (args : Tast.expr list) =
|
|||||||
Passing the header by value here would hand the runtime a
|
Passing the header by value here would hand the runtime a
|
||||||
copy to grow and leave the caller's untouched. *)
|
copy to grow and leave the caller's untouched. *)
|
||||||
| Types.Vec _ | Types.Map _ -> [ "ptr " ^ addr f a ]
|
| Types.Vec _ | Types.Map _ -> [ "ptr " ^ addr f a ]
|
||||||
(* A fixed array crossing into a dyn view (M2 item 3) needs its
|
(* A fixed array crosses by address, as a Vec or a Map does: a
|
||||||
address for the same reason a Vec or a Map does here — the
|
runtime entry point that takes one reads it in place. (A dyn
|
||||||
view reads through it live, and passing the value would hand
|
view is handed an explicit [addr_of] by check.ml's [box].) *)
|
||||||
the runtime a copy nothing writes back through. Every other
|
|
||||||
[Rt] caller of an array argument is [flan_dyn_view_flat],
|
|
||||||
which takes the address and never mutates the array's shape,
|
|
||||||
so this is not the move-only argument Vec/Map's comment is
|
|
||||||
about — it is simply the only way to view rather than copy. *)
|
|
||||||
| Types.Array _ -> [ "ptr " ^ addr f a ]
|
| Types.Array _ -> [ "ptr " ^ addr f a ]
|
||||||
| t -> [ ll t ^ " " ^ value f a ])
|
| t -> [ ll t ^ " " ^ value f a ])
|
||||||
args)
|
args)
|
||||||
@ -4478,6 +4474,13 @@ let emit_fn m ?(hidden = false) ?(pnames = []) (fn : Tast.fn) =
|
|||||||
"%%frame.a = getelementptr inbounds %%flanframe, ptr %%frame, i32 0, i32 %d"
|
"%%frame.a = getelementptr inbounds %%flanframe, ptr %%frame, i32 0, i32 %d"
|
||||||
(Rt.index Rt.flanframe "at");
|
(Rt.index Rt.flanframe "at");
|
||||||
"store ptr null, ptr %frame.a";
|
"store ptr null, ptr %frame.a";
|
||||||
|
(* Zeroed at every push: a dyn view of a local claims a number here
|
||||||
|
(runtime/flan_dev.c, [flan_dev_frame_claim]), and the next call
|
||||||
|
to land at this address must not inherit it. *)
|
||||||
|
Printf.sprintf
|
||||||
|
"%%frame.n = getelementptr inbounds %%flanframe, ptr %%frame, i32 0, i32 %d"
|
||||||
|
(Rt.index Rt.flanframe "serial");
|
||||||
|
"store i64 0, ptr %frame.n";
|
||||||
"store ptr %frame, ptr @flan_frame_head" ];
|
"store ptr %frame, ptr @flan_frame_head" ];
|
||||||
f.frame <- Some prev;
|
f.frame <- Some prev;
|
||||||
(* The parameters are bound before the body starts, so they are recorded
|
(* The parameters are bound before the body starts, so they are recorded
|
||||||
@ -5059,8 +5062,8 @@ declare i32 @flan_dyn_cast_kind(i64, ptr, i64, ptr, i64, i32)
|
|||||||
declare i32 @flan_dyn_is_nil(i64)
|
declare i32 @flan_dyn_is_nil(i64)
|
||||||
declare i64 @flan_dyn_need_not_nil(i64)
|
declare i64 @flan_dyn_need_not_nil(i64)
|
||||||
declare i32 @flan_dyn_truthy(i64)
|
declare i32 @flan_dyn_truthy(i64)
|
||||||
declare i64 @flan_dyn_view_vec(ptr, i32)
|
declare i64 @flan_dyn_view_slice(ptr, i64, ptr, i64, i32)
|
||||||
declare i64 @flan_dyn_view_flat(ptr, i64, i32)
|
declare i64 @flan_dyn_view_at(ptr, i64, ptr, i64, i32, i32)
|
||||||
declare void @flan_dyn_root_push(ptr)
|
declare void @flan_dyn_root_push(ptr)
|
||||||
declare void @flan_dyn_root_push_desc(ptr, ptr)
|
declare void @flan_dyn_root_push_desc(ptr, ptr)
|
||||||
declare ptr @flan_dyn_env_new(i64, ptr)
|
declare ptr @flan_dyn_env_new(i64, ptr)
|
||||||
|
|||||||
15
lib/x86.ml
15
lib/x86.ml
@ -1602,12 +1602,9 @@ let classify_c (l : loc) (t : Types.t) =
|
|||||||
| Types.String | Types.Slice _ -> [ Aint (l, Types.Ptr (Types.Mut, Types.Unit)); Alen l ]
|
| Types.String | Types.Slice _ -> [ Aint (l, Types.Ptr (Types.Mut, Types.Unit)); Alen l ]
|
||||||
| Types.Unit | Types.Never -> []
|
| Types.Unit | Types.Never -> []
|
||||||
| Types.Vec _ | Types.Map _ -> [ Aptr l ]
|
| Types.Vec _ | Types.Map _ -> [ Aptr l ]
|
||||||
(* A fixed array crossing into a dyn view (M2 item 3) needs its address for
|
(* A fixed array crosses by address, as a Vec or a Map does: a runtime
|
||||||
the same reason: the view reads through it live and a copy would leave
|
entry point that takes one reads it in place. (A dyn view is handed an
|
||||||
the caller's own array unseen by later writes through the view. Every
|
explicit [addr_of] by check.ml's [box].) *)
|
||||||
[Rt] call that takes an array argument is [flan_dyn_view_flat], which
|
|
||||||
never mutates the array's shape, so this is not the move-only case
|
|
||||||
Vec/Map is. *)
|
|
||||||
| Types.Array _ -> [ Aptr l ]
|
| Types.Array _ -> [ Aptr l ]
|
||||||
| _ when is_agg t ->
|
| _ when is_agg t ->
|
||||||
unsupported "aggregate %s across the C boundary" (Types.to_string t)
|
unsupported "aggregate %s across the C boundary" (Types.to_string t)
|
||||||
@ -4216,6 +4213,12 @@ let emit_fn (md : Emit.m) ~externs ~fns ?(ext = fun _ -> false)
|
|||||||
store_int f.b
|
store_int f.b
|
||||||
~src:rax ~mm:(Frame (fr + Emit.Rt.field Emit.Rt.flanframe "at"))
|
~src:rax ~mm:(Frame (fr + Emit.Rt.field Emit.Rt.flanframe "at"))
|
||||||
~size:8;
|
~size:8;
|
||||||
|
(* Zeroed at every push, as emit.ml's is: a dyn view of a local claims a
|
||||||
|
number here, and the next call to land at this address must not
|
||||||
|
inherit it. *)
|
||||||
|
store_int f.b
|
||||||
|
~src:rax ~mm:(Frame (fr + Emit.Rt.field Emit.Rt.flanframe "serial"))
|
||||||
|
~size:8;
|
||||||
lea f.b ~dst:rax ~mm:(Frame fr);
|
lea f.b ~dst:rax ~mm:(Frame fr);
|
||||||
store_int f.b ~src:rax ~mm:(lmem f head ~scratch:r11) ~size:8;
|
store_int f.b ~src:rax ~mm:(lmem f head ~scratch:r11) ~size:8;
|
||||||
(* The parameters are bound before the body starts, so they are recorded
|
(* The parameters are bound before the body starts, so they are recorded
|
||||||
|
|||||||
@ -1111,6 +1111,11 @@ typedef struct flan_frame {
|
|||||||
* from that call since — which is why [flan_dev_frame_at_loc] is read for
|
* from that call since — which is why [flan_dev_frame_at_loc] is read for
|
||||||
* the outer frames only. */
|
* the outer frames only. */
|
||||||
const char *at;
|
const char *at;
|
||||||
|
/* Zero at the push, and given a number from [flan_dev_frame_claim] the
|
||||||
|
* first time a dyn view is taken of this frame's storage. It is what tells
|
||||||
|
* this activation from the next call to land at the same address, which a
|
||||||
|
* view kept past the return would otherwise take for its own. */
|
||||||
|
uint64_t serial;
|
||||||
} flan_frame;
|
} flan_frame;
|
||||||
|
|
||||||
/* The compiler names this symbol directly. A redefinition module reaches it
|
/* The compiler names this symbol directly. A redefinition module reaches it
|
||||||
@ -1134,6 +1139,35 @@ void flan_dev_frames_reset(void) { flan_frame_head = NULL; }
|
|||||||
void *flan_dev_frames_mark(void) { return flan_frame_head; }
|
void *flan_dev_frames_mark(void) { return flan_frame_head; }
|
||||||
void flan_dev_frames_restore(void *head) { flan_frame_head = (flan_frame *)head; }
|
void flan_dev_frames_restore(void *head) { flan_frame_head = (flan_frame *)head; }
|
||||||
|
|
||||||
|
/* A dyn view of a local (flan_dyn.c, [view_make]): the frame's serial, made
|
||||||
|
* on first use, and the function's name for the sentence a stale view prints
|
||||||
|
* — copied out now, because by then the frame is dead stack. */
|
||||||
|
static uint64_t flan_frame_serials;
|
||||||
|
|
||||||
|
uint64_t flan_dev_frame_claim(void *frame, const char **name,
|
||||||
|
int64_t *namelen) {
|
||||||
|
flan_frame *f = frame;
|
||||||
|
*name = "?";
|
||||||
|
*namelen = 1;
|
||||||
|
if (f == NULL) return 0;
|
||||||
|
if (f->info != NULL) {
|
||||||
|
*name = f->info->name;
|
||||||
|
*namelen = f->info->namelen;
|
||||||
|
}
|
||||||
|
if (f->serial == 0) f->serial = ++flan_frame_serials;
|
||||||
|
return f->serial;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Still on the chain, and still the same activation. The walk is from the
|
||||||
|
* innermost frame out and is the depth of the stack at worst; dead stack
|
||||||
|
* keeps its old bytes, so the serial alone cannot say the frame is gone. */
|
||||||
|
int32_t flan_dev_frame_alive(const void *frame, uint64_t serial) {
|
||||||
|
const flan_frame *f;
|
||||||
|
for (f = flan_frame_head; f != NULL; f = f->prev)
|
||||||
|
if (f == frame) return f->serial == serial;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* [i] counts from the innermost. NULL past the end, which is how a caller
|
/* [i] counts from the innermost. NULL past the end, which is how a caller
|
||||||
* learns the depth without a second walk. */
|
* learns the depth without a second walk. */
|
||||||
void *flan_dev_frame_at(int32_t i) {
|
void *flan_dev_frame_at(int32_t i) {
|
||||||
@ -1775,6 +1809,50 @@ int32_t flan_dev_reg_live(const void *p) {
|
|||||||
return (int32_t)(e != NULL && e->died == 0 ? 1 : 0);
|
return (int32_t)(e != NULL && e->died == 0 ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* A dyn view's storage (flan_dyn.c, [view_make]): the smallest live block
|
||||||
|
* holding [p], by base and sequence, so the view can ask later whether that
|
||||||
|
* same note is still alive. The smallest, because an arena's own region can
|
||||||
|
* be a block too, and it outlives the allocation inside it that a free-all
|
||||||
|
* ends. 0 when no live block holds [p] — a global, a frame, C memory — and
|
||||||
|
* always in a release build. A scan, once per crossing, in a dev build. */
|
||||||
|
int32_t flan_dev_reg_claim(const void *p, uintptr_t *base, int64_t *seq,
|
||||||
|
const char **type, int64_t *typelen) {
|
||||||
|
uintptr_t a = (uintptr_t)p;
|
||||||
|
flan_reg_entry *best = NULL;
|
||||||
|
int64_t i;
|
||||||
|
if (!flan_reg_on || a == 0) return 0;
|
||||||
|
for (i = 0; i < FLAN_REG_CAP; i++) {
|
||||||
|
flan_reg_entry *e = &flan_reg[i];
|
||||||
|
if (e->base == 0 || e->died != 0) continue;
|
||||||
|
if (a < e->base || a >= e->base + (uintptr_t)e->bytes) continue;
|
||||||
|
if (best == NULL || e->bytes < best->bytes) best = e;
|
||||||
|
}
|
||||||
|
if (best == NULL) return 0;
|
||||||
|
*base = best->base;
|
||||||
|
*seq = best->seq;
|
||||||
|
*type = best->type;
|
||||||
|
*typelen = best->typelen;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Is the note [flan_dev_reg_claim] found still there and alive? The probe a
|
||||||
|
* free takes, keyed on the base: an entry is dropped only when its address is
|
||||||
|
* handed out again, and the new note has a new sequence. A compaction moves
|
||||||
|
* entries but keeps both. */
|
||||||
|
int32_t flan_dev_reg_alive(uintptr_t base, int64_t seq) {
|
||||||
|
size_t s;
|
||||||
|
int64_t probe;
|
||||||
|
if (!flan_reg_on || base == 0) return 1;
|
||||||
|
s = flan_reg_slot(base);
|
||||||
|
for (probe = 0; probe < FLAN_REG_CAP; probe++) {
|
||||||
|
size_t j = (s + (size_t)probe) & (FLAN_REG_CAP - 1);
|
||||||
|
if (flan_reg[j].base == 0) return 0;
|
||||||
|
if (flan_reg[j].base != base || flan_reg[j].seq != seq) continue;
|
||||||
|
return flan_reg[j].died == 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* What was at this address, in words, for the branch that may not follow it.
|
/* What was at this address, in words, for the branch that may not follow it.
|
||||||
* Emitted straight into the result buffer rather than returned: the caller is
|
* Emitted straight into the result buffer rather than returned: the caller is
|
||||||
* a render thunk, which has no allocator, and every other piece of a rendering
|
* a render thunk, which has no allocator, and every other piece of a rendering
|
||||||
|
|||||||
@ -355,15 +355,17 @@ typedef struct flan_obj {
|
|||||||
entries and [cap] counting entries too. Sharing the arm is what lets the
|
entries and [cap] counting entries too. Sharing the arm is what lets the
|
||||||
marker and the sweep treat the two kinds with one load and a doubled
|
marker and the sweep treat the two kinds with one load and a doubled
|
||||||
count rather than a second field to keep in step. */
|
count rather than a second field to keep in step. */
|
||||||
/* OBJ_VIEW: a typed container's elements, native words this file did not
|
/* OBJ_VIEW: typed storage this file did not allocate and does not own.
|
||||||
allocate and does not own. [is_vec] set means [base] is a
|
The shape is in the header's [gen], which only a class instance
|
||||||
[flan_dyn_vec_hdr *] and [len] here is unused — the live length is
|
otherwise uses: VIEW_VEC means [base] is a [flan_dyn_vec_hdr *] whose
|
||||||
read from the header on every operation, which is the whole of why a
|
length is read live, which is why a Vec growing through the view
|
||||||
Vec growing through the view cannot go stale. [is_vec] clear means
|
cannot go stale; VIEW_FLAT means [base] is the first element and the
|
||||||
[base] is the first element's address and [len] is the snapshot taken
|
header's [len] the count taken at the crossing, for a slice or a fixed
|
||||||
at the crossing, for a slice or a fixed array, neither of which moves.
|
array; VIEW_STRUCT means [base] is one struct. [desc] is the element's
|
||||||
[elem] is one of FLAN_VIEW_I64/F64/BOOL. */
|
descriptor, or the struct's. [nul] is always NULL and sits where a
|
||||||
struct { void *base; int64_t len; int32_t elem; int32_t is_vec; } view;
|
map's [klass] does, so a struct view answers "no class" to every class
|
||||||
|
question without a branch. A [view_guard] trails the header. */
|
||||||
|
struct { void *base; const uint8_t *desc; void *nul; } view;
|
||||||
/* OBJ_ENV: the descriptor the environment's bytes are marked through,
|
/* OBJ_ENV: the descriptor the environment's bytes are marked through,
|
||||||
or NULL when it holds nothing the collector follows. [len] is the
|
or NULL when it holds nothing the collector follows. [len] is the
|
||||||
byte count, and the bytes trail the header as a text's do. */
|
byte count, and the bytes trail the header as a text's do. */
|
||||||
@ -520,7 +522,9 @@ int32_t flan_dyn_tag(flan_dyn v) {
|
|||||||
side there is nothing to tell them apart by, which is the point of a
|
side there is nothing to tell them apart by, which is the point of a
|
||||||
view being indistinguishable rather than a fourth kind of vec. */
|
view being indistinguishable rather than a fourth kind of vec. */
|
||||||
case OBJ_VEC: return FLAN_DYN_TAG_VEC;
|
case OBJ_VEC: return FLAN_DYN_TAG_VEC;
|
||||||
case OBJ_VIEW: return FLAN_DYN_TAG_VEC;
|
/* ...and a struct's view answers a map's, [gen] being its shape
|
||||||
|
(VIEW_STRUCT, below). */
|
||||||
|
case OBJ_VIEW: return o->gen == 2 ? FLAN_DYN_TAG_MAP : FLAN_DYN_TAG_VEC;
|
||||||
case OBJ_MAP: return FLAN_DYN_TAG_MAP;
|
case OBJ_MAP: return FLAN_DYN_TAG_MAP;
|
||||||
default: return FLAN_DYN_TAG_INT;
|
default: return FLAN_DYN_TAG_INT;
|
||||||
}
|
}
|
||||||
@ -631,9 +635,11 @@ static double dyn_num_value(flan_dyn v);
|
|||||||
* they are defined, alongside the container operations below */
|
* they are defined, alongside the container operations below */
|
||||||
static int64_t view_len(const uint8_t *loc, int64_t loclen, const char *op,
|
static int64_t view_len(const uint8_t *loc, int64_t loclen, const char *op,
|
||||||
flan_obj *o);
|
flan_obj *o);
|
||||||
static void *view_base(flan_obj *o);
|
/* A struct view's fields, for the map arms of the printers. */
|
||||||
static flan_dyn view_box(int32_t elem, const uint8_t *p);
|
static int64_t view_nfields(flan_obj *o);
|
||||||
static int64_t view_elem_size(int32_t elem);
|
static flan_dyn view_field_key(flan_obj *o, int64_t i);
|
||||||
|
static flan_dyn view_field_val(flan_obj *o, int64_t i);
|
||||||
|
static void view_struct_name(flan_obj *o, const char **name, int64_t *len);
|
||||||
|
|
||||||
/* forward: needed by [dyn_equal] below, defined alongside the view helpers
|
/* forward: needed by [dyn_equal] below, defined alongside the view helpers
|
||||||
* further down — a length and an element reader that answer correctly
|
* further down — a length and an element reader that answer correctly
|
||||||
@ -687,6 +693,24 @@ static void render(dyn_sink w, flan_dyn v, int depth, int nested) {
|
|||||||
case FLAN_DYN_TAG_MAP: {
|
case FLAN_DYN_TAG_MAP: {
|
||||||
flan_obj *o = dyn_obj(v);
|
flan_obj *o = dyn_obj(v);
|
||||||
int64_t i;
|
int64_t i;
|
||||||
|
/* A struct's view prints as the class instance it most resembles: its
|
||||||
|
* type's name as the shape tag, then its fields in declaration order. */
|
||||||
|
if (o->kind == OBJ_VIEW) {
|
||||||
|
const char *nm;
|
||||||
|
int64_t nl, n = view_nfields(o);
|
||||||
|
view_struct_name(o, &nm, &nl);
|
||||||
|
emit(w, "#");
|
||||||
|
emit_n(w, (const uint8_t *)nm, nl);
|
||||||
|
emit(w, "{");
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
if (i > 0) emit(w, " ");
|
||||||
|
render(w, view_field_key(o, i), depth + 1, 1);
|
||||||
|
emit(w, " ");
|
||||||
|
render(w, view_field_val(o, i), depth + 1, 1);
|
||||||
|
}
|
||||||
|
emit(w, "}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
/* A class instance prints its shape tag in front, Clojure's own spelling
|
/* A class instance prints its shape tag in front, Clojure's own spelling
|
||||||
* for a record: #point{:x 1 :y 2}. The tag is not an entry, so it is
|
* for a record: #point{:x 1 :y 2}. The tag is not an entry, so it is
|
||||||
* written here or it is not written at all. */
|
* written here or it is not written at all. */
|
||||||
@ -706,17 +730,11 @@ static void render(dyn_sink w, flan_dyn v, int depth, int nested) {
|
|||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
flan_obj *o = dyn_obj(v);
|
flan_obj *o = dyn_obj(v);
|
||||||
int64_t i, n = o->kind == OBJ_VIEW ? view_len(NULL, 0, "print", o) : o->len;
|
int64_t i, n = vecish_len(o);
|
||||||
emit(w, "[");
|
emit(w, "[");
|
||||||
for (i = 0; i < n; i++) {
|
for (i = 0; i < n; i++) {
|
||||||
if (i > 0) emit(w, " ");
|
if (i > 0) emit(w, " ");
|
||||||
if (o->kind == OBJ_VIEW)
|
render(w, vecish_at(o, i), depth + 1, 1);
|
||||||
render(w, view_box(o->u.view.elem,
|
|
||||||
(const uint8_t *)view_base(o)
|
|
||||||
+ i * view_elem_size(o->u.view.elem)),
|
|
||||||
depth + 1, 1);
|
|
||||||
else
|
|
||||||
render(w, o->u.v.items[i], depth + 1, 1);
|
|
||||||
}
|
}
|
||||||
emit(w, "]");
|
emit(w, "]");
|
||||||
return;
|
return;
|
||||||
@ -802,6 +820,27 @@ static void say_render(sayer *s, flan_dyn v, int depth) {
|
|||||||
flan_obj *o = dyn_obj(v);
|
flan_obj *o = dyn_obj(v);
|
||||||
int64_t i;
|
int64_t i;
|
||||||
if (depth >= 2) { say_puts(s, "{...}"); return; }
|
if (depth >= 2) { say_puts(s, "{...}"); return; }
|
||||||
|
if (o->kind == OBJ_VIEW) {
|
||||||
|
const char *nm;
|
||||||
|
int64_t nl, n = view_nfields(o), j;
|
||||||
|
view_struct_name(o, &nm, &nl);
|
||||||
|
say_puts(s, "#");
|
||||||
|
for (j = 0; j < nl && s->n < s->cap - 8; j++) {
|
||||||
|
char c[2];
|
||||||
|
c[0] = nm[j];
|
||||||
|
c[1] = '\0';
|
||||||
|
say_puts(s, c);
|
||||||
|
}
|
||||||
|
say_puts(s, "{");
|
||||||
|
for (i = 0; i < n && s->n < s->cap - 8; i++) {
|
||||||
|
if (i > 0) say_puts(s, " ");
|
||||||
|
say_render(s, view_field_key(o, i), depth + 1);
|
||||||
|
say_puts(s, " ");
|
||||||
|
say_render(s, view_field_val(o, i), depth + 1);
|
||||||
|
}
|
||||||
|
say_puts(s, i == n ? "}" : i > 0 ? " ...}" : "...}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
/* The same tag [render] writes, so a trap sentence naming an instance
|
/* The same tag [render] writes, so a trap sentence naming an instance
|
||||||
* says which class it was. Truncated with the rest when the buffer is
|
* says which class it was. Truncated with the rest when the buffer is
|
||||||
* short: [say] is a 96-byte sentence, not a printer. */
|
* short: [say] is a 96-byte sentence, not a printer. */
|
||||||
@ -827,19 +866,12 @@ static void say_render(sayer *s, flan_dyn v, int depth) {
|
|||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
flan_obj *o = dyn_obj(v);
|
flan_obj *o = dyn_obj(v);
|
||||||
int64_t i, n = o->kind == OBJ_VIEW ? view_len(NULL, 0, "print", o) : o->len;
|
int64_t i, n = vecish_len(o);
|
||||||
if (depth >= 2) { say_puts(s, "[...]"); return; }
|
if (depth >= 2) { say_puts(s, "[...]"); return; }
|
||||||
say_puts(s, "[");
|
say_puts(s, "[");
|
||||||
for (i = 0; i < n && s->n < s->cap - 8; i++) {
|
for (i = 0; i < n && s->n < s->cap - 8; i++) {
|
||||||
if (i > 0) say_puts(s, " ");
|
if (i > 0) say_puts(s, " ");
|
||||||
if (o->kind == OBJ_VIEW)
|
say_render(s, vecish_at(o, i), depth + 1);
|
||||||
say_render(s,
|
|
||||||
view_box(o->u.view.elem,
|
|
||||||
(const uint8_t *)view_base(o)
|
|
||||||
+ i * view_elem_size(o->u.view.elem)),
|
|
||||||
depth + 1);
|
|
||||||
else
|
|
||||||
say_render(s, o->u.v.items[i], depth + 1);
|
|
||||||
}
|
}
|
||||||
say_puts(s, i == n ? "]" : i > 0 ? " ...]" : "...]");
|
say_puts(s, i == n ? "]" : i > 0 ? " ...]" : "...]");
|
||||||
return;
|
return;
|
||||||
@ -2810,6 +2842,23 @@ static int dyn_equal(flan_dyn a, flan_dyn b, int depth) {
|
|||||||
int64_t i, j;
|
int64_t i, j;
|
||||||
if (x == y) return 1;
|
if (x == y) return 1;
|
||||||
if (depth >= EQ_DEPTH) return 0;
|
if (depth >= EQ_DEPTH) return 0;
|
||||||
|
/* A struct's view is equal to another view of the same struct type with
|
||||||
|
equal fields, and to nothing else — the answer an instance gets beside
|
||||||
|
a plain map, for the same reason: the type's name is its shape tag. */
|
||||||
|
if (x->kind == OBJ_VIEW || y->kind == OBJ_VIEW) {
|
||||||
|
const char *xn, *yn;
|
||||||
|
int64_t xl, yl, n;
|
||||||
|
if (x->kind != OBJ_VIEW || y->kind != OBJ_VIEW) return 0;
|
||||||
|
view_struct_name(x, &xn, &xl);
|
||||||
|
view_struct_name(y, &yn, &yl);
|
||||||
|
if (xl != yl || memcmp(xn, yn, (size_t)xl) != 0) return 0;
|
||||||
|
n = view_nfields(x);
|
||||||
|
if (n != view_nfields(y)) return 0;
|
||||||
|
for (i = 0; i < n; i++)
|
||||||
|
if (!dyn_equal(view_field_val(x, i), view_field_val(y, i), depth + 1))
|
||||||
|
return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
/* Two instances of one class built either side of a redefinition hold
|
/* Two instances of one class built either side of a redefinition hold
|
||||||
different key sets, and comparing those key sets would answer "not
|
different key sets, and comparing those key sets would answer "not
|
||||||
equal" about a difference the class no longer has. So both are brought
|
equal" about a difference the class no longer has. So both are brought
|
||||||
@ -2860,14 +2909,273 @@ static inline int is_map(flan_dyn v) {
|
|||||||
|
|
||||||
/* ── Typed containers as views ─────────────────────────────────────────
|
/* ── Typed containers as views ─────────────────────────────────────────
|
||||||
*
|
*
|
||||||
* Every entry point below already dispatches on [flan_dyn_tag], which does
|
* Every entry point below already dispatches on [flan_dyn_tag]. A view over
|
||||||
* not distinguish a view from a heap vec — see [flan_dyn_tag]'s switch — so
|
* a Vec, a slice or a fixed array answers the vec tag and a view over a
|
||||||
* [flan_dyn_len], [flan_dyn_at], [flan_dyn_set_at], [flan_dyn_push] and the
|
* struct answers the map tag, so [flan_dyn_len], [flan_dyn_at],
|
||||||
* printer each add one branch for [OBJ_VIEW] beside the existing [OBJ_VEC]
|
* [flan_dyn_set_at], [flan_dyn_push], the map operations and the printers
|
||||||
* one. What follows is that branch's machinery. */
|
* each add one branch for [OBJ_VIEW]. What follows is that branch's
|
||||||
|
* machinery.
|
||||||
|
*
|
||||||
|
* What one element is comes from a descriptor the compiler writes into
|
||||||
|
* read-only data (lib/check.ml, [view_desc]), a prefix code:
|
||||||
|
*
|
||||||
|
* b B h H i I l L i8 u8 i16 u16 i32 u32 i64 u64
|
||||||
|
* f d ? f32 f64 bool
|
||||||
|
* t str (read as a copy; never written from here)
|
||||||
|
* a<n>;T a fixed [n T]
|
||||||
|
* sT a slice [T]
|
||||||
|
* vT a (Vec T)
|
||||||
|
* {Name;f1;T1f2;T2} a struct, its fields in declaration order
|
||||||
|
*
|
||||||
|
* Offsets are computed here by C's rule, the one Emit.lay spells: every
|
||||||
|
* scalar aligned to its size, a struct to its strictest field and padded to
|
||||||
|
* it, an array adding no padding of its own. test/programs/dyn-view-any.flan
|
||||||
|
* reads back a struct whose fields sit at offsets only that rule gets right,
|
||||||
|
* on both backends.
|
||||||
|
*
|
||||||
|
* A view never stores a collector pointer into typed storage: a read boxes
|
||||||
|
* (or copies, for a str), and a write unboxes a number or a bool. That is
|
||||||
|
* why a str element is read-only from here, and why an aggregate element is
|
||||||
|
* written through its own view rather than replaced whole. */
|
||||||
|
|
||||||
static int64_t view_elem_size(int32_t elem) {
|
static int64_t desc_int(const uint8_t **p) {
|
||||||
return elem == FLAN_VIEW_BOOL ? 1 : 8;
|
int64_t n = 0;
|
||||||
|
while (**p >= '0' && **p <= '9') { n = n * 10 + (**p - '0'); (*p)++; }
|
||||||
|
if (**p == ';') (*p)++;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Past a name and its ';'. */
|
||||||
|
static const uint8_t *desc_name_end(const uint8_t *d) {
|
||||||
|
while (*d != ';') d++;
|
||||||
|
return d + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const uint8_t *desc_skip(const uint8_t *d) {
|
||||||
|
switch (*d) {
|
||||||
|
case 'a': d++; desc_int(&d); return desc_skip(d);
|
||||||
|
case 's': case 'v': return desc_skip(d + 1);
|
||||||
|
case '{':
|
||||||
|
d = desc_name_end(d + 1);
|
||||||
|
while (*d != '}') d = desc_skip(desc_name_end(d));
|
||||||
|
return d + 1;
|
||||||
|
default: return d + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t align_to(int64_t n, int64_t a) { return (n + a - 1) / a * a; }
|
||||||
|
|
||||||
|
static void desc_lay(const uint8_t *d, int64_t *size, int64_t *align) {
|
||||||
|
switch (*d) {
|
||||||
|
case 'b': case 'B': case '?': *size = 1; *align = 1; return;
|
||||||
|
case 'h': case 'H': *size = 2; *align = 2; return;
|
||||||
|
case 'i': case 'I': case 'f': *size = 4; *align = 4; return;
|
||||||
|
case 't': case 's': *size = 16; *align = 8; return;
|
||||||
|
case 'v': *size = 40; *align = 8; return;
|
||||||
|
case 'a': {
|
||||||
|
int64_t n, s, a;
|
||||||
|
d++;
|
||||||
|
n = desc_int(&d);
|
||||||
|
desc_lay(d, &s, &a);
|
||||||
|
*size = n * s;
|
||||||
|
*align = a;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case '{': {
|
||||||
|
int64_t off = 0, al = 1, s, a;
|
||||||
|
d = desc_name_end(d + 1);
|
||||||
|
while (*d != '}') {
|
||||||
|
d = desc_name_end(d);
|
||||||
|
desc_lay(d, &s, &a);
|
||||||
|
if (a < 1) a = 1;
|
||||||
|
off = align_to(off, a) + s;
|
||||||
|
if (a > al) al = a;
|
||||||
|
d = desc_skip(d);
|
||||||
|
}
|
||||||
|
*size = align_to(off, al);
|
||||||
|
*align = al;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default: *size = 8; *align = 8; return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t desc_size(const uint8_t *d) {
|
||||||
|
int64_t s, a;
|
||||||
|
desc_lay(d, &s, &a);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A struct descriptor's fields, one at a time: [desc_fields] answers where
|
||||||
|
* the first one starts, and each [desc_next] answers a field's name, offset
|
||||||
|
* and type and steps past it, 0 at the closing brace. [*off] is the running
|
||||||
|
* offset. */
|
||||||
|
static const uint8_t *desc_fields(const uint8_t *d, int64_t *off) {
|
||||||
|
*off = 0;
|
||||||
|
return desc_name_end(d + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int desc_next(const uint8_t **at, int64_t *off, const uint8_t **name,
|
||||||
|
int64_t *namelen, int64_t *foff, const uint8_t **fty) {
|
||||||
|
const uint8_t *d = *at;
|
||||||
|
int64_t s, a;
|
||||||
|
if (*d == '}') return 0;
|
||||||
|
*name = d;
|
||||||
|
while (*d != ';') d++;
|
||||||
|
*namelen = (int64_t)(d - *name);
|
||||||
|
d++;
|
||||||
|
desc_lay(d, &s, &a);
|
||||||
|
if (a < 1) a = 1;
|
||||||
|
*foff = align_to(*off, a);
|
||||||
|
*off = *foff + s;
|
||||||
|
*fty = d;
|
||||||
|
*at = desc_skip(d);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The field keyword [k] names, or NULL. */
|
||||||
|
static const uint8_t *desc_field(const uint8_t *d, flan_dyn k, int64_t *foff) {
|
||||||
|
const uint8_t *at, *name, *fty;
|
||||||
|
int64_t off, namelen;
|
||||||
|
kw_entry *kw;
|
||||||
|
if (flan_dyn_tag(k) != FLAN_DYN_TAG_KEYWORD) return NULL;
|
||||||
|
kw = dyn_kw(k);
|
||||||
|
at = desc_fields(d, &off);
|
||||||
|
while (desc_next(&at, &off, &name, &namelen, foff, &fty))
|
||||||
|
if (namelen == kw->len && memcmp(name, kw_bytes(kw), (size_t)namelen) == 0)
|
||||||
|
return fty;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t desc_nfields(const uint8_t *d) {
|
||||||
|
const uint8_t *at, *name, *fty;
|
||||||
|
int64_t off, namelen, foff, n = 0;
|
||||||
|
at = desc_fields(d, &off);
|
||||||
|
while (desc_next(&at, &off, &name, &namelen, &foff, &fty)) n++;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The Flan spelling of a descriptor's type, for a sentence. */
|
||||||
|
static void desc_spell(const uint8_t *d, char *buf, size_t cap) {
|
||||||
|
static const char scalars[] = "bBhHiIlLfd?t";
|
||||||
|
static const char *const words[] = { "i8", "u8", "i16", "u16", "i32", "u32",
|
||||||
|
"i64", "u64", "f32", "f64", "bool",
|
||||||
|
"str" };
|
||||||
|
const char *w;
|
||||||
|
char inner[96];
|
||||||
|
if (cap == 0) return;
|
||||||
|
buf[0] = '\0';
|
||||||
|
if (*d != '\0' && (w = strchr(scalars, *d)) != NULL) {
|
||||||
|
snprintf(buf, cap, "%s", words[w - scalars]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (*d) {
|
||||||
|
case 'a': {
|
||||||
|
int64_t n;
|
||||||
|
d++;
|
||||||
|
n = desc_int(&d);
|
||||||
|
desc_spell(d, inner, sizeof inner);
|
||||||
|
snprintf(buf, cap, "[%lld %s]", (long long)n, inner);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 's':
|
||||||
|
desc_spell(d + 1, inner, sizeof inner);
|
||||||
|
snprintf(buf, cap, "[%s]", inner);
|
||||||
|
return;
|
||||||
|
case 'v':
|
||||||
|
desc_spell(d + 1, inner, sizeof inner);
|
||||||
|
snprintf(buf, cap, "(Vec %s)", inner);
|
||||||
|
return;
|
||||||
|
case '{': {
|
||||||
|
const uint8_t *e = desc_name_end(d + 1);
|
||||||
|
snprintf(buf, cap, "%.*s", (int)(e - d - 2), (const char *)d + 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default: snprintf(buf, cap, "?"); return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* What a dev build remembers of a view's storage at the crossing, and checks
|
||||||
|
* before every read or write through it (runtime/flan_dev.c keeps both
|
||||||
|
* tables):
|
||||||
|
*
|
||||||
|
* a frame the shadow frame the storage belongs to and the serial that
|
||||||
|
* activation was given. Live while that frame is still on the
|
||||||
|
* chain with the same serial: a later call landing at the same
|
||||||
|
* address gets a different one.
|
||||||
|
* a block the registry entry of the heap or arena block holding the
|
||||||
|
* storage, by base and note sequence. Live while the entry is,
|
||||||
|
* which a free, a free-all, an arena's destroy and a Vec's
|
||||||
|
* growth (for the block it left) all end.
|
||||||
|
*
|
||||||
|
* A release build keeps neither table, so a view there records nothing and
|
||||||
|
* checks nothing. Storage neither table knows — a global, rodata, C memory,
|
||||||
|
* a caller's local reached through a slice parameter — is not checked. */
|
||||||
|
typedef struct view_guard {
|
||||||
|
const void *frame; /* NULL when no frame is checked */
|
||||||
|
uint64_t serial;
|
||||||
|
const char *fname; /* whose frame, for the sentence */
|
||||||
|
int64_t fnamelen;
|
||||||
|
uintptr_t rbase; /* 0 when no block is checked */
|
||||||
|
int64_t rseq;
|
||||||
|
const char *rtype;
|
||||||
|
int64_t rtypelen;
|
||||||
|
} view_guard;
|
||||||
|
|
||||||
|
uint64_t flan_dev_frame_claim(void *frame, const char **name, int64_t *namelen);
|
||||||
|
int32_t flan_dev_frame_alive(const void *frame, uint64_t serial);
|
||||||
|
int32_t flan_dev_reg_claim(const void *p, uintptr_t *base, int64_t *seq,
|
||||||
|
const char **type, int64_t *typelen);
|
||||||
|
int32_t flan_dev_reg_alive(uintptr_t base, int64_t seq);
|
||||||
|
extern void *flan_frame_head;
|
||||||
|
|
||||||
|
#define VIEW_FLAT 0 /* [base] is the first element, [o->len] the count */
|
||||||
|
#define VIEW_VEC 1 /* [base] is a Vec's header, read live */
|
||||||
|
#define VIEW_STRUCT 2 /* [base] is the struct, [desc] the struct's own */
|
||||||
|
|
||||||
|
static inline view_guard *view_g(flan_obj *o) { return (view_guard *)(o + 1); }
|
||||||
|
static inline int view_shape(flan_obj *o) { return (int)o->gen; }
|
||||||
|
|
||||||
|
static void guard_block(view_guard *g, const void *p) {
|
||||||
|
g->rbase = 0;
|
||||||
|
if (p != NULL)
|
||||||
|
flan_dev_reg_claim(p, &g->rbase, &g->rseq, &g->rtype, &g->rtypelen);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A new view record, its guard empty. */
|
||||||
|
static flan_obj *view_new(void *base, int64_t len, const uint8_t *desc,
|
||||||
|
int shape) {
|
||||||
|
flan_obj *o = gc_alloc(OBJ_VIEW, (int64_t)sizeof(view_guard));
|
||||||
|
o->u.view.base = base;
|
||||||
|
o->u.view.desc = desc;
|
||||||
|
o->u.view.nul = NULL;
|
||||||
|
o->gen = (uint32_t)shape;
|
||||||
|
o->len = len;
|
||||||
|
memset(view_g(o), 0, sizeof(view_guard));
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The stale-storage check. The sentence never renders the view: it has just
|
||||||
|
* been found to point at storage that is gone, and rendering reads it. */
|
||||||
|
static void view_guard_check(const uint8_t *loc, int64_t loclen,
|
||||||
|
const char *op, flan_obj *o) {
|
||||||
|
view_guard *g = view_g(o);
|
||||||
|
if (g->frame != NULL && !flan_dev_frame_alive(g->frame, g->serial)) {
|
||||||
|
flan_say(loc, loclen,
|
||||||
|
"dyn %s: this view points into a local of %.*s, and that call "
|
||||||
|
"has returned. A view of a local lasts as long as the call that "
|
||||||
|
"made it",
|
||||||
|
op, (int)g->fnamelen, g->fname);
|
||||||
|
flan_trap((const uint8_t *)"DynStale", 8);
|
||||||
|
}
|
||||||
|
if (g->rbase != 0 && !flan_dev_reg_alive(g->rbase, g->rseq)) {
|
||||||
|
flan_say(loc, loclen,
|
||||||
|
"dyn %s: this view's storage, a block of %.*s, has been "
|
||||||
|
"released — freed, cleared by free-all, or left behind when a "
|
||||||
|
"Vec grew. Take the view again after the change",
|
||||||
|
op, (int)g->rtypelen, g->rtype);
|
||||||
|
flan_trap((const uint8_t *)"DynStale", 8);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The stale-container check flan_rt.c's [flan_vec_check] runs for a typed
|
/* The stale-container check flan_rt.c's [flan_vec_check] runs for a typed
|
||||||
@ -2875,17 +3183,8 @@ static int64_t view_elem_size(int32_t elem) {
|
|||||||
* doctrine's dyn side gets its own spelling (docs/SPIKE-DUPLICITY.md), and a
|
* doctrine's dyn side gets its own spelling (docs/SPIKE-DUPLICITY.md), and a
|
||||||
* dyn program that hits this wants the same park-and-inspect [flan_trap]
|
* dyn program that hits this wants the same park-and-inspect [flan_trap]
|
||||||
* gives every other dyn mistake, not the typed side's [rt_die]. A Vec with
|
* gives every other dyn mistake, not the typed side's [rt_die]. A Vec with
|
||||||
* no allocator yet — one nobody has pushed to — has nothing to check.
|
* no allocator yet — one nobody has pushed to — has nothing to check. Like
|
||||||
*
|
* the guard's, the sentence never renders the view. */
|
||||||
* The message never renders the view it just declared unsafe to read —
|
|
||||||
* review's second finding, and it was not a decoration this dropped for
|
|
||||||
* safety's sake, it was a real infinite recursion: [say] on a view calls
|
|
||||||
* [say_render]'s view branch, which calls [view_len], which calls back in
|
|
||||||
* here, unconditionally, because the epoch is still stale. Every render of
|
|
||||||
* this same view would hit the same check and take the same branch, so
|
|
||||||
* nothing about depth or a visited set closes it — the fix is that a
|
|
||||||
* stale-container check must never read the container it has just refused
|
|
||||||
* to trust, not even to describe it in the sentence explaining why. */
|
|
||||||
static void view_vec_check(const uint8_t *loc, int64_t loclen, const char *op,
|
static void view_vec_check(const uint8_t *loc, int64_t loclen, const char *op,
|
||||||
flan_dyn_vec_hdr *h) {
|
flan_dyn_vec_hdr *h) {
|
||||||
if (h->alloc) {
|
if (h->alloc) {
|
||||||
@ -2902,104 +3201,300 @@ static void view_vec_check(const uint8_t *loc, int64_t loclen, const char *op,
|
|||||||
|
|
||||||
/* [len] and [base], read live for a Vec view (so a push that grows and
|
/* [len] and [base], read live for a Vec view (so a push that grows and
|
||||||
* moves the underlying Vec is seen the very next operation) and read from
|
* moves the underlying Vec is seen the very next operation) and read from
|
||||||
* the snapshot for a flat one. */
|
* the snapshot for a flat one. [view_len] checks the guard first. */
|
||||||
static int64_t view_len(const uint8_t *loc, int64_t loclen, const char *op,
|
static int64_t view_len(const uint8_t *loc, int64_t loclen, const char *op,
|
||||||
flan_obj *o) {
|
flan_obj *o) {
|
||||||
if (o->u.view.is_vec) {
|
view_guard_check(loc, loclen, op, o);
|
||||||
|
if (view_shape(o) == VIEW_VEC) {
|
||||||
flan_dyn_vec_hdr *h = (flan_dyn_vec_hdr *)o->u.view.base;
|
flan_dyn_vec_hdr *h = (flan_dyn_vec_hdr *)o->u.view.base;
|
||||||
view_vec_check(loc, loclen, op, h);
|
view_vec_check(loc, loclen, op, h);
|
||||||
return h->len;
|
return h->len;
|
||||||
}
|
}
|
||||||
return o->u.view.len;
|
return o->len;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *view_base(flan_obj *o) {
|
static void *view_base(flan_obj *o) {
|
||||||
if (o->u.view.is_vec) return ((flan_dyn_vec_hdr *)o->u.view.base)->ptr;
|
if (view_shape(o) == VIEW_VEC) return ((flan_dyn_vec_hdr *)o->u.view.base)->ptr;
|
||||||
return o->u.view.base;
|
return o->u.view.base;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reads box the element on the way out — the runtime already knows how to
|
/* A view of the aggregate element at [p], inside [parent]'s storage. An
|
||||||
* box an i64, an f64 or a bool, so this is that, from raw bytes rather than
|
* element of a Vec is checked against the Vec's block, since the Vec's
|
||||||
* from a C value already in hand. */
|
* growth is what would leave it behind; any other inline element shares its
|
||||||
static flan_dyn view_box(int32_t elem, const uint8_t *p) {
|
* parent's guard. A slice element's data is somewhere else, so it is looked
|
||||||
switch (elem) {
|
* up afresh. */
|
||||||
case FLAN_VIEW_I64: { int64_t x; memcpy(&x, p, 8); return flan_dyn_from_i64(x); }
|
static flan_dyn view_child(flan_obj *parent, const uint8_t *d, uint8_t *p) {
|
||||||
case FLAN_VIEW_F64: { double x; memcpy(&x, p, 8); return flan_dyn_from_f64(x); }
|
flan_obj *c;
|
||||||
default: { uint8_t b = *p; return flan_dyn_from_bool(b); }
|
switch (*d) {
|
||||||
|
case 's': {
|
||||||
|
void *data;
|
||||||
|
int64_t n;
|
||||||
|
memcpy(&data, p, 8);
|
||||||
|
memcpy(&n, p + 8, 8);
|
||||||
|
c = view_new(data, n, d + 1, VIEW_FLAT);
|
||||||
|
guard_block(view_g(c), data);
|
||||||
|
return dyn_make(BOX_OBJ, (uint64_t)(uintptr_t)c);
|
||||||
}
|
}
|
||||||
|
case 'a': {
|
||||||
|
const uint8_t *e = d + 1;
|
||||||
|
int64_t n = desc_int(&e);
|
||||||
|
c = view_new(p, n, e, VIEW_FLAT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'v': c = view_new(p, 0, d + 1, VIEW_VEC); break;
|
||||||
|
default: c = view_new(p, 0, d, VIEW_STRUCT); break;
|
||||||
|
}
|
||||||
|
if (view_shape(parent) == VIEW_VEC) guard_block(view_g(c), p);
|
||||||
|
else *view_g(c) = *view_g(parent);
|
||||||
|
return dyn_make(BOX_OBJ, (uint64_t)(uintptr_t)c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* One element, boxed on the way out. A number widens to a dyn int or float,
|
||||||
|
* except a u64 above the largest i64, which has no dyn int to become. */
|
||||||
|
static flan_dyn view_read(const uint8_t *loc, int64_t loclen, const char *op,
|
||||||
|
flan_obj *parent, const uint8_t *d, uint8_t *p) {
|
||||||
|
switch (*d) {
|
||||||
|
case 'b': { int8_t x; memcpy(&x, p, 1); return flan_dyn_from_i64(x); }
|
||||||
|
case 'B': { uint8_t x; memcpy(&x, p, 1); return flan_dyn_from_i64(x); }
|
||||||
|
case 'h': { int16_t x; memcpy(&x, p, 2); return flan_dyn_from_i64(x); }
|
||||||
|
case 'H': { uint16_t x; memcpy(&x, p, 2); return flan_dyn_from_i64(x); }
|
||||||
|
case 'i': { int32_t x; memcpy(&x, p, 4); return flan_dyn_from_i64(x); }
|
||||||
|
case 'I': { uint32_t x; memcpy(&x, p, 4); return flan_dyn_from_i64(x); }
|
||||||
|
case 'l': { int64_t x; memcpy(&x, p, 8); return flan_dyn_from_i64(x); }
|
||||||
|
case 'L': {
|
||||||
|
uint64_t x;
|
||||||
|
memcpy(&x, p, 8);
|
||||||
|
if (x > (uint64_t)INT64_MAX) {
|
||||||
|
flan_say(loc, loclen,
|
||||||
|
"dyn %s: this u64 element is %llu, above the largest dyn int "
|
||||||
|
"(9223372036854775807), so it has no dyn value",
|
||||||
|
op, (unsigned long long)x);
|
||||||
|
flan_trap((const uint8_t *)"DynRange", 8);
|
||||||
|
}
|
||||||
|
return flan_dyn_from_i64((int64_t)x);
|
||||||
|
}
|
||||||
|
case 'f': { float x; memcpy(&x, p, 4); return flan_dyn_from_f64((double)x); }
|
||||||
|
case 'd': { double x; memcpy(&x, p, 8); return flan_dyn_from_f64(x); }
|
||||||
|
case '?': return flan_dyn_from_bool(*p ? 1 : 0);
|
||||||
|
case 't': {
|
||||||
|
const uint8_t *s;
|
||||||
|
int64_t n;
|
||||||
|
memcpy(&s, p, 8);
|
||||||
|
memcpy(&n, p + 8, 8);
|
||||||
|
return flan_dyn_from_bytes(s, n);
|
||||||
|
}
|
||||||
|
default: return view_child(parent, d, p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The range each integer element holds. */
|
||||||
|
static int int_range(uint8_t c, int64_t *lo, int64_t *hi) {
|
||||||
|
switch (c) {
|
||||||
|
case 'b': *lo = INT8_MIN; *hi = INT8_MAX; return 1;
|
||||||
|
case 'B': *lo = 0; *hi = UINT8_MAX; return 1;
|
||||||
|
case 'h': *lo = INT16_MIN; *hi = INT16_MAX; return 1;
|
||||||
|
case 'H': *lo = 0; *hi = UINT16_MAX; return 1;
|
||||||
|
case 'i': *lo = INT32_MIN; *hi = INT32_MAX; return 1;
|
||||||
|
case 'I': *lo = 0; *hi = UINT32_MAX; return 1;
|
||||||
|
case 'l': *lo = INT64_MIN; *hi = INT64_MAX; return 1;
|
||||||
|
case 'L': *lo = 0; *hi = INT64_MAX; return 1;
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* One element, unboxed on the way in. The dyn value's tag must be the one
|
||||||
|
* the element's type wants, or this traps by name and never coerces a
|
||||||
|
* mismatched value into the slot; an int the element's width cannot hold
|
||||||
|
* traps too, naming both. An int is not a float here: it is refused, not
|
||||||
|
* converted, as every write through a view has been. A float into an f32
|
||||||
|
* narrows, as (f32 x) does. [v] is the view and [x] the value, for the
|
||||||
|
* sentence. */
|
||||||
|
static void view_write(const uint8_t *loc, int64_t loclen, const char *op,
|
||||||
|
flan_dyn v, const uint8_t *d, flan_dyn x, uint8_t *p) {
|
||||||
|
char ty[128];
|
||||||
|
int64_t lo, hi;
|
||||||
|
if (int_range(*d, &lo, &hi)) {
|
||||||
|
int64_t n;
|
||||||
|
if (flan_dyn_tag(x) != FLAN_DYN_TAG_INT)
|
||||||
|
trap2(loc, loclen, TYPE_TRAP, op, "this view's elements are int", v, x);
|
||||||
|
n = dyn_int_value(x);
|
||||||
|
if (n < lo || n > hi) {
|
||||||
|
desc_spell(d, ty, sizeof ty);
|
||||||
|
if (*d == 'L')
|
||||||
|
flan_say(loc, loclen,
|
||||||
|
"dyn %s: %lld does not fit a u64 element, which holds no "
|
||||||
|
"negative number", op, (long long)n);
|
||||||
|
else
|
||||||
|
flan_say(loc, loclen,
|
||||||
|
"dyn %s: %lld does not fit a %s element, which holds %lld "
|
||||||
|
"to %lld", op, (long long)n, ty, (long long)lo, (long long)hi);
|
||||||
|
flan_trap((const uint8_t *)"DynRange", 8);
|
||||||
|
}
|
||||||
|
switch (*d) {
|
||||||
|
case 'b': case 'B': { uint8_t b = (uint8_t)n; memcpy(p, &b, 1); return; }
|
||||||
|
case 'h': case 'H': { uint16_t h = (uint16_t)n; memcpy(p, &h, 2); return; }
|
||||||
|
case 'i': case 'I': { uint32_t w = (uint32_t)n; memcpy(p, &w, 4); return; }
|
||||||
|
default: memcpy(p, &n, 8); return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch (*d) {
|
||||||
|
case 'f': case 'd': {
|
||||||
|
double f;
|
||||||
|
if (flan_dyn_tag(x) != FLAN_DYN_TAG_FLOAT)
|
||||||
|
trap2(loc, loclen, TYPE_TRAP, op, "this view's elements are float", v, x);
|
||||||
|
f = dyn_num_value(x);
|
||||||
|
if (*d == 'f') { float g = (float)f; memcpy(p, &g, 4); }
|
||||||
|
else memcpy(p, &f, 8);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case '?':
|
||||||
|
if (flan_dyn_tag(x) != FLAN_DYN_TAG_BOOL)
|
||||||
|
trap2(loc, loclen, TYPE_TRAP, op, "this view's elements are bool", v, x);
|
||||||
|
*p = dyn_payload(x) ? 1 : 0;
|
||||||
|
return;
|
||||||
|
case 't':
|
||||||
|
trap2(loc, loclen, TYPE_TRAP, op,
|
||||||
|
"a str element is read-only through a dyn view", v, x);
|
||||||
|
default: {
|
||||||
|
char sx[SAY_MAX];
|
||||||
|
desc_spell(d, ty, sizeof ty);
|
||||||
|
say(sx, SAY_MAX, x);
|
||||||
|
flan_say(loc, loclen,
|
||||||
|
"dyn %s: this element is a %s, and a dyn view does not replace "
|
||||||
|
"it whole — write into its own elements or fields instead of "
|
||||||
|
"storing %s",
|
||||||
|
op, ty, sx);
|
||||||
|
flan_trap((const uint8_t *)"DynType", 7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Element [i] of a vec-shaped view. The caller has checked the bounds. */
|
||||||
|
static uint8_t *view_elem_at(flan_obj *o, int64_t i) {
|
||||||
|
return (uint8_t *)view_base(o) + i * desc_size(o->u.view.desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A length and an element reader that answer correctly whether [o] is an
|
/* A length and an element reader that answer correctly whether [o] is an
|
||||||
* ordinary heap vec (OBJ_VEC, elements are dyn words) or a view over a
|
* ordinary heap vec (OBJ_VEC, elements are dyn words) or a view over a
|
||||||
* typed container (OBJ_VIEW, elements are native bytes boxed on the way
|
* typed container (OBJ_VIEW, native bytes boxed on the way out) — the pair
|
||||||
* out) — the pair [dyn_equal]'s VEC arm needs so that a view compares
|
* [dyn_equal]'s VEC arm and the printers need. */
|
||||||
* correctly against another view and against an ordinary vec alike. Reading
|
|
||||||
* [o->len]/[o->u.v.items] directly, the way that arm used to, answers 0 and
|
|
||||||
* garbage for a view: nothing sets [len] for OBJ_VIEW, and its elements
|
|
||||||
* alias [u.view.base] reinterpreted as dyn words rather than the native
|
|
||||||
* bytes they are. */
|
|
||||||
static int64_t vecish_len(flan_obj *o) {
|
static int64_t vecish_len(flan_obj *o) {
|
||||||
return o->kind == OBJ_VIEW ? view_len(NULL, 0, "=", o) : o->len;
|
return o->kind == OBJ_VIEW ? view_len(NULL, 0, "=", o) : o->len;
|
||||||
}
|
}
|
||||||
|
|
||||||
static flan_dyn vecish_at(flan_obj *o, int64_t i) {
|
static flan_dyn vecish_at(flan_obj *o, int64_t i) {
|
||||||
if (o->kind == OBJ_VIEW)
|
if (o->kind == OBJ_VIEW)
|
||||||
return view_box(o->u.view.elem,
|
return view_read(NULL, 0, "print", o, o->u.view.desc, view_elem_at(o, i));
|
||||||
(const uint8_t *)view_base(o)
|
|
||||||
+ i * view_elem_size(o->u.view.elem));
|
|
||||||
return o->u.v.items[i];
|
return o->u.v.items[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Writes tag-check on the way in: the dyn value's tag must be the one this
|
/* A struct view's field [k], or a trap naming the fields there are: a
|
||||||
* view's element type wants, or this traps by name and never coerces or
|
* struct's shape is fixed, so a key it does not have is a mistake rather
|
||||||
* truncates a mismatched value into the slot. [v] is the view, for the
|
* than an absence. */
|
||||||
* sentence's container half; [x] is the value that was refused. */
|
static uint8_t *view_field(const uint8_t *loc, int64_t loclen, const char *op,
|
||||||
static void view_unbox(const uint8_t *loc, int64_t loclen, const char *op,
|
flan_obj *o, flan_dyn k, const uint8_t **fty) {
|
||||||
flan_dyn v, int32_t elem, flan_dyn x, uint8_t *p) {
|
int64_t off;
|
||||||
switch (elem) {
|
const uint8_t *d = o->u.view.desc;
|
||||||
case FLAN_VIEW_I64: {
|
view_guard_check(loc, loclen, op, o);
|
||||||
int64_t n;
|
*fty = desc_field(d, k, &off);
|
||||||
if (flan_dyn_tag(x) != FLAN_DYN_TAG_INT)
|
if (*fty == NULL) {
|
||||||
trap2(loc, loclen, TYPE_TRAP, op, "this view's elements are int", v, x);
|
char sk[SAY_MAX], nm[96];
|
||||||
n = dyn_int_value(x);
|
const uint8_t *at, *name, *t;
|
||||||
memcpy(p, &n, 8);
|
int64_t o2, namelen, foff;
|
||||||
return;
|
say(sk, SAY_MAX, k);
|
||||||
}
|
desc_spell(d, nm, sizeof nm);
|
||||||
case FLAN_VIEW_F64: {
|
said_len = 0;
|
||||||
double d;
|
said_add("dyn %s: a %s has no field %s. Its fields are", op, nm, sk);
|
||||||
if (flan_dyn_tag(x) != FLAN_DYN_TAG_FLOAT)
|
at = desc_fields(d, &o2);
|
||||||
trap2(loc, loclen, TYPE_TRAP, op, "this view's elements are float", v, x);
|
while (desc_next(&at, &o2, &name, &namelen, &foff, &t))
|
||||||
d = dyn_num_value(x);
|
said_add(" :%.*s", (int)namelen, (const char *)name);
|
||||||
memcpy(p, &d, 8);
|
flan_say(loc, loclen, "%s", said_buf);
|
||||||
return;
|
flan_trap((const uint8_t *)"DynType", 7);
|
||||||
}
|
|
||||||
default: {
|
|
||||||
uint8_t b;
|
|
||||||
if (flan_dyn_tag(x) != FLAN_DYN_TAG_BOOL)
|
|
||||||
trap2(loc, loclen, TYPE_TRAP, op, "this view's elements are bool", v, x);
|
|
||||||
b = dyn_payload(x) ? 1 : 0;
|
|
||||||
*p = b;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return (uint8_t *)o->u.view.base + off;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The crossing. [base] is the container's address (a Vec's header, the
|
||||||
|
* array's or the struct's first byte) or a slice's data, [len] a flat
|
||||||
|
* view's element count, [desc] the element's descriptor (the struct's own,
|
||||||
|
* for a struct view). [here] is the checker's word that the storage is the
|
||||||
|
* calling function's own frame; otherwise a dev build looks the address up
|
||||||
|
* in the allocation registry. */
|
||||||
|
static flan_dyn view_make(void *base, int64_t len, const uint8_t *desc,
|
||||||
|
int shape, int32_t here) {
|
||||||
|
flan_obj *o = view_new(base, len, desc, shape);
|
||||||
|
view_guard *g = view_g(o);
|
||||||
|
if (here) {
|
||||||
|
if (flan_frame_head != NULL) {
|
||||||
|
g->frame = flan_frame_head;
|
||||||
|
g->serial =
|
||||||
|
flan_dev_frame_claim(flan_frame_head, &g->fname, &g->fnamelen);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
guard_block(g, base);
|
||||||
|
return dyn_make(BOX_OBJ, (uint64_t)(uintptr_t)o);
|
||||||
|
}
|
||||||
|
|
||||||
|
flan_dyn flan_dyn_view_slice(void *data, int64_t len, const uint8_t *desc,
|
||||||
|
int64_t desclen, int32_t here) {
|
||||||
|
(void)desclen;
|
||||||
|
return view_make(data, len, desc, VIEW_FLAT, here);
|
||||||
|
}
|
||||||
|
|
||||||
|
flan_dyn flan_dyn_view_at(void *addr, int64_t len, const uint8_t *desc,
|
||||||
|
int64_t desclen, int32_t shape, int32_t here) {
|
||||||
|
(void)desclen;
|
||||||
|
return view_make(addr, len, desc, shape, here);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A struct view's fields by position, for the printers and equality. */
|
||||||
|
static int64_t view_nfields(flan_obj *o) {
|
||||||
|
view_guard_check(NULL, 0, "print", o);
|
||||||
|
return desc_nfields(o->u.view.desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int view_nth(flan_obj *o, int64_t i, const uint8_t **name,
|
||||||
|
int64_t *namelen, int64_t *foff, const uint8_t **fty) {
|
||||||
|
int64_t off;
|
||||||
|
const uint8_t *at = desc_fields(o->u.view.desc, &off);
|
||||||
|
while (desc_next(&at, &off, name, namelen, foff, fty))
|
||||||
|
if (i-- == 0) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static flan_dyn view_field_key(flan_obj *o, int64_t i) {
|
||||||
|
const uint8_t *name, *fty;
|
||||||
|
int64_t namelen, foff;
|
||||||
|
if (!view_nth(o, i, &name, &namelen, &foff, &fty)) return flan_dyn_nil();
|
||||||
|
return flan_dyn_kw(name, namelen);
|
||||||
|
}
|
||||||
|
|
||||||
|
static flan_dyn view_field_val(flan_obj *o, int64_t i) {
|
||||||
|
const uint8_t *name, *fty;
|
||||||
|
int64_t namelen, foff;
|
||||||
|
if (!view_nth(o, i, &name, &namelen, &foff, &fty)) return flan_dyn_nil();
|
||||||
|
return view_read(NULL, 0, "print", o, fty, (uint8_t *)o->u.view.base + foff);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void view_struct_name(flan_obj *o, const char **name, int64_t *len) {
|
||||||
|
const uint8_t *d = o->u.view.desc;
|
||||||
|
const uint8_t *e = desc_name_end(d + 1);
|
||||||
|
*name = (const char *)d + 1;
|
||||||
|
*len = (int64_t)(e - d - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The first ABI, kept for test/dyn_ops.c: FLAN_VIEW_I64/F64/BOOL. */
|
||||||
|
static const uint8_t *old_elem_desc(int32_t elem) {
|
||||||
|
return (const uint8_t *)(elem == FLAN_VIEW_I64 ? "l"
|
||||||
|
: elem == FLAN_VIEW_F64 ? "d" : "?");
|
||||||
}
|
}
|
||||||
|
|
||||||
flan_dyn flan_dyn_view_vec(void *hdr, int32_t elem) {
|
flan_dyn flan_dyn_view_vec(void *hdr, int32_t elem) {
|
||||||
flan_obj *o = gc_alloc(OBJ_VIEW, 0);
|
return view_make(hdr, 0, old_elem_desc(elem), VIEW_VEC, 0);
|
||||||
o->u.view.base = hdr;
|
|
||||||
o->u.view.len = 0;
|
|
||||||
o->u.view.elem = elem;
|
|
||||||
o->u.view.is_vec = 1;
|
|
||||||
return dyn_make(BOX_OBJ, (uint64_t)(uintptr_t)o);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
flan_dyn flan_dyn_view_flat(void *data, int64_t len, int32_t elem) {
|
flan_dyn flan_dyn_view_flat(void *data, int64_t len, int32_t elem) {
|
||||||
flan_obj *o = gc_alloc(OBJ_VIEW, 0);
|
return view_make(data, len, old_elem_desc(elem), VIEW_FLAT, 0);
|
||||||
o->u.view.base = data;
|
|
||||||
o->u.view.len = len;
|
|
||||||
o->u.view.elem = elem;
|
|
||||||
o->u.view.is_vec = 0;
|
|
||||||
return dyn_make(BOX_OBJ, (uint64_t)(uintptr_t)o);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
flan_dyn flan_dyn_len(flan_dyn v) {
|
flan_dyn flan_dyn_len(flan_dyn v) {
|
||||||
@ -3009,6 +3504,10 @@ flan_dyn flan_dyn_len(flan_dyn v) {
|
|||||||
reason [get] is. */
|
reason [get] is. */
|
||||||
if (is_map(v)) {
|
if (is_map(v)) {
|
||||||
flan_obj *o = dyn_obj(v);
|
flan_obj *o = dyn_obj(v);
|
||||||
|
if (o->kind == OBJ_VIEW) {
|
||||||
|
view_guard_check(NULL, 0, "length", o);
|
||||||
|
return flan_dyn_from_i64(view_nfields(o));
|
||||||
|
}
|
||||||
class_sync(o);
|
class_sync(o);
|
||||||
return flan_dyn_from_i64(o->len);
|
return flan_dyn_from_i64(o->len);
|
||||||
}
|
}
|
||||||
@ -3044,8 +3543,7 @@ flan_dyn flan_dyn_at(flan_dyn v, flan_dyn i, const uint8_t *loc,
|
|||||||
if (o->kind == OBJ_VIEW) {
|
if (o->kind == OBJ_VIEW) {
|
||||||
int64_t len = view_len(loc, loclen, "at", o);
|
int64_t len = view_len(loc, loclen, "at", o);
|
||||||
if (k < 0 || k >= len) trap_range(loc, loclen, "at", v, k, len);
|
if (k < 0 || k >= len) trap_range(loc, loclen, "at", v, k, len);
|
||||||
return view_box(o->u.view.elem,
|
return view_read(loc, loclen, "at", o, o->u.view.desc, view_elem_at(o, k));
|
||||||
(const uint8_t *)view_base(o) + k * view_elem_size(o->u.view.elem));
|
|
||||||
}
|
}
|
||||||
if (k < 0 || k >= o->len) trap_range(loc, loclen, "at", v, k, o->len);
|
if (k < 0 || k >= o->len) trap_range(loc, loclen, "at", v, k, o->len);
|
||||||
if (o->kind == OBJ_TEXT) return flan_dyn_from_i64(obj_text_bytes(o)[k]);
|
if (o->kind == OBJ_TEXT) return flan_dyn_from_i64(obj_text_bytes(o)[k]);
|
||||||
@ -3092,10 +3590,8 @@ void flan_dyn_set_at(flan_dyn v, flan_dyn i, flan_dyn x, const uint8_t *loc,
|
|||||||
o = dyn_obj(v);
|
o = dyn_obj(v);
|
||||||
if (o->kind == OBJ_VIEW) {
|
if (o->kind == OBJ_VIEW) {
|
||||||
int64_t len = view_len(loc, loclen, "set-at", o);
|
int64_t len = view_len(loc, loclen, "set-at", o);
|
||||||
uint8_t *p;
|
|
||||||
if (k < 0 || k >= len) trap_range(loc, loclen, "set-at", v, k, len);
|
if (k < 0 || k >= len) trap_range(loc, loclen, "set-at", v, k, len);
|
||||||
p = (uint8_t *)view_base(o) + k * view_elem_size(o->u.view.elem);
|
view_write(loc, loclen, "set-at", v, o->u.view.desc, x, view_elem_at(o, k));
|
||||||
view_unbox(loc, loclen, "set-at", v, o->u.view.elem, x, p);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (k < 0 || k >= o->len) trap_range(loc, loclen, "set-at", v, k, o->len);
|
if (k < 0 || k >= o->len) trap_range(loc, loclen, "set-at", v, k, o->len);
|
||||||
@ -3111,20 +3607,24 @@ void flan_dyn_push(flan_dyn v, flan_dyn x, const uint8_t *loc, int64_t loclen) {
|
|||||||
}
|
}
|
||||||
o = dyn_obj(v);
|
o = dyn_obj(v);
|
||||||
if (o->kind == OBJ_VIEW) {
|
if (o->kind == OBJ_VIEW) {
|
||||||
uint8_t buf[8];
|
uint8_t buf[16];
|
||||||
/* The typed Vec's own traps print a site too, and without one from the
|
/* The typed Vec's own traps print a site too, and without one from the
|
||||||
* caller the best this can name is the operation. */
|
* caller the best this can name is the operation. */
|
||||||
static const uint8_t push_loc[] = "(dyn push)";
|
static const uint8_t push_loc[] = "(dyn push)";
|
||||||
const uint8_t *site = loc != NULL && loclen > 0 ? loc : push_loc;
|
const uint8_t *site = loc != NULL && loclen > 0 ? loc : push_loc;
|
||||||
int64_t sitelen = loc != NULL && loclen > 0 ? loclen
|
int64_t sitelen = loc != NULL && loclen > 0 ? loclen
|
||||||
: (int64_t)sizeof(push_loc) - 1;
|
: (int64_t)sizeof(push_loc) - 1;
|
||||||
int64_t size;
|
int64_t size, align;
|
||||||
if (!o->u.view.is_vec)
|
if (view_shape(o) != VIEW_VEC)
|
||||||
trap2(loc, loclen, TYPE_TRAP, "push",
|
trap2(loc, loclen, TYPE_TRAP, "push",
|
||||||
"this view is a slice or an array and cannot grow", v, x);
|
"this view is a slice or an array and cannot grow", v, x);
|
||||||
size = view_elem_size(o->u.view.elem);
|
view_len(loc, loclen, "push", o);
|
||||||
view_unbox(loc, loclen, "push", v, o->u.view.elem, x, buf);
|
desc_lay(o->u.view.desc, &size, &align);
|
||||||
if (!flan_vec_push(o->u.view.base, buf, size, size, site, sitelen))
|
/* Only a number or a bool is ever written, and [view_write] refuses the
|
||||||
|
rest before a byte of [buf] is used. */
|
||||||
|
memset(buf, 0, sizeof buf);
|
||||||
|
view_write(loc, loclen, "push", v, o->u.view.desc, x, buf);
|
||||||
|
if (!flan_vec_push(o->u.view.base, buf, size, align, site, sitelen))
|
||||||
trap_oom(loc, loclen, size);
|
trap_oom(loc, loclen, size);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -3187,12 +3687,22 @@ static flan_obj *want_map(const char *op, flan_dyn m, flan_dyn k) {
|
|||||||
|
|
||||||
flan_dyn flan_dyn_map_get(flan_dyn m, flan_dyn k) {
|
flan_dyn flan_dyn_map_get(flan_dyn m, flan_dyn k) {
|
||||||
flan_obj *o = want_map("get", m, k);
|
flan_obj *o = want_map("get", m, k);
|
||||||
|
if (o->kind == OBJ_VIEW) {
|
||||||
|
const uint8_t *fty;
|
||||||
|
uint8_t *p = view_field(NULL, 0, "get", o, k, &fty);
|
||||||
|
return view_read(NULL, 0, "get", o, fty, p);
|
||||||
|
}
|
||||||
int64_t i = map_find(o, k);
|
int64_t i = map_find(o, k);
|
||||||
return i < 0 ? flan_dyn_nil() : o->u.v.items[i * 2 + 1];
|
return i < 0 ? flan_dyn_nil() : o->u.v.items[i * 2 + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
flan_dyn flan_dyn_map_contains(flan_dyn m, flan_dyn k) {
|
flan_dyn flan_dyn_map_contains(flan_dyn m, flan_dyn k) {
|
||||||
flan_obj *o = want_map("has-key?", m, k);
|
flan_obj *o = want_map("has-key?", m, k);
|
||||||
|
if (o->kind == OBJ_VIEW) {
|
||||||
|
int64_t off;
|
||||||
|
view_guard_check(NULL, 0, "has-key?", o);
|
||||||
|
return flan_dyn_from_bool(desc_field(o->u.view.desc, k, &off) != NULL);
|
||||||
|
}
|
||||||
return flan_dyn_from_bool(map_find(o, k) >= 0);
|
return flan_dyn_from_bool(map_find(o, k) >= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3294,6 +3804,12 @@ void flan_dyn_slot_set(flan_dyn m, flan_dyn k, flan_dyn v,
|
|||||||
class_entry *e;
|
class_entry *e;
|
||||||
int64_t j;
|
int64_t j;
|
||||||
flan_dyn out;
|
flan_dyn out;
|
||||||
|
if (is_map(m) && dyn_obj(m)->kind == OBJ_VIEW) {
|
||||||
|
const uint8_t *fty;
|
||||||
|
uint8_t *p = view_field(loc, loclen, "set", dyn_obj(m), k, &fty);
|
||||||
|
view_write(loc, loclen, "set", m, fty, v, p);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!is_map(m) || dyn_obj(m)->u.v.klass == NULL) {
|
if (!is_map(m) || dyn_obj(m)->u.v.klass == NULL) {
|
||||||
char sm[SAY_MAX];
|
char sm[SAY_MAX];
|
||||||
say(sm, SAY_MAX, m);
|
say(sm, SAY_MAX, m);
|
||||||
@ -3335,6 +3851,12 @@ void flan_dyn_map_put(flan_dyn m, flan_dyn k, flan_dyn v, const uint8_t *loc,
|
|||||||
class_entry *e;
|
class_entry *e;
|
||||||
if (!is_map(m)) trap2(NULL, 0, TYPE_TRAP, "put", "only a map answers it", m, k);
|
if (!is_map(m)) trap2(NULL, 0, TYPE_TRAP, "put", "only a map answers it", m, k);
|
||||||
o = dyn_obj(m);
|
o = dyn_obj(m);
|
||||||
|
if (o->kind == OBJ_VIEW) {
|
||||||
|
const uint8_t *fty;
|
||||||
|
uint8_t *p = view_field(loc, loclen, "put", o, k, &fty);
|
||||||
|
view_write(loc, loclen, "put", m, fty, v, p);
|
||||||
|
return;
|
||||||
|
}
|
||||||
e = class_sync(o);
|
e = class_sync(o);
|
||||||
/* A map with no class, and a class with no typed slot, stop at the test. */
|
/* A map with no class, and a class with no typed slot, stop at the test. */
|
||||||
if (e != NULL && e->typed) v = check_slot(loc, loclen, BY_PUT, o, e, m, k, v);
|
if (e != NULL && e->typed) v = check_slot(loc, loclen, BY_PUT, o, e, m, k, v);
|
||||||
|
|||||||
@ -301,56 +301,40 @@ flan_dyn flan_dyn_need_not_nil(flan_dyn v);
|
|||||||
* "", an empty vec, an empty map, and any keyword. Never traps. */
|
* "", an empty vec, an empty map, and any keyword. Never traps. */
|
||||||
uint8_t flan_dyn_truthy(flan_dyn v);
|
uint8_t flan_dyn_truthy(flan_dyn v);
|
||||||
|
|
||||||
/* ── Typed containers as views — M2 item 3 ─────────────────────────────
|
/* ── Typed containers as views ────────────────────────────────────────
|
||||||
*
|
*
|
||||||
* A [(Vec T)], a [T] slice, or a fixed [n T] array crossing into dyn is a
|
* A [(Vec T)], a [T] slice, a fixed [n T] array or a struct crossing into
|
||||||
* VIEW, not a copy: the box holds a small heap record naming where the
|
* dyn is a VIEW, not a copy: the box holds a small heap record naming where
|
||||||
* elements live and what one of them is, and every read or write goes
|
* the storage is and a descriptor of one element (flan_dyn.c documents the
|
||||||
* straight through to the container's own storage. [flan_dyn_at] boxes an
|
* code beside [desc_lay]), and every read or write goes straight through to
|
||||||
* element on the way out; [flan_dyn_set_at] tag-checks the dyn value it is
|
* the container's own storage. A read boxes the element — every number
|
||||||
* given against the element type on the way in and traps, by [flan_trap],
|
* widens, an aggregate element answers a view of its own, a str is copied
|
||||||
* on a mismatch — never a silent coercion.
|
* into a text — and a write tag-checks and range-checks the dyn value
|
||||||
|
* against the element type and traps, by [flan_trap], rather than coerce.
|
||||||
|
* A struct's view answers the map tag: [get], [put] and (set (get p :k) v)
|
||||||
|
* reach its fields.
|
||||||
*
|
*
|
||||||
* T is restricted to i64, f64 and bool — exactly the set [flan_dyn_need_i64]
|
* No collector pointer is ever written into typed storage, which the
|
||||||
* and friends already treat as crossing the typed boundary both ways. That
|
* collector never scans: that is why a str element is read-only from dyn and
|
||||||
* is not an arbitrary cut: the excluded case that matters is a string
|
* an aggregate element is written through its own view, never replaced.
|
||||||
* element, whose dyn form is a pointer into this collector's heap, while a
|
|
||||||
* typed container's storage is arena or stack memory the collector never
|
|
||||||
* scans. Writing such a pointer into that memory would be a live reference
|
|
||||||
* nothing ever traces — a use-after-free the collector cannot see coming,
|
|
||||||
* not a bug in this file but a hazard the type admits. i64, f64 and bool
|
|
||||||
* carry no such pointer, so a view restricted to them cannot manufacture
|
|
||||||
* it. [box] in lib/check.ml keeps the "does not cross into dyn yet" refusal
|
|
||||||
* for every other element type, and this paragraph is why.
|
|
||||||
*
|
*
|
||||||
* Two kinds, because the containers split exactly here: a [(Vec T)] can grow
|
* [flan_dyn_view_at] with shape 1 takes the address of a Vec's own header —
|
||||||
* and move (a push may reallocate), a slice and a fixed array cannot.
|
* the struct [flan_vec] in flan_rt.c, restated in flan_dyn.c under the same
|
||||||
|
* "if either table changes, change both" rule — and every operation re-reads
|
||||||
|
* its [ptr] and [len], so a push that grows and moves the Vec is never seen
|
||||||
|
* as stale. A slice (flan_dyn_view_slice) and a fixed array (shape 0) are
|
||||||
|
* snapshotted at the crossing, sound because neither moves; a struct is
|
||||||
|
* shape 2.
|
||||||
*
|
*
|
||||||
* [flan_dyn_view_vec] takes the address of the Vec's own header — the
|
* The storage may be anywhere. [here] is the compiler's word that it is the
|
||||||
* struct [flan_vec] in flan_rt.c, restated in flan_dyn.c under the same
|
* calling function's own frame; a dev build then records that activation
|
||||||
* "if either table changes, change both" rule this whole boundary already
|
* (runtime/flan_dev.c's shadow frame and its serial), and otherwise looks
|
||||||
* lives under. That address is the Vec's home, fixed for as long as the Vec
|
* the address up in its allocation registry, and every later operation
|
||||||
* exists — but "as long as the Vec exists" is the whole of the guarantee,
|
* traps with DynStale when the frame has returned or the block has been
|
||||||
* which is why [permanent_root] in lib/check.ml admits only storage that
|
* released. A release build records and checks nothing.
|
||||||
* outlives every frame: a global, a field or an array element of one, or a
|
|
||||||
* slice cut from one at the crossing. A local's slot is a home too, and it
|
|
||||||
* is precisely the one that is refused. Every operation re-reads that
|
|
||||||
* header's [ptr] and [len] fresh, so a push that grows and moves the Vec is
|
|
||||||
* never seen as stale — [flan_vec_grow] overwrites the SAME header's [ptr]
|
|
||||||
* field in place, and there is no snapshot anywhere to go stale. That is
|
|
||||||
* what makes the failure the open design question worried about
|
|
||||||
* (a push through dyn holding a dangling pointer) impossible rather than
|
|
||||||
* merely unlikely: there is nothing captured at the crossing for a later
|
|
||||||
* push to invalidate.
|
|
||||||
*
|
*
|
||||||
* [flan_dyn_view_flat] takes a data address and a length captured once, at
|
* The FLAN_VIEW_* entry points below are the first ABI, kept for
|
||||||
* the crossing — sound for a slice and for a fixed array because neither
|
* test/dyn_ops.c; the compiler calls the two after them.
|
||||||
* ever moves or grows. Note the asymmetry is not an oversight: pointing
|
|
||||||
* *this* case at the value's own slot instead would be worse than a
|
|
||||||
* snapshot, because a slot's lifetime is not the slice's, and a slice taken
|
|
||||||
* from a Vec is already one push away from dangling on its own account
|
|
||||||
* (flan_vec_grow's own comment says so) — the view is exactly as
|
|
||||||
* stale-safe as the thing it is a view of, no more and no less.
|
|
||||||
*/
|
*/
|
||||||
#define FLAN_VIEW_I64 0
|
#define FLAN_VIEW_I64 0
|
||||||
#define FLAN_VIEW_F64 1
|
#define FLAN_VIEW_F64 1
|
||||||
@ -358,6 +342,10 @@ uint8_t flan_dyn_truthy(flan_dyn v);
|
|||||||
|
|
||||||
flan_dyn flan_dyn_view_vec(void *hdr, int32_t elem);
|
flan_dyn flan_dyn_view_vec(void *hdr, int32_t elem);
|
||||||
flan_dyn flan_dyn_view_flat(void *data, int64_t len, int32_t elem);
|
flan_dyn flan_dyn_view_flat(void *data, int64_t len, int32_t elem);
|
||||||
|
flan_dyn flan_dyn_view_slice(void *data, int64_t len, const uint8_t *desc,
|
||||||
|
int64_t desclen, int32_t here);
|
||||||
|
flan_dyn flan_dyn_view_at(void *addr, int64_t len, const uint8_t *desc,
|
||||||
|
int64_t desclen, int32_t shape, int32_t here);
|
||||||
|
|
||||||
/* ── The collector ─────────────────────────────────────────────────────
|
/* ── The collector ─────────────────────────────────────────────────────
|
||||||
*
|
*
|
||||||
|
|||||||
211
test/programs/dyn-view-any.flan
Normal file
211
test/programs/dyn-view-any.flan
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
;;;; Any typed container crosses into dyn as a view: every element type and
|
||||||
|
;;;; any storage. Mode 0 is the survey; the others are one trap each, since a
|
||||||
|
;;;; trap ends the process. test_acceptance.ml runs it on both backends, and
|
||||||
|
;;;; the stale-view modes under --dev only: a release build keeps no record of
|
||||||
|
;;;; frames or blocks, so it neither checks nor promises anything there.
|
||||||
|
|
||||||
|
;; Unannotated parameters and returns are dyn: every call below boxes its
|
||||||
|
;; typed argument into a view at the call.
|
||||||
|
(defn show [label d] ()
|
||||||
|
(print label)
|
||||||
|
(print " ")
|
||||||
|
(println d))
|
||||||
|
|
||||||
|
(defn bump-all [d] ()
|
||||||
|
(dotimes [i (length d)]
|
||||||
|
(set (at d i) (+ (at d i) 1))))
|
||||||
|
|
||||||
|
(defn keep [d] dyn d)
|
||||||
|
|
||||||
|
;; Field offsets only C's layout rule gets right: a u8, then an i64 aligned
|
||||||
|
;; to 8, an f32, a bool, three u16 at 2, an i32 at 4.
|
||||||
|
(defstruct Mix [a u8 b i64 c f32 d bool e [3 u16] f i32])
|
||||||
|
(defstruct Point [x f32 y i32])
|
||||||
|
(defstruct Named [name str id u32])
|
||||||
|
|
||||||
|
(defn make-point [] Point (Point {.x 1.5 .y 2}))
|
||||||
|
|
||||||
|
(defn param-array [xs [4 i32]] i32
|
||||||
|
;; A parameter array is this frame's copy.
|
||||||
|
(bump-all xs)
|
||||||
|
(at xs 3))
|
||||||
|
|
||||||
|
(defn param-slice [xs [i64]] ()
|
||||||
|
(bump-all xs))
|
||||||
|
|
||||||
|
(defonce held dyn nil)
|
||||||
|
|
||||||
|
;; A view of a local, kept past the call that owns the local.
|
||||||
|
(defn leak-local [] ()
|
||||||
|
(let [a [1 2 3]]
|
||||||
|
(set held (keep a))))
|
||||||
|
|
||||||
|
(defn clobber [] i64
|
||||||
|
(let [b [(i64 7) 8 9 10 11 12]]
|
||||||
|
(+ (at b 0) (at b 5))))
|
||||||
|
|
||||||
|
(defn main [args [str]] i32
|
||||||
|
(let [n (i32 (bytes->i64 (bytes-view (at args 1))))]
|
||||||
|
(cond
|
||||||
|
(= n 0)
|
||||||
|
(do
|
||||||
|
;; Every integer width, a local array each: read widens, write stores.
|
||||||
|
(let [a [(i8 -1) -2 -3]
|
||||||
|
b [(u8 250) 251 252]
|
||||||
|
c [(i16 -300) 300]
|
||||||
|
d [(u16 60000) 1]
|
||||||
|
e [(i32 -70000) 70000]
|
||||||
|
f [(u32 4000000000) 1]
|
||||||
|
g [(i64 -5) 5]
|
||||||
|
h [(u64 9000000000000000000) 1]]
|
||||||
|
(bump-all a) (bump-all b) (bump-all c) (bump-all d)
|
||||||
|
(bump-all e) (bump-all f) (bump-all g) (bump-all h)
|
||||||
|
(show "i8" a) (show "u8" b) (show "i16" c) (show "u16" d)
|
||||||
|
(show "i32" e) (show "u32" f) (show "i64" g) (show "u64" h)
|
||||||
|
(println (at b 2)))
|
||||||
|
;; f32: read widens, write narrows.
|
||||||
|
(let [fs [(f32 0.5) 1.25]
|
||||||
|
dv (keep fs)]
|
||||||
|
(set (at dv 0) 2.75)
|
||||||
|
(set (at dv 1) 3.1)
|
||||||
|
(show "f32" dv)
|
||||||
|
(println (at fs 0)))
|
||||||
|
;; bool, through a slice cut from a local array.
|
||||||
|
(let [bs [true false true]]
|
||||||
|
(let [dv (keep (slice bs 0 3))]
|
||||||
|
(set (at dv 1) true)
|
||||||
|
(show "bool" dv)
|
||||||
|
(println (at bs 1))))
|
||||||
|
;; The author's case: a [u8] from bytes, sorted in place by dyn code.
|
||||||
|
(let [text (bytes "INSERTIONSORT")]
|
||||||
|
(let [d (keep text)]
|
||||||
|
(dotimes [i (length d)]
|
||||||
|
(let [j i]
|
||||||
|
(while (and (> j 0) (< (at d j) (at d (- j 1))))
|
||||||
|
(let [t (at d j)]
|
||||||
|
(set (at d j) (at d (- j 1)))
|
||||||
|
(set (at d (- j 1)) t))
|
||||||
|
(set j (- j 1))))))
|
||||||
|
(println (str text)))
|
||||||
|
;; A struct: a map-like view, written through get and put.
|
||||||
|
(let [p (Point {.x 0.5 .y 3})
|
||||||
|
dp (keep p)]
|
||||||
|
(show "point" dp)
|
||||||
|
(set (get dp :y) 40)
|
||||||
|
(put dp :x 9.5)
|
||||||
|
(println (.y p))
|
||||||
|
(println (.x p))
|
||||||
|
(println (get dp :x))
|
||||||
|
(println (length dp))
|
||||||
|
(println (has-key? dp :y)))
|
||||||
|
;; Every field of a padded struct reads back.
|
||||||
|
(let [m (Mix {.a 7 .b -9000000000 .c 2.5 .d true .e [1 2 3] .f -4})]
|
||||||
|
(show "mix" (keep m))
|
||||||
|
(set (at (get (keep m) :e) 2) 65535)
|
||||||
|
(println (at (.e m) 2)))
|
||||||
|
;; A str field reads as text.
|
||||||
|
(let [nm (Named {.name "ada" .id 7})]
|
||||||
|
(show "named" (keep nm)))
|
||||||
|
;; Nested arrays: a view of a view.
|
||||||
|
(let [grid [[(i32 1) 2 3] [4 5 6]]
|
||||||
|
dg (keep grid)]
|
||||||
|
(set (at (at dg 1) 2) 60)
|
||||||
|
(show "grid" dg)
|
||||||
|
(println (at (at grid 1) 2)))
|
||||||
|
;; An array of structs.
|
||||||
|
(let [ps [(Point {.x 1.0 .y 1}) (Point {.x 2.0 .y 2})]
|
||||||
|
dp (keep ps)]
|
||||||
|
(set (get (at dp 1) :y) 20)
|
||||||
|
(show "points" dp)
|
||||||
|
(println (.y (at ps 1))))
|
||||||
|
;; A Vec in a local: a push through the view grows the typed Vec.
|
||||||
|
(let [v (vec-new i32)]
|
||||||
|
(push v 1)
|
||||||
|
(let [dv (keep v)]
|
||||||
|
(push dv 2)
|
||||||
|
(push dv 3)
|
||||||
|
(show "vec" dv))
|
||||||
|
(println (length v)))
|
||||||
|
;; A Vec of structs.
|
||||||
|
(let [v (vec-new Point)]
|
||||||
|
(push v (Point {.x 0.0 .y 5}))
|
||||||
|
(let [dv (keep v)]
|
||||||
|
(set (get (at dv 0) :y) 6))
|
||||||
|
(println (.y (at v 0))))
|
||||||
|
;; Parameters: an array parameter is the callee's copy; a slice
|
||||||
|
;; parameter sees the caller's elements.
|
||||||
|
(let [xs [(i32 1) 2 3 4]]
|
||||||
|
(println (param-array xs))
|
||||||
|
(println (at xs 3)))
|
||||||
|
(let [ys [(i64 1) 2]]
|
||||||
|
(param-slice (slice ys 0 2))
|
||||||
|
(println (at ys 1)))
|
||||||
|
;; A temporary: a struct returned by a call.
|
||||||
|
(show "temp" (keep (make-point)))
|
||||||
|
;; A heap slice, and an arena's Vec.
|
||||||
|
(let [c (clone (slice [(i64 1) 2 3] 0 3))]
|
||||||
|
(bump-all c)
|
||||||
|
(println (at c 2)))
|
||||||
|
(let [ar (arena-new 4096)]
|
||||||
|
(with-allocator ar
|
||||||
|
(let [v (vec-new i16)]
|
||||||
|
(push v 1)
|
||||||
|
(push v 2)
|
||||||
|
(bump-all v)
|
||||||
|
(println (at v 1))))
|
||||||
|
(arena-destroy ar))
|
||||||
|
;; Two views of equal structs are equal.
|
||||||
|
(let [p (Point {.x 1.0 .y 2})
|
||||||
|
q (Point {.x 1.0 .y 2})]
|
||||||
|
(println (= (keep p) (keep q))))
|
||||||
|
0)
|
||||||
|
;; A value an element's width cannot hold.
|
||||||
|
(= n 1)
|
||||||
|
(let [b [(u8 1) 2]]
|
||||||
|
(set (at (keep b) 0) 300)
|
||||||
|
0)
|
||||||
|
;; A u64 above the largest dyn int.
|
||||||
|
(= n 2)
|
||||||
|
(let [h [(u64 18000000000000000000)]]
|
||||||
|
(println (at (keep h) 0))
|
||||||
|
0)
|
||||||
|
;; A field a struct does not have.
|
||||||
|
(= n 3)
|
||||||
|
(let [p (Point {.x 1.0 .y 2})]
|
||||||
|
(println (get (keep p) :z))
|
||||||
|
0)
|
||||||
|
;; A view of a local, used after its call returned: stale in a dev build.
|
||||||
|
(= n 4)
|
||||||
|
(do (leak-local)
|
||||||
|
(println (clobber))
|
||||||
|
(println (at held 0))
|
||||||
|
0)
|
||||||
|
;; A slice of a Vec's block, used after the Vec grew and moved.
|
||||||
|
(= n 5)
|
||||||
|
(let [v (vec-new i64)]
|
||||||
|
(push v 1)
|
||||||
|
(let [d (keep (slice v))]
|
||||||
|
(dotimes [i 100] (push v i))
|
||||||
|
(println (at d 0)))
|
||||||
|
0)
|
||||||
|
;; A heap slice used after it was freed.
|
||||||
|
(= n 6)
|
||||||
|
(let [c (clone (slice [(i64 1) 2 3] 0 3))
|
||||||
|
d (keep c)]
|
||||||
|
(free c)
|
||||||
|
(println (at d 0))
|
||||||
|
0)
|
||||||
|
;; An arena's storage used after free-all.
|
||||||
|
(= n 7)
|
||||||
|
(let [ar (arena-new 4096)
|
||||||
|
v (with-allocator ar (clone (slice [(i32 4) 5] 0 2)))
|
||||||
|
d (keep v)]
|
||||||
|
(free-all ar)
|
||||||
|
(println (at d 1))
|
||||||
|
0)
|
||||||
|
;; A str element is read-only through a view.
|
||||||
|
(= n 8)
|
||||||
|
(let [nm (Named {.name "ada" .id 7})]
|
||||||
|
(put (keep nm) :name "bob")
|
||||||
|
0)
|
||||||
|
:else (do (println "?") 1))))
|
||||||
@ -5,24 +5,8 @@
|
|||||||
;;;; caller's own value, which is what makes [dv] below the SAME storage [v]
|
;;;; caller's own value, which is what makes [dv] below the SAME storage [v]
|
||||||
;;;; is and not a copy of it.
|
;;;; is and not a copy of it.
|
||||||
;;;;
|
;;;;
|
||||||
;;;; Every container viewed below is a GLOBAL, and that is not incidental to
|
;;;; Every container viewed below is a global; dyn-view-any.flan covers
|
||||||
;;;; this program — it is the lifetime guard review added after the first
|
;;;; the other storage and element types.
|
||||||
;;;; landing: a view's descriptor chases the container's own address on every
|
|
||||||
;;;; operation, which is what makes a Vec's growth safe, but it is also what
|
|
||||||
;;;; makes a DANGLING container's address a live hazard. box refuses a Vec, a
|
|
||||||
;;;; slice or a fixed array whose storage is not known to outlive the view —
|
|
||||||
;;;; a local's, a parameter's, a temporary's — and a global's is the one
|
|
||||||
;;;; storage this milestone can prove permanent: fixed in .data for the
|
|
||||||
;;;; process — as is a field of one, and an ELEMENT of one when the global
|
|
||||||
;;;; is an array, whose elements sit inside its own storage. An element of a
|
|
||||||
;;;; global SLICE is not: the slice is ptr+len and says nothing about where
|
|
||||||
;;;; the data is — and that holds at every index of a multi-index (at g i j),
|
|
||||||
;;;; not just the first, so one slice level anywhere in the walk refuses.
|
|
||||||
;;;; test_flan.ml's checker tests carry the refusal side of this (a local
|
|
||||||
;;;; Vec, a Vec parameter, a Vec behind a Ptr, a slice rebound to a local, an
|
|
||||||
;;;; element of a global slice, and an element reached through a slice at a
|
|
||||||
;;;; later index level); this program is the acceptance side, over storage
|
|
||||||
;;;; the guard allows.
|
|
||||||
;;;;
|
;;;;
|
||||||
;;;; Mode 0 is the survey: a read through the view boxes the element
|
;;;; Mode 0 is the survey: a read through the view boxes the element
|
||||||
;;;; correctly, a write through either side is seen through the other, and a
|
;;;; correctly, a write through either side is seen through the other, and a
|
||||||
|
|||||||
@ -5838,6 +5838,74 @@ level "1"
|
|||||||
dyn_view ~opt:"-O0" ();
|
dyn_view ~opt:"-O0" ();
|
||||||
dyn_view ~x86:true ();
|
dyn_view ~x86:true ();
|
||||||
|
|
||||||
|
(* ── Any typed container crosses as a view ─────────────────────────
|
||||||
|
programs/dyn-view-any.flan: every integer width, f32, bool, str, a
|
||||||
|
struct and a padded one, nested arrays, arrays and Vecs of structs,
|
||||||
|
over locals, parameters, a temporary, a heap slice and an arena's Vec
|
||||||
|
(mode 0); then one trap per mode. The traps a release build cannot
|
||||||
|
see — a view kept past its frame, a slice kept past its Vec's growth,
|
||||||
|
past a free, past a free-all — run under --dev only, on both
|
||||||
|
backends. The survey's text was captured from the running program and
|
||||||
|
is the same in all five builds. *)
|
||||||
|
let any_out =
|
||||||
|
"i8 [0 -1 -2]\nu8 [251 252 253]\ni16 [-299 301]\nu16 [60001 2]\n\
|
||||||
|
i32 [-69999 70001]\nu32 [4000000001 2]\ni64 [-4 6]\n\
|
||||||
|
u64 [9000000000000000001 2]\n253\n\
|
||||||
|
f32 [2.75 3.1]\n2.75\n\
|
||||||
|
bool [true true true]\ntrue\n\
|
||||||
|
EIINNOORRSSTT\n\
|
||||||
|
point #Point{:x 0.5 :y 3}\n40\n9.5\n9.5\n2\ntrue\n\
|
||||||
|
mix #Mix{:a 7 :b -9000000000 :c 2.5 :d true :e [1 2 3] :f -4}\n65535\n\
|
||||||
|
named #Named{:name \"ada\" :id 7}\n\
|
||||||
|
grid [[1 2 3] [4 5 60]]\n60\n\
|
||||||
|
points [#Point{:x 1 :y 1} #Point{:x 2 :y 20}]\n20\n\
|
||||||
|
vec [1 2 3]\n3\n6\n5\n4\n3\n\
|
||||||
|
temp #Point{:x 1.5 :y 2}\n4\n3\ntrue\n"
|
||||||
|
in
|
||||||
|
let any_traps =
|
||||||
|
[ ("1", "300 does not fit a u8 element, which holds 0 to 255");
|
||||||
|
("2", "this u64 element is 18000000000000000000, above the largest \
|
||||||
|
dyn int");
|
||||||
|
("3", "a Point has no field :z. Its fields are :x :y");
|
||||||
|
("8", "a str element is read-only through a dyn view") ]
|
||||||
|
and any_stale =
|
||||||
|
[ ("4", "this view points into a local of leak-local, and that call \
|
||||||
|
has returned");
|
||||||
|
("5", "this view's storage, a block of i64, has been released");
|
||||||
|
("6", "this view's storage, a block of i64, has been released");
|
||||||
|
("7", "this view's storage, a block of i32, has been released") ]
|
||||||
|
in
|
||||||
|
let dyn_view_any ?opt ?(x86 = false) ?(dev = false) () =
|
||||||
|
let exe = compile ?opt ~x86 ~dev "programs/dyn-view-any.flan" in
|
||||||
|
let name what =
|
||||||
|
"dyn: any container's view" ^ what
|
||||||
|
^ (match opt with Some o -> ", " ^ o | None -> "")
|
||||||
|
^ (if x86 then ", --x86" else "") ^ (if dev then ", --dev" else "")
|
||||||
|
in
|
||||||
|
let code, text = run exe (Some "0") in
|
||||||
|
if code <> 0 || text <> any_out then begin
|
||||||
|
incr failures;
|
||||||
|
Printf.printf "FAIL %s\n got: %S (exit %d)\n wanted: %S\n"
|
||||||
|
(name "") text code any_out
|
||||||
|
end;
|
||||||
|
List.iter
|
||||||
|
(fun (mode, needle) ->
|
||||||
|
let code, text = run exe (Some mode) in
|
||||||
|
if code <> 134 || not (contains text needle) then begin
|
||||||
|
incr failures;
|
||||||
|
Printf.printf
|
||||||
|
"FAIL %s\n got: %S (exit %d)\n wanted a trap \
|
||||||
|
saying %S\n" (name (", mode " ^ mode)) text code needle
|
||||||
|
end)
|
||||||
|
(any_traps @ if dev then any_stale else []);
|
||||||
|
(try Sys.remove exe with Sys_error _ -> ())
|
||||||
|
in
|
||||||
|
dyn_view_any ();
|
||||||
|
dyn_view_any ~opt:"-O0" ();
|
||||||
|
dyn_view_any ~x86:true ();
|
||||||
|
dyn_view_any ~dev:true ();
|
||||||
|
dyn_view_any ~dev:true ~x86:true ();
|
||||||
|
|
||||||
(* The root count, which is the part of this feature the runs above cannot
|
(* The root count, which is the part of this feature the runs above cannot
|
||||||
check — and the reason has outlived the stub it was first written
|
check — and the reason has outlived the stub it was first written
|
||||||
about. flan_dyn.c's trigger has a one-megabyte floor, and not one
|
about. flan_dyn.c's trigger has a one-megabyte floor, and not one
|
||||||
|
|||||||
@ -1525,105 +1525,80 @@ let () =
|
|||||||
"(defonce v (Vec f64) (vec-new f64))\n\
|
"(defonce v (Vec f64) (vec-new f64))\n\
|
||||||
(defn take [d dyn] i32 1)\n\
|
(defn take [d dyn] i32 1)\n\
|
||||||
(defn main [] i32 (take v))";
|
(defn main [] i32 (take v))";
|
||||||
(* The element restriction is still refused, and by name: a string element
|
(* Every element a view can describe crosses: every number, bool, str,
|
||||||
would need a dyn string's own boxing, whose payload is a pointer into
|
struct, and arrays, slices and Vecs of those. What cannot be described
|
||||||
the collector's heap, planted where nothing will ever trace it. *)
|
is refused by name — a pointer, an Option, a function, a map, an enum
|
||||||
rejects_check "a Vec of strings does not view into dyn yet"
|
or a data type inside the container. *)
|
||||||
|
accepts "a Vec of strings views into dyn, read-only"
|
||||||
"(defonce v (Vec str) (vec-new str))\n\
|
"(defonce v (Vec str) (vec-new str))\n\
|
||||||
(defn take [d dyn] i32 1)\n\
|
(defn take [d dyn] i32 1)\n\
|
||||||
(defn main [] i32 (take v))"
|
(defn main [] i32 (take v))";
|
||||||
~needle:"only when its elements are i64, f64 or bool";
|
accepts "an i32 element views into dyn"
|
||||||
rejects_check "an i32 element is not one of the view's three"
|
|
||||||
"(defonce v (Vec i32) (vec-new i32))\n\
|
"(defonce v (Vec i32) (vec-new i32))\n\
|
||||||
|
(defn take [d dyn] i32 1)\n\
|
||||||
|
(defn main [] i32 (take v))";
|
||||||
|
rejects_check "a Vec of pointers does not view into dyn"
|
||||||
|
"(defonce v (Vec (Ptr i64)) (vec-new (Ptr i64)))\n\
|
||||||
(defn take [d dyn] i32 1)\n\
|
(defn take [d dyn] i32 1)\n\
|
||||||
(defn main [] i32 (take v))"
|
(defn main [] i32 (take v))"
|
||||||
~needle:"only when its elements are i64, f64 or bool";
|
~needle:"a (Ptr i64) is none of these";
|
||||||
|
rejects_check "a struct with an Option field names the field's type"
|
||||||
|
"(defstruct Maybe [x (Option i64)])\n\
|
||||||
|
(defn take [d dyn] i32 1)\n\
|
||||||
|
(defn main [] i32 (take (Maybe {.x None})))"
|
||||||
|
~needle:"a (Option i64) is none of these";
|
||||||
(* A typed (Map K V) is unrelated to item 3 and keeps its own refusal. *)
|
(* A typed (Map K V) is unrelated to item 3 and keeps its own refusal. *)
|
||||||
rejects_check "a typed Map still refuses into dyn"
|
rejects_check "a typed Map still refuses into dyn"
|
||||||
"(defonce m (Map i64 i64) (map-new i64 i64))\n\
|
"(defonce m (Map i64 i64) (map-new i64 i64))\n\
|
||||||
(defn take [d dyn] i32 1)\n\
|
(defn take [d dyn] i32 1)\n\
|
||||||
(defn main [] i32 (take m))"
|
(defn main [] i32 (take m))"
|
||||||
~needle:"does not cross into dyn yet";
|
~needle:"does not cross into dyn yet";
|
||||||
(* Which of the two refusals wins when both apply. A LOCAL (Vec string)
|
(* Any storage: a local, a parameter, a temporary, a slice bound to a
|
||||||
fails the lifetime guard and the element check both, and the element
|
local, an element of a global slice, and a container behind a pointer.
|
||||||
one has to be the one that speaks: the lifetime message names
|
A dev build checks each against its frame or its block at run time
|
||||||
(defonce g ...) as the spelling that works, and for a string element
|
(test_acceptance.ml, dyn-view-any.flan); nothing is refused here. *)
|
||||||
the global spelling is refused too, so the other order would hand back
|
accepts "a local Vec views into dyn"
|
||||||
advice that fails when taken. *)
|
|
||||||
rejects_check "a local Vec of strings gets the element refusal, not the \
|
|
||||||
lifetime one"
|
|
||||||
"(defn take [d dyn] i32 1)\n\
|
"(defn take [d dyn] i32 1)\n\
|
||||||
(defn main [] i32 (let [v (vec-new str)] (take v)))"
|
(defn main [] i32 (let [v (vec-new i64)] (take v)))";
|
||||||
~needle:"only when its elements are i64, f64 or bool";
|
accepts "a Vec parameter views into dyn"
|
||||||
(* ── The lifetime guard, added on review ─────────────────────────
|
|
||||||
A local, a parameter and a temporary all answer false to
|
|
||||||
[permanent_root], and each gets the same message rather than "cannot be
|
|
||||||
indexed" or some other accident of which path noticed. *)
|
|
||||||
rejects_check "a local Vec does not view into dyn — its frame ends"
|
|
||||||
"(defn take [d dyn] i32 1)\n\
|
|
||||||
(defn main [] i32 (let [v (vec-new i64)] (take v)))"
|
|
||||||
~needle:"only when it is a global";
|
|
||||||
rejects_check "a Vec parameter does not view into dyn"
|
|
||||||
"(defn take [d dyn] i32 1)\n\
|
"(defn take [d dyn] i32 1)\n\
|
||||||
(defn give [v (Vec i64)] i32 (take v))\n\
|
(defn give [v (Vec i64)] i32 (take v))\n\
|
||||||
(defn main [] i32 0)"
|
(defn main [] i32 0)";
|
||||||
~needle:"only when it is a global";
|
accepts "a fixed array local views into dyn"
|
||||||
rejects_check "a fixed array local does not view into dyn"
|
|
||||||
"(defn take [d dyn] i32 1)\n\
|
"(defn take [d dyn] i32 1)\n\
|
||||||
(defn main [] i32 (let [a (array 4 i64)] (take a)))"
|
(defn main [] i32 (let [a (array 4 i64)] (take a)))";
|
||||||
~needle:"only when it is a global";
|
accepts "a slice cut from a global inline views into dyn"
|
||||||
(* A slice cut from a global is permanent; the same slice expression
|
|
||||||
rebound to a local first loses the trace back to it and is refused —
|
|
||||||
conservative rather than wrong, and the message says what does work. *)
|
|
||||||
accepts "a slice cut from a global inline is still permanent"
|
|
||||||
"(defonce xs [3 i64])\n\
|
"(defonce xs [3 i64])\n\
|
||||||
(defn take [d dyn] i32 1)\n\
|
(defn take [d dyn] i32 1)\n\
|
||||||
(defn main [] i32 (take (slice xs 0 3)))";
|
(defn main [] i32 (take (slice xs 0 3)))";
|
||||||
rejects_check "a slice rebound to a local loses the trace and is refused"
|
accepts "a slice bound to a local views into dyn"
|
||||||
"(defonce xs [3 i64])\n\
|
"(defonce xs [3 i64])\n\
|
||||||
(defn take [d dyn] i32 1)\n\
|
(defn take [d dyn] i32 1)\n\
|
||||||
(defn main [] i32 (let [s (slice xs 0 3)] (take s)))"
|
(defn main [] i32 (let [s (slice xs 0 3)] (take s)))";
|
||||||
~needle:"only when it is a global";
|
accepts "an element of a global array views into dyn"
|
||||||
(* An element of a global is permanent only when the global is an ARRAY.
|
|
||||||
An array's elements are inside the global's own storage; a slice's are
|
|
||||||
not — a global [[T]] holds ptr+len and nothing more, and what they
|
|
||||||
point at may be a frame that has already returned. The refusal row
|
|
||||||
below is one word different from the acceptance row above it, which is
|
|
||||||
the point: it is the [At] arm's demand for an array at the level being
|
|
||||||
indexed and nothing else deciding. Before that guard the refusal row
|
|
||||||
compiled and segfaulted with no diagnostic at all. *)
|
|
||||||
accepts "an element of a global array is permanent"
|
|
||||||
"(defonce rows [2 (Vec i64)])\n\
|
"(defonce rows [2 (Vec i64)])\n\
|
||||||
(defn take [d dyn] i32 1)\n\
|
(defn take [d dyn] i32 1)\n\
|
||||||
(defn main [] i32 (take (at rows 0)))";
|
(defn main [] i32 (take (at rows 0)))";
|
||||||
rejects_check "an element of a global slice is not permanent"
|
accepts "an element of a global slice views into dyn"
|
||||||
"(defonce sv [(Vec i64)])\n\
|
"(defonce sv [(Vec i64)])\n\
|
||||||
(defn take [d dyn] i32 1)\n\
|
(defn take [d dyn] i32 1)\n\
|
||||||
(defn main [] i32 (take (at sv 0)))"
|
(defn main [] i32 (take (at sv 0)))";
|
||||||
~needle:"only when it is a global";
|
accepts "an element of a global array of arrays views into dyn"
|
||||||
(* [(at g i j)] is ONE typed node holding both indices, not two nested
|
|
||||||
ones, so a guard that reads the target's type alone sees level zero and
|
|
||||||
nothing after it. These two rows pin the multi-index spelling on both
|
|
||||||
sides: every level an array is permanent, and a slice at ANY level is
|
|
||||||
not — including the second, which the one-level guard accepted and
|
|
||||||
which then printed a dead frame's contents with exit 0. *)
|
|
||||||
accepts "an element of a global array of arrays is permanent"
|
|
||||||
"(defonce rows [2 [3 (Vec i64)]])\n\
|
"(defonce rows [2 [3 (Vec i64)]])\n\
|
||||||
(defn take [d dyn] i32 1)\n\
|
(defn take [d dyn] i32 1)\n\
|
||||||
(defn main [] i32 (take (at rows 0 1)))";
|
(defn main [] i32 (take (at rows 0 1)))";
|
||||||
rejects_check "an element reached through a slice level is not permanent"
|
accepts "an element reached through a slice level views into dyn"
|
||||||
"(defonce g [2 [[3 i64]]])\n\
|
"(defonce g [2 [[3 i64]]])\n\
|
||||||
(defn take [d dyn] i32 1)\n\
|
(defn take [d dyn] i32 1)\n\
|
||||||
(defn main [] i32 (take (at g 0 1)))"
|
(defn main [] i32 (take (at g 0 1)))";
|
||||||
~needle:"only when it is a global";
|
accepts "a Vec behind a Ptr views into dyn"
|
||||||
(* A Vec behind a Ptr is refused even though some Ptrs really are
|
|
||||||
heap-durable — the checker cannot tell this one from a Ptr taken off a
|
|
||||||
local, and admitting one admits the other. *)
|
|
||||||
rejects_check "a Vec behind a Ptr does not view into dyn"
|
|
||||||
"(defn take [d dyn] i32 1)\n\
|
"(defn take [d dyn] i32 1)\n\
|
||||||
(defn use [p (Ptr (Vec i64))] i32 (take (deref p)))\n\
|
(defn use [p (Ptr (Vec i64))] i32 (take (deref p)))\n\
|
||||||
(defn main [] i32 0)"
|
(defn main [] i32 0)";
|
||||||
~needle:"only when it is a global";
|
accepts "a struct views into dyn"
|
||||||
|
"(defstruct P [x f32 y u8])\n\
|
||||||
|
(defn take [d dyn] i32 1)\n\
|
||||||
|
(defn main [] i32 (let [p (P {.x 1.0 .y 2})] (take p)))";
|
||||||
(* A bracket *literal* is not a typed container yet, and where a dyn is
|
(* A bracket *literal* is not a typed container yet, and where a dyn is
|
||||||
wanted it builds the runtime's own vec instead — the lowering the map
|
wanted it builds the runtime's own vec instead — the lowering the map
|
||||||
literal's values ride on, and what makes {:xs [1 2]} mean what it
|
literal's values ride on, and what makes {:xs [1 2]} mean what it
|
||||||
@ -3700,12 +3675,10 @@ let () =
|
|||||||
|
|
||||||
The three-element spelling does not mean this, and could not. A defonce
|
The three-element spelling does not mean this, and could not. A defonce
|
||||||
whose third element is not a type is a *dyn* global by the 2026-09-20
|
whose third element is not a type is a *dyn* global by the 2026-09-20
|
||||||
rule, and a typed fixed array crosses into dyn only as a view of storage
|
rule, and a typed fixed array crosses into dyn as a view of its
|
||||||
that outlives the view. A freshly built array is a temporary, so the view
|
storage. A freshly built array is the initialiser's temporary, gone when
|
||||||
lifetime guard refuses it — and where the elements are an array rather
|
the initialiser returns, so a view of it is refused there by name with
|
||||||
than one of the three scalar widths a view carries, the element refusal
|
the typed spelling as the fix. *)
|
||||||
gets there first. Both refusals are the ones any other temporary gets;
|
|
||||||
neither was written for this form. *)
|
|
||||||
defvar_reading "a typed array-fill global is computed, not zeroed"
|
defvar_reading "a typed array-fill global is computed, not zeroed"
|
||||||
"(defconst rows 2) (defconst cols 3)\n\
|
"(defconst rows 2) (defconst cols 3)\n\
|
||||||
(defonce grid [rows [cols u8]] (array-fill [rows cols] 255))\n\
|
(defonce grid [rows [cols u8]] (array-fill [rows cols] 255))\n\
|
||||||
@ -3713,10 +3686,11 @@ let () =
|
|||||||
"grid" ~ty:"[2 [3 u8]]" ~zeroed:false;
|
"grid" ~ty:"[2 [3 u8]]" ~zeroed:false;
|
||||||
rejects_check "a three-element array-fill defonce is the dyn reading"
|
rejects_check "a three-element array-fill defonce is the dyn reading"
|
||||||
"(defonce xs (array-fill [3] (i64 1))) (defn f [] ())"
|
"(defonce xs (array-fill [3] (i64 1))) (defn f [] ())"
|
||||||
~needle:"only when it is a global";
|
~needle:"xs is a dyn global, and its initialiser builds a [3 i64] that is \
|
||||||
rejects_check "and its element type is asked about first"
|
gone once the initialiser returns";
|
||||||
|
rejects_check "and the fix it names is the typed spelling"
|
||||||
"(defonce grid (array-fill [2 3] 255)) (defn f [] ())"
|
"(defonce grid (array-fill [2 3] 255)) (defn f [] ())"
|
||||||
~needle:"only when its elements are i64, f64 or bool";
|
~needle:"as in (defonce grid [2 [3 i32]] ...)";
|
||||||
(* A defconst is not a second path to it: its value is what the linker
|
(* A defconst is not a second path to it: its value is what the linker
|
||||||
writes into the image, and a fill is a loop. *)
|
writes into the image, and a fill is a loop. *)
|
||||||
rejects_check "array-fill is not a constant's value"
|
rejects_check "array-fill is not a constant's value"
|
||||||
@ -7664,14 +7638,18 @@ let () =
|
|||||||
infers "the names the element type of a mixed literal" "(the [dyn] [1 2.5])" "[2 dyn]";
|
infers "the names the element type of a mixed literal" "(the [dyn] [1 2.5])" "[2 dyn]";
|
||||||
infers "the with a slice type gives the literal's array type"
|
infers "the with a slice type gives the literal's array type"
|
||||||
"(the [f32] [1 2.5])" "[2 f32]";
|
"(the [f32] [1 2.5])" "[2 f32]";
|
||||||
(match checked "(defstruct P [x i32]) (defn main [] i32 (let [a [(P 1) 2]] 0))" with
|
(* A struct crosses into dyn as a view, so a struct beside a number is a dyn
|
||||||
| _ -> check "a struct beside a number is refused" false
|
vector; a pointer has no dyn form, so it is refused against the first. *)
|
||||||
|
accepts "a struct beside a number is a dyn vector"
|
||||||
|
"(defstruct P [x i32]) (defn main [] i32 (let [a [(P 1) 2]] (println a) 0))";
|
||||||
|
(match checked "(defn main [] i32 (let [x 1 a [(addr x) 2]] 0))" with
|
||||||
|
| _ -> check "a pointer beside a number is refused" false
|
||||||
| exception Loc.Error d ->
|
| exception Loc.Error d ->
|
||||||
check "elements that cannot become a dyn are refused against the first"
|
check "elements that cannot become a dyn are refused against the first"
|
||||||
(contains d.Loc.dmsg "expected P, found the integer literal 2"
|
(contains d.Loc.dmsg "expected (Ptr i32), found the integer literal 2"
|
||||||
&& List.exists
|
&& List.exists
|
||||||
(fun (n : Loc.note) ->
|
(fun (n : Loc.note) ->
|
||||||
contains n.Loc.nmsg "this array's first element is P")
|
contains n.Loc.nmsg "this array's first element is (Ptr i32)")
|
||||||
d.Loc.notes));
|
d.Loc.notes));
|
||||||
(match checked "(defn g [x $t] i32 (let [a [x 1]] 0))" with
|
(match checked "(defn g [x $t] i32 (let [a [x 1]] 0))" with
|
||||||
| _ -> check "a type variable beside a literal is refused" false
|
| _ -> check "a type variable beside a literal is refused" false
|
||||||
|
|||||||
@ -262,6 +262,10 @@ let corpus =
|
|||||||
view that grows and moves the Vec leaves anything for ASan's
|
view that grows and moves the Vec leaves anything for ASan's
|
||||||
use-after-free detection to find. *)
|
use-after-free detection to find. *)
|
||||||
"programs/dyn-view.flan", [ "0" ];
|
"programs/dyn-view.flan", [ "0" ];
|
||||||
|
(* Views of every element type over locals, parameters, a temporary, a
|
||||||
|
heap slice and an arena's Vec: the offsets the runtime computes from
|
||||||
|
a descriptor, read and written under ASan. *)
|
||||||
|
"programs/dyn-view-any.flan", [ "0" ];
|
||||||
"programs/x86-p13-dyn-collect.flan", [];
|
"programs/x86-p13-dyn-collect.flan", [];
|
||||||
"programs/sand-headless.flan", [];
|
"programs/sand-headless.flan", [];
|
||||||
"programs/signedness.flan", [];
|
"programs/signedness.flan", [];
|
||||||
|
|||||||
@ -930,46 +930,54 @@ let checks name text =
|
|||||||
let () =
|
let () =
|
||||||
let poke_fln = "fn poke(coll) -> dyn\n coll[0] = 99\n coll\n\n" in
|
let poke_fln = "fn poke(coll) -> dyn\n coll[0] = 99\n coll\n\n" in
|
||||||
let poke_flan = "(defn poke [coll] dyn (set (at coll 0) 99) coll)\n" in
|
let poke_flan = "(defn poke [coll] dyn (set (at coll 0) 99) coll)\n" in
|
||||||
(* A typed local is not a global, so no dyn value may see into it. *)
|
(* A container of pointers has no dyn view, and the refusal's subject and
|
||||||
|
fix follow what the container is: a local, a temporary, a parameter. *)
|
||||||
refused "view-local.fln"
|
refused "view-local.fln"
|
||||||
(poke_fln ^ "fn main() -> ()\n let a: [4 i64] = [6 2 4 9]\n poke(a)\n")
|
(poke_fln ^ "fn main() -> ()\n let a = vec-new(Ptr(i64))\n poke(a)\n")
|
||||||
[ "a is a [4 i64], and a dyn value is wanted here";
|
[ "a is a Vec(Ptr(i64)), and a dyn value is wanted here";
|
||||||
"a local, a parameter or a temporary";
|
"a Ptr(i64) is none of these";
|
||||||
"as in let a: dyn = [...]" ];
|
"as in let a: dyn = [...]" ];
|
||||||
refused "view-local.flan"
|
refused "view-local.flan"
|
||||||
(poke_flan ^ "(defn main [] () (let [a (array 4 i64)] (poke a)))\n")
|
(poke_flan ^ "(defn main [] () (let [a (vec-new (Ptr i64))] (poke a)))\n")
|
||||||
[ "a is a [4 i64]"; "as in (let [a (the dyn [...])] ...)" ];
|
[ "a is a (Vec (Ptr i64))"; "as in (let [a (the dyn [...])] ...)" ];
|
||||||
refused "view-temp.flan"
|
refused "view-temp.flan"
|
||||||
(poke_flan ^ "(defn main [] () (poke (array 4 i64)))\n")
|
(poke_flan ^ "(defn main [] () (poke (vec-new (Ptr i64))))\n")
|
||||||
[ "This is a [4 i64]"; "as in (the dyn [...])" ];
|
[ "This is a (Vec (Ptr i64))"; "as in (the dyn [...])" ];
|
||||||
(* An unannotated literal is [4 i32], whose elements no view carries. *)
|
(* Any storage and any number crosses: a local [4 i32] is a view. *)
|
||||||
refused "view-elem.fln"
|
checks "view-elem.fln"
|
||||||
(poke_fln ^ "fn main() -> ()\n let d = [6 2 4 9]\n poke(d)\n")
|
(poke_fln ^ "fn main() -> ()\n let d = [6 2 4 9]\n poke(d)\n");
|
||||||
[ "d is a [4 i32]"; "only when its elements are i64, f64 or bool, and these are i32";
|
|
||||||
"as in let d: dyn = [...]" ];
|
|
||||||
(* A parameter is made by the caller, so its fix is its declaration. *)
|
(* A parameter is made by the caller, so its fix is its declaration. *)
|
||||||
refused "view-param.fln"
|
refused "view-param.fln"
|
||||||
"fn take(d) -> i32 = 1\n\nfn give(n: i32, v: [4 i64]) -> i32\n take(v)\n\n\
|
"fn take(d) -> i32 = 1\n\nfn give(n: i32, v: [Ptr(i64)]) -> i32\n take(v)\n\n\
|
||||||
fn main() -> i32 = 0\n"
|
fn main() -> i32 = 0\n"
|
||||||
[ "v is a [4 i64] parameter"; "Declare v as dyn in give's parameters: v: dyn" ];
|
[ "v is a [Ptr(i64)] parameter"; "Declare v as dyn in give's parameters: v: dyn" ];
|
||||||
refused "view-param.flan"
|
refused "view-param.flan"
|
||||||
"(defn take [d dyn] i32 1)\n(defn give [n i32 v (Vec i64)] i32 (take v))\n\
|
"(defn take [d dyn] i32 1)\n(defn give [n i32 v (Vec (Ptr i64))] i32 (take v))\n\
|
||||||
(defn main [] i32 0)\n"
|
(defn main [] i32 0)\n"
|
||||||
[ "v is a (Vec i64) parameter"; "Declare v as dyn in give's parameters: v dyn" ];
|
[ "v is a (Vec (Ptr i64)) parameter"; "Declare v as dyn in give's parameters: v dyn" ];
|
||||||
checks "view-param-fix.fln"
|
checks "view-param-fix.fln"
|
||||||
"fn take(d) -> i32 = 1\n\nfn give(n: i32, v: dyn) -> i32\n take(v)\n\n\
|
"fn take(d) -> i32 = 1\n\nfn give(n: i32, v: dyn) -> i32\n take(v)\n\n\
|
||||||
fn main() -> i32 = 0\n";
|
fn main() -> i32 = 0\n";
|
||||||
(* A global's fix redefines it, in the form it was defined with. *)
|
(* A global's fix redefines it, in the form it was defined with. *)
|
||||||
let show_flan = "(defn show [d dyn] i32 1)\n" in
|
let show_flan = "(defn show [d dyn] i32 1)\n" in
|
||||||
refused "view-global.flan"
|
refused "view-global.flan"
|
||||||
("(defonce gs [2 i32] [1 2])\n" ^ show_flan ^ "(defn main [] i32 (show gs))\n")
|
("(defonce gs (Vec (Ptr i32)) (vec-new (Ptr i32)))\n" ^ show_flan
|
||||||
[ "gs is a [2 i32]"; "as in (defonce gs dyn [...])" ];
|
^ "(defn main [] i32 (show gs))\n")
|
||||||
|
[ "gs is a (Vec (Ptr i32))"; "as in (defonce gs dyn [...])" ];
|
||||||
refused "view-global-def.flan"
|
refused "view-global-def.flan"
|
||||||
("(def gs [2 i32] [1 2])\n" ^ show_flan ^ "(defn main [] i32 (show gs))\n")
|
("(def gs (Vec (Ptr i32)) (vec-new (Ptr i32)))\n" ^ show_flan
|
||||||
|
^ "(defn main [] i32 (show gs))\n")
|
||||||
[ "as in (def gs dyn [...])" ];
|
[ "as in (def gs dyn [...])" ];
|
||||||
refused "view-global.fln"
|
refused "view-global.fln"
|
||||||
"once gs: [2 i32] = [1 2]\n\nfn show(d) -> i32 = 1\n\nfn main() -> i32 = show(gs)\n"
|
"once gs: Vec(Ptr(i32)) = vec-new(Ptr(i32))\n\nfn show(d) -> i32 = 1\n\n\
|
||||||
[ "gs is a [2 i32]"; "as in once gs: dyn = [...]" ];
|
fn main() -> i32 = show(gs)\n"
|
||||||
|
[ "gs is a Vec(Ptr(i32))"; "as in once gs: dyn = [...]" ];
|
||||||
|
checks "view-global-typed.flan"
|
||||||
|
("(defonce gs [2 i32] [1 2])\n" ^ show_flan ^ "(defn main [] i32 (show gs))\n");
|
||||||
|
(* A dyn global's initialiser cannot view what it builds itself. *)
|
||||||
|
refused "view-global-init.fln"
|
||||||
|
"once xs = array-fill([3], i64(1))\n\nfn main() -> i32 = 0\n"
|
||||||
|
[ "xs is a dyn global"; "as in once xs: [3 i64] = ..." ];
|
||||||
checks "view-global-fix.flan"
|
checks "view-global-fix.flan"
|
||||||
("(defonce gs dyn [1 2])\n(def hs dyn [1 2])\n" ^ show_flan
|
("(defonce gs dyn [1 2])\n(def hs dyn [1 2])\n" ^ show_flan
|
||||||
^ "(defn main [] i32 (show gs) (show hs))\n");
|
^ "(defn main [] i32 (show gs) (show hs))\n");
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user