A pointer's element type can be cast and a slice made from a pointer and a count with slice-from
This commit is contained in:
commit
a14e633a21
17
TODO.org
17
TODO.org
@ -485,10 +485,10 @@ shapes.
|
||||
|
||||
** DONE A pointer from C needs a length before it can be indexed
|
||||
CLOSED: [2026-09-13]
|
||||
=(slice-from-ptr p n)=: the caller states the length and owns being right about
|
||||
=(slice-from p n)=: the caller states the length and owns being right about
|
||||
it. The alternative weighed — a per-binding declaration naming which argument
|
||||
carries the count — cannot reach a count that is a sibling field. No marker on the
|
||||
name; =ptr= is the marker, it owns nothing, and =free= refuses it.
|
||||
carries the count — cannot reach a count that is a sibling field. It owns nothing,
|
||||
and =free= refuses it.
|
||||
|
||||
** DONE A string cannot be returned from C
|
||||
CLOSED: [2026-09-25]
|
||||
@ -669,9 +669,10 @@ of !=.
|
||||
|
||||
* Checker
|
||||
|
||||
** NEXT A slice from a C pointer, and a pointer cast
|
||||
Decided 2026-09-25: =(slice-from p n)= makes a slice from a pointer and a count, and a
|
||||
cast changes a pointer's element type; both unchecked. No pointer arithmetic.
|
||||
** DONE A slice from a C pointer, and a pointer cast
|
||||
CLOSED: [2026-09-25]
|
||||
=(slice-from p n)= and =((Ptr U) p)=, both unchecked. Rules out pointer arithmetic
|
||||
and a cast between a pointer and an integer; =addr= and =slice-from= are the routes.
|
||||
|
||||
** CANCELLED A pointer crossing into dyn
|
||||
Not needed: dyn code has no use for an address it cannot read through.
|
||||
@ -1222,10 +1223,10 @@ allocated in the =restart-case= that offers it. Rules out =use-value= at the
|
||||
failing operation. Only a =saturate= restart on the cast arm alone was left as a
|
||||
question, and it is not written anywhere.
|
||||
|
||||
** DONE slice-from-ptr's runtime refusal names the promise
|
||||
** DONE slice-from's runtime refusal names the promise
|
||||
CLOSED: [2026-09-14]
|
||||
It used to reuse the slice error and report a range and a length the caller
|
||||
never wrote. =slice-from-ptr= is the one form where the compiler cannot check
|
||||
never wrote. =slice-from= is the one form where the compiler cannot check
|
||||
the thing that matters, so its refusal is where the promise is spelled out. The
|
||||
check is signed on purpose: a negative length sign-extended is a huge unsigned
|
||||
value an unsigned compare waves through.
|
||||
|
||||
@ -133,7 +133,7 @@ territory; fix or record, the lane's call.
|
||||
rather than the arithmetic: `flan_f64_to_bytes` and the two dev emitters render any NaN
|
||||
as unsigned `nan`, which is what `format-f64` in the prelude always did. Pinned in
|
||||
`test/programs/format.flan`. See docs/BUILT.md.
|
||||
- **x86's slice-from-ptr refusal is the wrong sentence**: `x86.ml` still reports a
|
||||
- **x86's slice-from-ptr refusal is the wrong sentence** — **fixed** 2026-09-25; x86 calls `flan_slice_promise_error`: `x86.ml` still reports a
|
||||
negative promise through `flan_slice_error` — "slice [0 -2) is out of bounds for
|
||||
length 0", naming a range and a length the caller never wrote — where `emit.ml` has
|
||||
its own `flan_slice_promise_error`. Same condition and same exit on both sides, only
|
||||
|
||||
@ -778,24 +778,10 @@ binding over a `const char *` cursor meets it.
|
||||
|
||||
### A.2 There is no cast between pointer types
|
||||
|
||||
> **Half closed, 2026-09-14, and the other half turned out to be somewhere else.** The
|
||||
> header check does now accept it: a C `void *` is opaque about *what* it points at, so
|
||||
> `ptr_agrees` lets a `(Ptr` anything `)` stand against one, and that was the refusal this
|
||||
> section hit. What the refusal was masking is that `lib/shim.ml` will not take a second
|
||||
> `declare-c` for a C symbol it already has — a shim emits one C prototype per
|
||||
> declaration, and two prototypes for `UpdateTexture` that disagree about a parameter type
|
||||
> is a C file that does not compile. So the natural binding fix named below is refused
|
||||
> after all, by a rule that is right.
|
||||
>
|
||||
> Its own message says what to write instead — "another Flan name for it is a defn" — and
|
||||
> `vendor/raylib/raylib.flan` now has `update-texture-colors`, a one-line defn over the
|
||||
> generated `update-texture` that spells the conversion as `(addr (.r pixels))`. The
|
||||
> `(Ptr u8)` face has to stay the declared one, because `(.data im-copy)` is already a
|
||||
> `(Ptr u8)` over the same bytes and `(Ptr u8)` → `(Ptr Color)` is the direction that
|
||||
> cannot be written. The example's call site is `(rl/update-texture-colors texture pixels)`
|
||||
> and the slice-and-index is gone; the cast still exists, once, with a name and a comment
|
||||
> on it. **What is still open is the general thing this section is about** — a pointer
|
||||
> reinterpretation — and it is not a checker arm.
|
||||
> **Closed, 2026-09-25.** `((Ptr U) p)` casts a pointer's element type, spelled as `(i32 x)`
|
||||
> is, and checks nothing; it may add const and may not drop it. The example's call is now
|
||||
> `(rl/update-texture texture ((Ptr u8) pixels))`; `update-texture-colors` stays as a one-line defn over the cast. There is
|
||||
> still no cast between a pointer and an integer, and no pointer arithmetic.
|
||||
|
||||
|
||||
`textures_image_processing` hands the pixels `LoadImageColors` returned straight to
|
||||
@ -816,7 +802,7 @@ A.1, with the same message. **Not built.**
|
||||
the first element is the address of the buffer.
|
||||
|
||||
```flan
|
||||
(rl/update-texture texture (addr (.r (at (slice-from-ptr pixels n) 0))))
|
||||
(rl/update-texture texture (addr (.r (at (slice-from pixels n) 0))))
|
||||
```
|
||||
|
||||
`Color`'s first field is `r`, a `u8`, at offset 0. It compiles, it is the right address,
|
||||
@ -827,7 +813,7 @@ often — `void *` appears thirty-odd times in raylib.h alone.
|
||||
Two smaller notes on the same call. `(.data im-copy)` is already a `(Ptr u8)` over the same
|
||||
bytes, so the whole round trip is avoidable; the example keeps it because it is what the C
|
||||
does and because nothing else in the corpus exercises
|
||||
`LoadImageColors`/`UnloadImageColors`. And `slice-from-ptr` is what makes any of this
|
||||
`LoadImageColors`/`UnloadImageColors`. And `slice-from` is what makes any of this
|
||||
readable — it is the one form that turns a raylib pointer plus a raylib count into
|
||||
something with a length, and it was reached for three times across the two files.
|
||||
|
||||
|
||||
@ -173,7 +173,7 @@ face says.")
|
||||
;; files
|
||||
"slurp" "barf" "delete-file" "make-directory" "rename-file"
|
||||
;; containers and memory
|
||||
"length" "at" "slice" "slice-from-ptr" "addr" "deref"
|
||||
"length" "at" "slice" "slice-from" "addr" "deref"
|
||||
;; options, bytes, the host
|
||||
"Some" "bytes" "bytes-view" "string"
|
||||
"bytes->f64" "bytes->i64" "f64->bytes" "i64->bytes"
|
||||
|
||||
@ -190,11 +190,11 @@
|
||||
(defer (rl/close-window))
|
||||
|
||||
;; Turn each UTF-8 character of the text into the codepoint the font file
|
||||
;; indexes its glyphs by. raylib owns the array; slice-from-ptr is the
|
||||
;; indexes its glyphs by. raylib owns the array; slice-from is the
|
||||
;; promise that there are `codepoint-count` of them behind the pointer, and
|
||||
;; raylib's own out-parameter is where that number came from.
|
||||
(let [codepoints (rl/load-codepoints text (addr codepoint-count))]
|
||||
(collect-unique (slice-from-ptr codepoints codepoint-count))
|
||||
(collect-unique (slice-from codepoints codepoint-count))
|
||||
(rl/unload-codepoints codepoints))
|
||||
|
||||
;; The atlas is generated here, from the deduplicated set — a smaller set is
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
;;;; raylib [text] example - Rectangle bounds
|
||||
;;;;
|
||||
;;;; examples/text/text_rectangle_bounds.c. This is the example that could not
|
||||
;;;; be ported at all until `slice-from-ptr` existed, and it is the reason the
|
||||
;;;; be ported at all until `slice-from` existed, and it is the reason the
|
||||
;;;; form was built rather than an application found for it afterwards.
|
||||
;;;;
|
||||
;;;; Its inner loop reads `font.recs[index]` and `font.glyphs[index]`. Both
|
||||
@ -12,7 +12,7 @@
|
||||
;;;; readable surface of a two-hundred-glyph array.
|
||||
;;;;
|
||||
;;;; The count is not missing; it is in the struct, one field over, as
|
||||
;;;; `glyph-count`. What was missing was a way to say so. `slice-from-ptr` is
|
||||
;;;; `glyph-count`. What was missing was a way to say so. `slice-from` is
|
||||
;;;; that way, and `rl/font-recs` and `rl/font-glyphs` in the bindings are
|
||||
;;;; where this example says it — once, beside the invariant, rather than at
|
||||
;;;; every call site. Note which shape this is: the count is a sibling *field*,
|
||||
@ -59,7 +59,7 @@
|
||||
|
||||
;; Draw `text` inside `rec`, breaking lines on words when `word-wrap?`.
|
||||
;;
|
||||
;; The two `slice-from-ptr` uses are inside rl/font-recs and rl/font-glyphs;
|
||||
;; The two `slice-from` uses are inside rl/font-recs and rl/font-glyphs;
|
||||
;; from here they are ordinary slices, bounds-checked like any other, and the
|
||||
;; index comes from raylib's own get-glyph-index so it is in range by
|
||||
;; construction.
|
||||
|
||||
@ -143,21 +143,15 @@
|
||||
;; because it is what the C does and because the pair is the only thing in the
|
||||
;; corpus that exercises it.
|
||||
;;
|
||||
;; The awkward step in it is gone. LoadImageColors answers a (Ptr Color) and
|
||||
;; UpdateTexture's parameter is `const void *`, which the importer has to
|
||||
;; render as *something* and renders as (Ptr u8) — so the call used to be
|
||||
;; written `(addr (.r (at (slice-from-ptr pixels n) 0)))`: the address of the
|
||||
;; red channel of pixel zero, which is the address of the buffer said the long
|
||||
;; way round. A `void *` is opaque about what it points at by construction,
|
||||
;; and the header check now knows that, so the package can offer the same C
|
||||
;; function under the element type the caller actually has.
|
||||
;; update-texture-colors is that binding and this is its only caller.
|
||||
;; LoadImageColors answers a (Ptr Color) and UpdateTexture's parameter is
|
||||
;; `const void *`, which the importer renders as (Ptr const u8). The cast says
|
||||
;; the same address is bytes.
|
||||
(defn reload-texture [] ()
|
||||
(rl/unload-image im-copy)
|
||||
(set im-copy (rl/image-copy im-origin))
|
||||
(apply-process (addr im-copy) current-process)
|
||||
(let [pixels (rl/load-image-colors im-copy)]
|
||||
(rl/update-texture-colors texture pixels)
|
||||
(rl/update-texture texture ((Ptr u8) pixels))
|
||||
(rl/unload-image-colors pixels)))
|
||||
|
||||
(defn main [] ()
|
||||
|
||||
138
lib/check.ml
138
lib/check.ml
@ -9345,6 +9345,58 @@ and check_call ctx ~want loc (head : Ast.expr) (args : Ast.expr list) =
|
||||
[ mk loc Types.String (Tast.Str owner) ])
|
||||
| _ -> fail loc "internal: %%res-done takes a name — a compiler bug")
|
||||
| Ast.Var name -> named_call ctx ~want loc name args
|
||||
(* ((Ptr Color) p): a pointer cast, spelled the way (i32 x) is — the type
|
||||
is the head. It changes what the pointer is said to point at and nothing
|
||||
else, and checks nothing: the caller is promising the bytes are that
|
||||
type, as with [slice-from]. Both backends already lower a Ptr-to-Ptr
|
||||
[Cast] to no instruction. Adding const is allowed; dropping it is not,
|
||||
or a cast would undo the const [addr] put there. The rule is about the
|
||||
outer pointer only: the cast is unchecked, so ((Ptr (Ptr u8)) q) over a
|
||||
(Ptr const (Ptr const u8)) is refused for the outer const but a
|
||||
(Ptr (Ptr const u8)) casts to (Ptr (Ptr u8)) — what lies deeper is the
|
||||
writer's promise, like everything else the cast asserts. There is no cast
|
||||
between a pointer and an integer, and no pointer arithmetic. *)
|
||||
| Ast.Call ({ Ast.e = Ast.Var "Ptr"; _ }, _)
|
||||
when (match type_of_expr head with Some _ -> true | None -> false) ->
|
||||
let target = resolve ctx.env (Option.get (type_of_expr head)) in
|
||||
let spelled = Types.to_string target in
|
||||
(match args with
|
||||
| [ a ] ->
|
||||
let a_loc = a.Ast.loc in
|
||||
let spelled_a = spell_arg "p" a in
|
||||
let a = check ctx a in
|
||||
(match a.Tast.ty, target with
|
||||
| Types.Ptr (Types.Const, _), Types.Ptr (Types.Mut, u) ->
|
||||
fail loc
|
||||
"%s is a %s, which cannot be written through, and %s would allow \
|
||||
writes. Write (%s %s)"
|
||||
spelled_a (Types.to_string a.Tast.ty) spelled
|
||||
(Types.to_string (Types.Ptr (Types.Const, u))) spelled_a
|
||||
| Types.Ptr _, _ ->
|
||||
expect ctx loc ~want
|
||||
(mk loc target (Tast.Prim (Tast.Cast target, [ a ])))
|
||||
| (Types.Slice _ | Types.Array _ | Types.String), _ ->
|
||||
fail a_loc
|
||||
"%s converts a pointer, found %s. The address of the first \
|
||||
element is (addr (at %s 0)); write (%s (addr (at %s 0)))"
|
||||
spelled (Types.to_string a.Tast.ty) spelled_a spelled spelled_a
|
||||
| Types.Int _, _ ->
|
||||
fail a_loc
|
||||
"%s converts a pointer, found %s. There is no conversion between \
|
||||
an integer and a pointer"
|
||||
spelled (Types.to_string a.Tast.ty)
|
||||
| Types.Dyn, _ ->
|
||||
fail a_loc
|
||||
"%s converts a pointer, found dyn. A dyn value never holds a \
|
||||
pointer; give %s a pointer type"
|
||||
spelled spelled_a
|
||||
| other, _ ->
|
||||
fail a_loc
|
||||
"%s converts a pointer, found %s. The address of a place is \
|
||||
(addr %s); write (%s (addr %s))"
|
||||
spelled (Types.to_string other) spelled_a spelled spelled_a)
|
||||
| _ ->
|
||||
fail loc "%s takes one pointer, given %d" spelled (List.length args))
|
||||
(* A computed head: ((choose k) 3). The head is an ordinary expression and
|
||||
the only thing asked of it is that it be a function. *)
|
||||
| _ -> call_value ctx ~want loc (check ctx head) args
|
||||
@ -11003,11 +11055,11 @@ and named_call ?(qualified = false) ctx ~want loc name args =
|
||||
(match target.Tast.ty with
|
||||
| Types.Slice (_, e) -> Types.to_string e
|
||||
| t -> Types.to_string t)
|
||||
(* A view written right here — (slice ...) or (slice-from-ptr ...) —
|
||||
(* A view written right here — (slice ...) or (slice-from ...) —
|
||||
is storage something else owns, known without running anything. *)
|
||||
| Types.Slice (Types.Mut, _)
|
||||
when (match (List.hd args).Ast.e with
|
||||
| Ast.Call ({ Ast.e = Ast.Var ("slice" | "slice-from-ptr"); _ }, _) ->
|
||||
| Ast.Call ({ Ast.e = Ast.Var ("slice" | "slice-from"); _ }, _) ->
|
||||
true
|
||||
| _ -> false) ->
|
||||
fail loc
|
||||
@ -11941,7 +11993,7 @@ and named_call ?(qualified = false) ctx ~want loc name args =
|
||||
expect ctx loc ~want
|
||||
(mk loc result (Tast.Let ([ (s, target) ], [ body ])))))
|
||||
|
||||
(* (slice-from-ptr p n) — TODO.org, "A pointer from C needs a length before
|
||||
(* (slice-from p n) — TODO.org, "A pointer from C needs a length before
|
||||
it can be indexed". A (Ptr T) that came back from C is readable at
|
||||
element 0 through [deref] and nowhere else, because [indexed] takes an
|
||||
Array or a Slice and a pointer is neither. C hands back an address and no
|
||||
@ -11958,42 +12010,72 @@ and named_call ?(qualified = false) ctx ~want loc name args =
|
||||
|
||||
No marker on the name. A [?] in this language means *asks*
|
||||
([font-valid?]), and this does not ask; [zeroed], the nearest
|
||||
neighbour — a value conjured rather than
|
||||
derived — carries no marker either. [ptr] is the marker: a (Ptr T) only
|
||||
ever arrives from a [declare-c], so the word already names the C boundary,
|
||||
and a reader who sees it has already been told where the promise comes
|
||||
from.
|
||||
neighbour — a value conjured rather than derived — carries no marker
|
||||
either. The argument's type is the marker: only a (Ptr T) is accepted,
|
||||
and a (Ptr T) only ever arrives from a [declare-c], an [addr] or a
|
||||
pointer cast, so the site already says where the promise comes from.
|
||||
|
||||
**The count is any integer.** It is widened to i64 here, sign- or
|
||||
zero-extended by its own kind, so both backends see one i64 and the
|
||||
length word is what the caller wrote. The [n >= 0] test the backends
|
||||
plant runs in every build, release included and [--no-bounds-checks]
|
||||
included: it is not a bounds check against a known length (there is
|
||||
none), it is the claim that the word being stored is a count at all. A
|
||||
u64 above 2^63 fails it too, and should — no pointer has that many
|
||||
elements behind it.
|
||||
|
||||
**It owns nothing.** The result is a [Types.Slice], the same non-owning
|
||||
view (slice v) answers; (free s) on it is the program's error, which a
|
||||
dev build's registry traps as a slice no allocator handed out. *)
|
||||
| "slice-from-ptr" ->
|
||||
dev build's registry traps as a slice no allocator handed out. It carries
|
||||
no allocator epoch either — a slice is two words, see TODO.org "A stale
|
||||
slice reads poison in a dev build" — so a view made over arena memory
|
||||
that is later freed reads the dev build's poison and does not trap. *)
|
||||
| "slice-from" ->
|
||||
arity ctx loc name 2 args;
|
||||
(match args with
|
||||
| [ target; n ] ->
|
||||
let target_loc = target.Ast.loc in
|
||||
let spelled_target = spell_arg "p" target in
|
||||
let target = check ctx target in
|
||||
let elem =
|
||||
match target.Tast.ty with
|
||||
| Types.Ptr (_, t) -> t
|
||||
| other ->
|
||||
fail loc
|
||||
"slice-from-ptr takes a (Ptr T) and the number of elements behind \
|
||||
it, found %s"
|
||||
fail target_loc
|
||||
"slice-from takes a (Ptr T) and the number of elements behind \
|
||||
it, found %s. A slice or an array already has a length; \
|
||||
(slice v lo hi) views part of one"
|
||||
(Types.to_string other)
|
||||
in
|
||||
let n_loc = n.Ast.loc in
|
||||
let n = check ctx ~want:index_ty n in
|
||||
let spelled_n = spell_arg "n" n in
|
||||
let n = check ctx n in
|
||||
(match n.Tast.ty with
|
||||
| Types.Int _ -> ()
|
||||
| Types.Var v ->
|
||||
cast_operand ctx n_loc name ~needs:"integer?"
|
||||
~what:"an element count" ~is:"an integer" v
|
||||
| other ->
|
||||
fail n_loc
|
||||
"slice-from counts elements with an integer, found %s. Write \
|
||||
(slice-from %s (i64 %s))"
|
||||
(Types.to_string other) spelled_target spelled_n);
|
||||
(* A negative literal is a lie the checker can see, so it does not wait
|
||||
for the run-time test emit.ml plants beside it. *)
|
||||
(match literal n with
|
||||
| Some k when k < 0L ->
|
||||
fail n_loc
|
||||
"slice-from-ptr length %Ld is negative" k
|
||||
"slice-from length %Ld is negative" k
|
||||
| _ -> ());
|
||||
(* A read-only pointer gives a read-only slice, or [slice-from-ptr]
|
||||
let i64 = Types.Int Types.I64 in
|
||||
let n =
|
||||
if Types.equal n.Tast.ty i64 then n
|
||||
else mk n.Tast.loc i64 (Tast.Prim (Tast.Cast i64, [ n ]))
|
||||
in
|
||||
(* A read-only pointer gives a read-only slice, or [slice-from]
|
||||
would undo the const [addr] put there. *)
|
||||
let m = match target.Tast.ty with Types.Ptr (m, _) -> m | _ -> Types.Mut in
|
||||
prim Tast.SliceFromPtr (Types.Slice (m, elem)) [ target; n ]
|
||||
prim Tast.SliceFrom (Types.Slice (m, elem)) [ target; n ]
|
||||
| _ -> assert false)
|
||||
|
||||
(* ── pointers ──────────────────────────────────────────────────── *)
|
||||
@ -12688,6 +12770,19 @@ and ordinary_call ctx ~want loc name args =
|
||||
says what the view is: over a Vec it borrows the storage the Vec \
|
||||
owns, over an array or a string it looks at the value itself. \
|
||||
Write %s" call
|
||||
else if name = "slice-from-ptr" then
|
||||
(* The name this form had before; code written against it lands here.
|
||||
Said as the form to write, with the reader's arguments, and not as
|
||||
a rename — a first-time reader has no old name to be told about. *)
|
||||
let call =
|
||||
match args with
|
||||
| [ p; n ] ->
|
||||
"(slice-from " ^ spell_arg "p" p ^ " " ^ spell_arg "n" n ^ ")"
|
||||
| _ -> "(slice-from p n)"
|
||||
in
|
||||
Loc.failk "check/unknown-function" loc
|
||||
"there is no slice-from-ptr. A slice from a pointer and a count of \
|
||||
the elements behind it is slice-from. Write %s" call
|
||||
else if no_such_rand name <> None then
|
||||
(* A retired randomness name, which is a name and not a near miss:
|
||||
"did you mean rand?" for [rand-f32] would be true and would not say
|
||||
@ -13895,10 +13990,11 @@ let builtins : (string * string * string) list =
|
||||
runs backwards is refused here. A string slices to a string. A view of \
|
||||
a Vec is a borrow from storage the Vec owns, and a push, a put or a \
|
||||
reserve on that Vec may invalidate it.");
|
||||
("slice-from-ptr", "slice-from-ptr [(Ptr T) i32] [T]",
|
||||
"Puts a length on a pointer that came back from C. The caller promises \
|
||||
it addresses that many initialised T and that they outlive the result; \
|
||||
the compiler checks none of it.");
|
||||
("slice-from", "slice-from [(Ptr T) n] [T]",
|
||||
"Puts a length on a pointer that came back from C; n is any integer \
|
||||
type. The caller promises it addresses that many initialised T and that \
|
||||
they outlive the result; the compiler checks none of it. A negative n \
|
||||
traps in every build.");
|
||||
("addr", "addr [place] (Ptr T)",
|
||||
"The address of a place — a name, (.field x), (at a i) or (deref p) — \
|
||||
and not of an arbitrary expression.");
|
||||
|
||||
18
lib/emit.ml
18
lib/emit.ml
@ -3923,7 +3923,7 @@ and prim f (e : Tast.expr) (p : Tast.prim) (args : Tast.expr list) =
|
||||
let b = fresh f in
|
||||
ins f "%s = insertvalue %%slice %s, i64 %s, 1" b a d;
|
||||
b
|
||||
(* (slice-from-ptr p n): the two words a %slice already is, with the pointer
|
||||
(* (slice-from p n): the two words a %slice already is, with the pointer
|
||||
the caller handed over and the length the caller promised. No new
|
||||
representation — a Slice _ is {ptr, i64} here and in x86.ml, which is
|
||||
exactly ptr+len, so this is an insertvalue pair and no more.
|
||||
@ -3959,11 +3959,10 @@ and prim f (e : Tast.expr) (p : Tast.prim) (args : Tast.expr list) =
|
||||
to be stated, and it was the one place it was not. The signalled
|
||||
BoundsError is unchanged: same three fields, so a handler writes one
|
||||
clause for every bad index in the language. *)
|
||||
| Tast.SliceFromPtr, [ p; n ] ->
|
||||
| Tast.SliceFrom, [ p; n ] ->
|
||||
let pv = value f p in
|
||||
let nv = value f n in
|
||||
let n64 = fresh f in
|
||||
ins f "%s = sext i32 %s to i64" n64 nv;
|
||||
(* check.ml has already widened n to i64, by its own signedness. *)
|
||||
let n64 = value f n in
|
||||
let ok = fresh f in
|
||||
ins f "%s = icmp sge i64 %s, 0" ok n64;
|
||||
signal_block f e.Tast.loc ~guard:(fun () -> guard f) ok (fun id len ->
|
||||
@ -4149,11 +4148,10 @@ and cast f ~guard (x : Tast.expr) target =
|
||||
if Types.signed b then "fptosi" else "fptoui"
|
||||
| Types.Float a, Types.Float b ->
|
||||
if Types.bits_f b > Types.bits_f a then "fpext" else "fptrunc"
|
||||
(* Nothing in the surface language writes this: [check.ml] has no cast
|
||||
between pointer types. The locals thunk does — it is handed a slot's
|
||||
address as a raw pointer and has to read it as the type the slot
|
||||
holds — and under opaque pointers there is no instruction to emit for
|
||||
it, both sides being [ptr]. *)
|
||||
(* ((Ptr U) p), and the locals thunk, which is handed a slot's address
|
||||
as a raw pointer and has to read it as the type the slot holds.
|
||||
Under opaque pointers there is no instruction to emit, both sides
|
||||
being [ptr]. *)
|
||||
| Types.Ptr _, Types.Ptr _ -> "bitcast"
|
||||
(* Also not written in the surface language. [resolve] needs it: the
|
||||
runtime answers a pointer or NULL and the Option is built in the
|
||||
|
||||
@ -903,9 +903,9 @@ and prim f (e : Tast.expr) (p : Tast.prim) (args : Tast.expr list) =
|
||||
in
|
||||
Printf.sprintf "%s(%s, %s, %s, %s)" fn t a b (locstr loc)
|
||||
| _ -> assert false)
|
||||
| Tast.SliceFromPtr, _ ->
|
||||
| Tast.SliceFrom, _ ->
|
||||
at loc
|
||||
"(slice-from-ptr p n) is not in the JS dialect — it makes a slice out of \
|
||||
"(slice-from p n) is not in the JS dialect — it makes a slice out of \
|
||||
an address, and JavaScript has no addresses"
|
||||
(* [Bytes] and [StrOfBytes] are reinterprets in every backend: a string is a
|
||||
run of bytes here as it is there. See the header. *)
|
||||
|
||||
@ -83,7 +83,7 @@ let source = {flan|
|
||||
;;
|
||||
;; It is signalled with `error`, from the runtime rather than from Flan:
|
||||
;; flan_bounds_error, flan_slice_error and flan_slice_promise_error in
|
||||
;; runtime/flan_rt.c — the last of those is (slice-from-ptr p n), whose
|
||||
;; runtime/flan_rt.c — the last of those is (slice-from p n), whose
|
||||
;; message is about the caller's promise because there is no container to
|
||||
;; report, and which fills these fields with (0, n, 0): the condition it
|
||||
;; violated, 0 <= n, written as a range. Between them they are where every
|
||||
@ -1268,7 +1268,7 @@ let source = {flan|
|
||||
;;
|
||||
;; **The result borrows.** It is a view of the process environment, not a copy:
|
||||
;; it needs no allocator and no free, and it stays valid because there is no
|
||||
;; writer — that is the same promise `slice-from-ptr` asks a caller to make,
|
||||
;; writer — that is the same promise `slice-from` asks a caller to make,
|
||||
;; kept here once so that no caller has to. A program that wants to hold the
|
||||
;; value past a point where that reasoning stops being obvious should copy it
|
||||
;; into a Vec, which `concat` of one part already does.
|
||||
@ -1289,7 +1289,7 @@ let source = {flan|
|
||||
p (getenv-raw name (addr n))]
|
||||
(if (< n 0)
|
||||
None
|
||||
(Some (slice-from-ptr p (i32 n))))))
|
||||
(Some (slice-from p (i32 n))))))
|
||||
|
||||
;; ── Byte classes ──────────────────────────────────────────────────────
|
||||
;;
|
||||
@ -2108,7 +2108,7 @@ let source = {flan|
|
||||
p (macro-slurp-raw path (addr n))]
|
||||
(if (< n 0)
|
||||
None
|
||||
(Some (slice-from-ptr p (i32 n))))))
|
||||
(Some (slice-from p (i32 n))))))
|
||||
|
||||
;; ── Form: what a macro takes and what it answers ──────────────────────
|
||||
;;
|
||||
|
||||
@ -888,7 +888,7 @@ let flan_wrapper ?track (fn : Ast.fn) (s : shim) raw : Ast.decl_kind =
|
||||
out ],
|
||||
Some (ty loc (Ast.Tname n)) )
|
||||
| `Str ->
|
||||
(* (string (bytes (string (slice-from-ptr p n)))): a view of C's bytes,
|
||||
(* (string (bytes (string (slice-from p n)))): a view of C's bytes,
|
||||
copied by [bytes] into the context allocator, and seen as a string
|
||||
again. The length is bound before the pointer is, so its address
|
||||
exists to be written through. *)
|
||||
@ -903,7 +903,7 @@ let flan_wrapper ?track (fn : Ast.fn) (s : shim) raw : Ast.decl_kind =
|
||||
( [ app "string"
|
||||
[ app "bytes"
|
||||
[ app "string"
|
||||
[ app "slice-from-ptr" [ v ptr_tmp; v len_tmp ] ] ] ] ],
|
||||
[ app "slice-from" [ v ptr_tmp; v len_tmp ] ] ] ] ],
|
||||
fn.Ast.ret )
|
||||
| _ -> ([ call args ], fn.Ast.ret)
|
||||
in
|
||||
|
||||
@ -25,12 +25,12 @@ type prim =
|
||||
| BitAnd | BitOr | BitXor | Shl | Shr
|
||||
(* containers: fixed arrays and slices only at milestone 2 *)
|
||||
| Len | At | Slice
|
||||
(* (slice-from-ptr p n): a [T] made out of a (Ptr T) and a length the caller
|
||||
(* (slice-from p n): a [T] made out of a (Ptr T) and a length the caller
|
||||
supplies. It builds the same two words [Slice] builds and allocates
|
||||
nothing — the storage stays whoever's it was, which in practice is C's.
|
||||
The one thing the compiler cannot check is whether n is the truth; see
|
||||
check.ml's "slice-from-ptr" case for what it can. *)
|
||||
| SliceFromPtr
|
||||
check.ml's "slice-from" case for what it can. *)
|
||||
| SliceFrom
|
||||
(* the milestone-2 host primitives, plan.org. The four conversions are
|
||||
*text*: bytes->f64 parses "12.5", f64->bytes renders it — that is what
|
||||
calc-me's tokenizer and the prelude's printers each need. *)
|
||||
|
||||
17
lib/x86.ml
17
lib/x86.ml
@ -3458,7 +3458,7 @@ and prim f (e : Tast.expr) (p : Tast.prim) (args : Tast.expr list) dst =
|
||||
load_loc f ~reg:rcx llo lo.Tast.ty;
|
||||
sub_rr f.b ~dst:rax ~src:rcx;
|
||||
store_int f.b ~src:rax ~mm:(lmem f (shift dst 8) ~scratch:r11) ~size:8
|
||||
(* (slice-from-ptr p n): the two words a slice already is, with the pointer
|
||||
(* (slice-from p n): the two words a slice already is, with the pointer
|
||||
the caller handed over and the length the caller promised. A [Slice _] is
|
||||
{ptr, i64} here exactly as it is in [emit.ml], so there is no new
|
||||
representation to build — one store of the pointer and one of the length.
|
||||
@ -3470,9 +3470,9 @@ and prim f (e : Tast.expr) (p : Tast.prim) (args : Tast.expr list) dst =
|
||||
compares are unsigned, and a negative i32 sign-extended to 64 bits is a
|
||||
huge unsigned value that [jbe] waves straight through.
|
||||
|
||||
It reuses [flan_slice_error] for [emit.ml]'s reason: the violated
|
||||
condition is 0 <= n, which has the shape of a reversed slice, so the range
|
||||
is reported as [0 n) against a length of 0.
|
||||
It calls [flan_slice_promise_error], as [emit.ml] does, so both backends
|
||||
say the same sentence: the promise, not a range nobody wrote. The count
|
||||
arrives as an i64 — check.ml widened it — so the compare is on all 64 bits.
|
||||
|
||||
It is not behind [f.md.Emit.checks], and the reason is the one
|
||||
[check_slice] above spells out at length. [--no-bounds-checks] drops a
|
||||
@ -3482,20 +3482,17 @@ and prim f (e : Tast.expr) (p : Tast.prim) (args : Tast.expr list) dst =
|
||||
slice's count, and a count that is negative is not a slice with the
|
||||
bounds check taken off, it is not a slice. So the test runs in every
|
||||
build, exactly as [lo <= hi] does. *)
|
||||
| Tast.SliceFromPtr, [ p; n ] ->
|
||||
| Tast.SliceFrom, [ p; n ] ->
|
||||
let lp = eval f p in
|
||||
let ln = eval f n in
|
||||
scoped f (fun () ->
|
||||
let a = ptmp f and b = ptmp f and c = ptmp f in
|
||||
xor_rr f.b ~dst:rax ~src:rax;
|
||||
store_int f.b ~src:rax ~mm:(Frame a) ~size:8;
|
||||
store_int f.b ~src:rax ~mm:(Frame c) ~size:8;
|
||||
let b = ptmp f in
|
||||
load_loc f ~reg:rax ln n.Tast.ty;
|
||||
store_int f.b ~src:rax ~mm:(Frame b) ~size:8;
|
||||
cmp_imm f.b ~dst:rax 0;
|
||||
let ok = new_label f "inb" in
|
||||
jcc_lbl f.b ~cc:cc_ge ok;
|
||||
bounds_call f "flan_slice_error" e.Tast.loc [ a; b; c ];
|
||||
bounds_call f "flan_slice_promise_error" e.Tast.loc [ b ];
|
||||
lbl f.b ok);
|
||||
load_loc f ~reg:rax lp p.Tast.ty;
|
||||
store_int f.b ~src:rax ~mm:(lmem f dst ~scratch:r11) ~size:8;
|
||||
|
||||
@ -561,7 +561,7 @@ static int64_t fit(int n) {
|
||||
* last thing between that and a 511-byte read. check_slice no longer lets the
|
||||
* value out, which makes the negative case unreachable *from Flan* — and not
|
||||
* from here, because these take a raw (ptr, len) pair and the FFI, a C caller
|
||||
* and slice-from-ptr's promise all reach them too. A function that is correct
|
||||
* and slice-from's promise all reach them too. A function that is correct
|
||||
* on its own arguments does not become incorrect because its callers improved,
|
||||
* and two branches are not the price to argue about. */
|
||||
static size_t clamp_len(int64_t n, size_t cap) {
|
||||
@ -1303,7 +1303,7 @@ void flan_slice_error(const uint8_t *loc, int64_t loclen, int64_t lo,
|
||||
flan_slice_fail(loc, loclen, lo, hi, len);
|
||||
}
|
||||
|
||||
/* ── (slice-from-ptr p n), which has its own refusal ───────────────────
|
||||
/* ── (slice-from p n), which has its own refusal ───────────────────
|
||||
*
|
||||
* It used to borrow flan_slice_error, and what came out named a range and a
|
||||
* length the caller never wrote: "slice [0 -2) is out of bounds for length 0".
|
||||
@ -1329,7 +1329,7 @@ void flan_slice_error(const uint8_t *loc, int64_t loclen, int64_t lo,
|
||||
* Deliberately not (0, n, n) — that reads as a range in bounds, and a handler
|
||||
* testing high <= length would wave the failure through. */
|
||||
static void promise_sentence(int64_t n) {
|
||||
rt_sentence("slice-from-ptr was promised %lld elements behind the pointer, "
|
||||
rt_sentence("slice-from was promised %lld elements behind the pointer, "
|
||||
"and a count is never negative", (long long)n);
|
||||
}
|
||||
|
||||
@ -4118,7 +4118,7 @@ void flan_sleep_ns(int64_t ns) {
|
||||
* a test the language does not offer, since a (Ptr T) only ever arrives from a
|
||||
* declare and nothing in the type says it may be nothing. Absent is *len = -1
|
||||
* and a pointer to a valid empty string; present is *len >= 0 and the
|
||||
* environment's own bytes, which (slice-from-ptr) then views.
|
||||
* environment's own bytes, which (slice-from) then views.
|
||||
*
|
||||
* The bytes are the process environment's and are not copied. They outlive the
|
||||
* call — nothing in this language can call setenv or spawn a process, so there
|
||||
|
||||
@ -169,7 +169,8 @@ Each item: the proposal, then the reason in one line.
|
||||
reads back through the fallback, `defonce(.init-once.counter, i64, 7)`.
|
||||
- **`and`, `or`, `not` are words**, since they are Flan's own names. **Built.**
|
||||
- **Casts and type-taking builtins are calls:** `i32(x)`, `vec-new(u8)`,
|
||||
`max-value(u8)`, `the([3 f32], [1 2 3.5])`. **Built.**
|
||||
`max-value(u8)`, `the([3 f32], [1 2 3.5])`. A pointer cast is the type
|
||||
called: `Ptr(Color)(p)` reads `((Ptr Color) p)`. **Built.**
|
||||
|
||||
### Statements and blocks
|
||||
|
||||
|
||||
@ -34,12 +34,12 @@
|
||||
(= n 7) (set (at arr n) 1) ; write past the end
|
||||
(= n 4) (print (slice s n 9)) ; hi past the end
|
||||
(= n 2) (print (slice s n 1)) ; reversed range
|
||||
;; (slice-from-ptr p n) has nothing to check n against — the caller's
|
||||
;; (slice-from p n) has nothing to check n against — the caller's
|
||||
;; number is the only length there is — so what it checks is that the
|
||||
;; number is not absurd. Signed, deliberately: the comparisons the other
|
||||
;; two checks use are unsigned, and a negative i32 sign-extended to i64
|
||||
;; is a huge unsigned value that sails straight through them.
|
||||
(= n -2) (print (length (slice-from-ptr (addr (at arr 0)) n)))
|
||||
(= n -2) (print (length (slice-from (addr (at arr 0)) n)))
|
||||
|
||||
:else (println "?"))
|
||||
0))
|
||||
|
||||
@ -60,6 +60,6 @@
|
||||
(let [b (bytes "q")]
|
||||
(println (peek (addr (at r 1))) (peek (addr (at "abc" 2)))
|
||||
(peek (addr (at b 0)))
|
||||
(string (slice-from-ptr (addr (at r 7)) 5))))
|
||||
(string (slice-from (addr (at r 7)) 5))))
|
||||
(free names))
|
||||
0)
|
||||
|
||||
38
test/programs/ptr-cast.flan
Normal file
38
test/programs/ptr-cast.flan
Normal file
@ -0,0 +1,38 @@
|
||||
;;;; ((Ptr T) p) and (slice-from p n) together: the bytes of one array read as
|
||||
;;;; another element type, the way a C void * or a (Ptr u8) pixel buffer is
|
||||
;;;; read as the structs it holds.
|
||||
|
||||
(defstruct Rgba [r u8 g u8 b u8 a u8])
|
||||
|
||||
(defonce bytes [8 u8])
|
||||
|
||||
(defn sum [s [const Rgba]] i32
|
||||
(let [acc 0]
|
||||
(dotimes [i (length s)]
|
||||
(set acc (+ acc (i32 (.g (at s i))))))
|
||||
acc))
|
||||
|
||||
(defn main [] ()
|
||||
(dotimes [i 8]
|
||||
(set (at bytes i) (u8 (+ i 1))))
|
||||
|
||||
;; Eight bytes are two Rgba. The count is an i64, not an i32.
|
||||
(let [p ((Ptr Rgba) (addr (at bytes 0)))
|
||||
n (i64 2)
|
||||
px (slice-from p n)]
|
||||
(println (length px)) ; 2
|
||||
(println (.r (at px 0))) ; 1
|
||||
(println (.a (at px 1))) ; 8
|
||||
;; A write through the cast pointer is a write to the bytes.
|
||||
(set (.b (at px 1)) 70)
|
||||
(println (at bytes 6))) ; 70
|
||||
|
||||
;; Adding const is allowed, and the slice keeps it.
|
||||
(let [c ((Ptr const Rgba) (addr (at bytes 0)))]
|
||||
(println (sum (slice-from c (u8 2))))) ; 8
|
||||
|
||||
;; And back: the struct pointer read as bytes again.
|
||||
(let [p ((Ptr Rgba) (addr (at bytes 0)))
|
||||
b ((Ptr u8) p)]
|
||||
(println (deref b)) ; 1
|
||||
(println (length (slice-from b 8))))) ; 8
|
||||
@ -85,7 +85,7 @@
|
||||
(let [total 0
|
||||
raw (rl/load-codepoints cp/text (addr total))]
|
||||
(show "codepoints" total)
|
||||
(cp/collect-unique (slice-from-ptr raw total))
|
||||
(cp/collect-unique (slice-from raw total))
|
||||
(show "unique" cp/unique-count)
|
||||
(dotimes [i 5]
|
||||
(print "unique ") (print i) (print " ")
|
||||
@ -123,7 +123,7 @@
|
||||
|
||||
;; Item 3: and the forward walk is what raylib said the text contains.
|
||||
(let [agrees (= forward-n total)
|
||||
all (slice-from-ptr raw total)]
|
||||
all (slice-from raw total)]
|
||||
(dotimes [i forward-n]
|
||||
(when (and agrees (not (= (at forward i) (at all i))))
|
||||
(set agrees false)))
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
;;;; (slice-from-ptr p n) — TODO.org, "A pointer from C needs a length before
|
||||
;;;; (slice-from p n) — TODO.org, "A pointer from C needs a length before
|
||||
;;;; it can be indexed". The motivating pointers come from C, but nothing about
|
||||
;;;; the form does: a (Ptr T) is a (Ptr T) whoever made it, so this case makes
|
||||
;;;; its own with (addr (at a 0)) and needs no library and no window.
|
||||
@ -16,7 +16,7 @@
|
||||
;;;;
|
||||
;;;; What is NOT here, and is in test_flan.ml's refusal table instead: a
|
||||
;;;; negative literal length, a first argument that is not a pointer, and
|
||||
;;;; (free (slice-from-ptr ...)) — a slice owns nothing, so free refuses it by
|
||||
;;;; (free (slice-from ...)) — a slice owns nothing, so free refuses it by
|
||||
;;;; the rule it already had.
|
||||
|
||||
(defonce a [5 i32])
|
||||
@ -33,7 +33,7 @@
|
||||
|
||||
;; The whole array, as the caller promises it: five elements behind the
|
||||
;; address of the first.
|
||||
(let [s (slice-from-ptr (addr (at a 0)) 5)]
|
||||
(let [s (slice-from (addr (at a 0)) 5)]
|
||||
(println (length s)) ; 5
|
||||
(println (at s 0)) ; 10
|
||||
(println (at s 4)) ; 50
|
||||
@ -45,17 +45,17 @@
|
||||
;; A promise shorter than the truth. Nothing complains — there is nothing to
|
||||
;; complain with — and the length the caller gave is the length indexing and
|
||||
;; the bounds check both use.
|
||||
(let [s (slice-from-ptr (addr (at a 1)) 2)]
|
||||
(let [s (slice-from (addr (at a 1)) 2)]
|
||||
(println (length s)) ; 2
|
||||
(println (total s))) ; 50
|
||||
|
||||
;; Zero is a length like any other. An empty slice is not a null pointer and
|
||||
;; is not an error.
|
||||
(println (length (slice-from-ptr (addr (at a 0)) 0))) ; 0
|
||||
(println (length (slice-from (addr (at a 0)) 0))) ; 0
|
||||
|
||||
;; It is a view, not a copy: a write through the slice is a write to the
|
||||
;; array, and this is what would fail if the form ever grew a memcpy.
|
||||
(let [s (slice-from-ptr (addr (at a 0)) 5)]
|
||||
(let [s (slice-from (addr (at a 0)) 5)]
|
||||
(set (at s 2) 7)
|
||||
(println (at a 2)) ; 7
|
||||
(println (total s)))) ; 127
|
||||
@ -1,6 +1,6 @@
|
||||
;;;; (slice-from-ptr p n) with a length the checker cannot see.
|
||||
;;;; (slice-from p n) with a length the checker cannot see.
|
||||
;;;;
|
||||
;;;; test/programs/slice-from-ptr.flan covers the form itself, but every length
|
||||
;;;; test/programs/slice-from.flan covers the form itself, but every length
|
||||
;;;; in it is a literal, and a negative literal is refused by check.ml before
|
||||
;;;; any code is emitted. So the run-time half of the check -- the one both
|
||||
;;;; backends plant beside the form -- is walked by nothing in the corpus.
|
||||
@ -19,7 +19,7 @@
|
||||
(defn promised [n i32] i32
|
||||
;; n is a parameter, so the checker has no literal to look at.
|
||||
(restart-case
|
||||
(let [s (slice-from-ptr (addr (at a 0)) n)]
|
||||
(let [s (slice-from (addr (at a 0)) n)]
|
||||
(length s))
|
||||
(give-up [] -1)))
|
||||
|
||||
@ -693,7 +693,7 @@ let () =
|
||||
1 2 4 4 5 6 9 9\nEIINNOORRSSTT\n\
|
||||
105\ninsertion\nion\ninsert\nrt\n"
|
||||
in
|
||||
(* (slice-from-ptr p n) — TODO.org, "A pointer from C needs a length before
|
||||
(* (slice-from p n) — TODO.org, "A pointer from C needs a length before
|
||||
it can be indexed". The pointers that motivated it come from C, but the
|
||||
form does not care where one came from, so this case makes its own and
|
||||
needs no library: what it pins is that the result is an ordinary [T] —
|
||||
@ -702,9 +702,18 @@ let () =
|
||||
lines write through it and read the array back. The refusals are in
|
||||
test_flan.ml and the negative-length trap is in programs/bounds.flan. *)
|
||||
let sfp_out = "5\n10\n50\n150\n50\n2\n50\n0\n7\n127\n" in
|
||||
outputs "slice-from-ptr" "programs/slice-from-ptr.flan" sfp_out;
|
||||
outputs ~opt:"-O0" "slice-from-ptr, -O0" "programs/slice-from-ptr.flan"
|
||||
outputs "slice-from" "programs/slice-from.flan" sfp_out;
|
||||
outputs ~opt:"-O0" "slice-from, -O0" "programs/slice-from.flan"
|
||||
sfp_out;
|
||||
(* ((Ptr T) p): bytes read as structs and back, with slice-from counts of
|
||||
other integer types. The cast is no instruction on either backend, so
|
||||
what this pins is that the element size the slice indexes by is the
|
||||
new type's. *)
|
||||
let pcast_out = "2\n1\n8\n70\n8\n1\n8\n" in
|
||||
outputs "pointer cast" "programs/ptr-cast.flan" pcast_out;
|
||||
outputs ~opt:"-O0" "pointer cast, -O0" "programs/ptr-cast.flan" pcast_out;
|
||||
outputs ~x86:true "pointer cast, --x86" "programs/ptr-cast.flan" pcast_out;
|
||||
outputs ~dev:true "pointer cast, dev" "programs/ptr-cast.flan" pcast_out;
|
||||
outputs "slice algorithms" "programs/slices.flan" slices_out;
|
||||
outputs ~opt:"-O0" "slice algorithms, -O0" "programs/slices.flan" slices_out;
|
||||
(* And on the hand-written backend, because indexing a string is the one
|
||||
@ -2936,8 +2945,8 @@ let () =
|
||||
the *reason*: the location, and which index against which length. The
|
||||
line and column are not pinned, because
|
||||
editing the program should not break the test that reads it. *)
|
||||
let bounds ?opt () =
|
||||
let exe = compile ?opt "programs/bounds.flan" in
|
||||
let bounds ?opt ?x86 () =
|
||||
let exe = compile ?opt ?x86 "programs/bounds.flan" in
|
||||
let traps name arg reason =
|
||||
let code, text = run exe (Some arg) in
|
||||
if code <> 134
|
||||
@ -2974,7 +2983,7 @@ let () =
|
||||
slice of length hi - lo as a huge unsigned, which is worse. *)
|
||||
traps "slice with a reversed range" "2"
|
||||
"slice [2 1) is out of bounds for length 5";
|
||||
(* (slice-from-ptr p n) with a length that cannot be true. There is no
|
||||
(* (slice-from p n) with a length that cannot be true. There is no
|
||||
length to compare n against — the caller's number *is* the length — so
|
||||
the only check possible is that it is not negative, and it is a signed
|
||||
one: the two above are unsigned, and a negative i32 sign-extended to
|
||||
@ -2987,12 +2996,15 @@ let () =
|
||||
refusal is where the caller's promise gets stated. The asserted
|
||||
substring stays inside one output line; the second line is the part
|
||||
about what is *not* checked. *)
|
||||
traps "slice-from-ptr with a negative length" "-2"
|
||||
"slice-from-ptr was promised -2 elements behind the pointer";
|
||||
traps "slice-from with a negative length" "-2"
|
||||
"slice-from was promised -2 elements behind the pointer";
|
||||
(try Sys.remove exe with Sys_error _ -> ())
|
||||
in
|
||||
bounds ();
|
||||
bounds ~opt:"-O0" ();
|
||||
(* The hand-written backend says the same sentences, slice-from's promise
|
||||
included — it used to borrow the slice sentence there. *)
|
||||
bounds ~x86:true ();
|
||||
|
||||
(* What the release build drops, and what it does not. Asserted on the IR
|
||||
for the first half, because an index past the end has no defined
|
||||
@ -3001,7 +3013,7 @@ let () =
|
||||
|
||||
The line: @flan_bounds_error is the bounds check and goes. The two slice
|
||||
calls stay, because what survives behind them is not a bounds check —
|
||||
check_slice's lo <= hi and slice-from-ptr's n >= 0 are the claim that a
|
||||
check_slice's lo <= hi and slice-from's n >= 0 are the claim that a
|
||||
%slice's length word holds a count. This assertion is written as
|
||||
"present" and not merely as "no longer looked at", so that re-gating
|
||||
either one on f.md.checks fails here rather than passing quietly. *)
|
||||
@ -3051,8 +3063,8 @@ let () =
|
||||
end
|
||||
in
|
||||
still_traps "reversed slice" "2" "slice [2 1) is out of bounds for length 5";
|
||||
still_traps "slice-from-ptr with a negative length" "-2"
|
||||
"slice-from-ptr was promised -2 elements behind the pointer";
|
||||
still_traps "slice-from with a negative length" "-2"
|
||||
"slice-from was promised -2 elements behind the pointer";
|
||||
let code, text = run unchecked (Some "0") in
|
||||
if text <> "0ello\n" || code <> 0 then begin
|
||||
incr failures;
|
||||
|
||||
@ -2349,7 +2349,7 @@ let () =
|
||||
infers "the address of a string's byte" "(addr (at \"hi\" 0))"
|
||||
"(Ptr const u8)";
|
||||
infers "a const pointer slices to a const slice"
|
||||
"(slice-from-ptr (addr (at (bytes-view \"hi\") 0)) 2)" "[const u8]";
|
||||
"(slice-from (addr (at (bytes-view \"hi\") 0)) 2)" "[const u8]";
|
||||
rejects_check "a store through a const pointer"
|
||||
"(defn f [p (Ptr const i32)] () (set (deref p) 1))"
|
||||
~needle:"this writes through a (Ptr const i32)";
|
||||
@ -2368,8 +2368,8 @@ let () =
|
||||
rejects_check "a const pointer is not a writable one"
|
||||
"(defn g [p (Ptr u8)] i32 0) (defn f [v [const u8]] i32 (g (addr (at v 0))))"
|
||||
~needle:"expected (Ptr u8), found (Ptr const u8)";
|
||||
rejects_check "slice-from-ptr keeps the const"
|
||||
"(defn f [v [const u8]] [u8] (slice-from-ptr (addr (at v 0)) 1))"
|
||||
rejects_check "slice-from keeps the const"
|
||||
"(defn f [v [const u8]] [u8] (slice-from (addr (at v 0)) 1))"
|
||||
~needle:"expected [u8], found [const u8]";
|
||||
rejects_check "const alone is not a type" "(defn f [p (Ptr const)] i32 0)"
|
||||
~needle:"const is not a type on its own";
|
||||
@ -2502,24 +2502,57 @@ let () =
|
||||
accepts "a generic reader takes both"
|
||||
"(defn f [a [const i32] b [i32]] i64 (+ (sum-i32 a) (sum-i32 b)))";
|
||||
|
||||
(* (slice-from-ptr p n). The one form in the language whose central claim the
|
||||
(* (slice-from p n). The one form in the language whose central claim the
|
||||
compiler cannot check — whether n is the truth about what p addresses — so
|
||||
what it does check is worth pinning: the argument really is a pointer, the
|
||||
length is not absurd on its face, and the result owns nothing. *)
|
||||
accepts "a pointer plus a length is a slice"
|
||||
"(defn f [p (Ptr i32) n i32] i32 (at (slice-from-ptr p n) 0))";
|
||||
"(defn f [p (Ptr i32) n i32] i32 (at (slice-from p n) 0))";
|
||||
accepts "zero is a length"
|
||||
"(defn f [p (Ptr i32)] i32 (length (slice-from-ptr p 0)))";
|
||||
rejects_check "slice-from-ptr of something that is not a pointer"
|
||||
"(defn f [s [i32]] i32 (length (slice-from-ptr s 3)))"
|
||||
"(defn f [p (Ptr i32)] i32 (length (slice-from p 0)))";
|
||||
rejects_check "slice-from of something that is not a pointer"
|
||||
"(defn f [s [i32]] i32 (length (slice-from s 3)))"
|
||||
~needle:"takes a (Ptr T)";
|
||||
rejects_check "slice-from-ptr with a negative literal length"
|
||||
"(defn f [p (Ptr i32)] i32 (length (slice-from-ptr p -1)))"
|
||||
rejects_check "slice-from with a negative literal length"
|
||||
"(defn f [p (Ptr i32)] i32 (length (slice-from p -1)))"
|
||||
~needle:"is negative";
|
||||
accepts "slice-from counts with any integer type"
|
||||
"(defn f [p (Ptr i32) a i64 b u64 c u8] i64 \
|
||||
(+ (length (slice-from p a)) (length (slice-from p b)) \
|
||||
(length (slice-from p c))))";
|
||||
rejects_check "slice-from with a count that is not an integer"
|
||||
"(defn f [p (Ptr i32) n f32] i32 (length (slice-from p n)))"
|
||||
~needle:"Write (slice-from p (i64 n))";
|
||||
|
||||
(* ((Ptr T) p): the type is the head, as in (i32 x). It changes what the
|
||||
pointer points at and checks nothing else, but it keeps const. *)
|
||||
accepts "a pointer cast"
|
||||
"(defn f [p (Ptr u8)] (Ptr i32) ((Ptr i32) p))";
|
||||
accepts "a pointer cast may add const"
|
||||
"(defn f [p (Ptr u8)] (Ptr const i32) ((Ptr const i32) p))";
|
||||
accepts "a const pointer casts to a const pointer"
|
||||
"(defn f [p (Ptr const u8)] [const i32] (slice-from ((Ptr const i32) p) 2))";
|
||||
rejects_check "a pointer cast may not drop const"
|
||||
"(defn f [p (Ptr const u8)] (Ptr i32) ((Ptr i32) p))"
|
||||
~needle:"Write ((Ptr const i32) p)";
|
||||
rejects_check "a pointer cast of a slice"
|
||||
"(defn f [s [u8]] (Ptr i32) ((Ptr i32) s))"
|
||||
~needle:"write ((Ptr i32) (addr (at s 0)))";
|
||||
rejects_check "a pointer cast of an integer"
|
||||
"(defn f [x i64] (Ptr i32) ((Ptr i32) x))"
|
||||
~needle:"no conversion between an integer and a pointer";
|
||||
rejects_check "slice-from-ptr names slice-from"
|
||||
"(defn f [p (Ptr i32) n i32] i32 (length (slice-from-ptr p n)))"
|
||||
~needle:"Write (slice-from p n)";
|
||||
accepts "a pointer cast checks the outer const only"
|
||||
"(defn f [q (Ptr (Ptr const u8))] (Ptr (Ptr u8)) ((Ptr (Ptr u8)) q))";
|
||||
rejects_check "a pointer cast of a dyn"
|
||||
"(defn f [x dyn] (Ptr i32) ((Ptr i32) x))"
|
||||
~needle:"A dyn value never holds a pointer";
|
||||
(* The storage stays C's, and a view made in place is refused at free
|
||||
without running anything. *)
|
||||
rejects_check "free of a slice made from a pointer"
|
||||
"(defn f [p (Ptr i32)] () (free (slice-from-ptr p 3)))"
|
||||
"(defn f [p (Ptr i32)] () (free (slice-from p 3)))"
|
||||
~needle:"is a view of storage something else owns";
|
||||
rejects_check "free of a slice written in place"
|
||||
"(defn f [v (Vec i32)] () (free (slice v)))"
|
||||
|
||||
51
vendor/raylib/raylib.flan
vendored
51
vendor/raylib/raylib.flan
vendored
@ -619,41 +619,10 @@
|
||||
|
||||
(declare-c unload-texture [texture Texture2D] "UnloadTexture")
|
||||
|
||||
;; Refilling a texture's pixels from a Color buffer.
|
||||
;;
|
||||
;; UpdateTexture takes a `const void *` — raylib does not care what the buffer
|
||||
;; is an array *of*, only that it is the right number of bytes in the format
|
||||
;; the texture was made with. The importer has to render that as *something*
|
||||
;; and renders it `(Ptr u8)`, which is the generated `update-texture` above.
|
||||
;;
|
||||
;; That face cannot be the only one, because the commonest source of pixels is
|
||||
;; LoadImageColors and it answers a `(Ptr Color)`. It also cannot be replaced
|
||||
;; by a `(Ptr Color)` one: `(.data im-copy)` is already a `(Ptr u8)` over the
|
||||
;; same bytes and is the shorter route, and `(Ptr u8)` → `(Ptr Color)` is not
|
||||
;; expressible while `(Ptr Color)` → `(Ptr u8)` is. So the byte face is the
|
||||
;; declaration and the typed face is this.
|
||||
;;
|
||||
;; A second `declare-c` was the obvious shape and lib/shim.ml refuses it, for a
|
||||
;; reason that is right: a shim emits one C prototype per declaration, and two
|
||||
;; prototypes for one symbol that disagree about a parameter type is a C file
|
||||
;; that does not compile. Its message says what to write instead — "another
|
||||
;; Flan name for it is a defn" — and this is that defn. docs/PORTING.md §A.2
|
||||
;; expected the header check to be the only thing in the way; it was not.
|
||||
;;
|
||||
;; `(addr (.r pixels))` is the whole of the conversion. `.` auto-derefs one
|
||||
;; level and `r` is Color's first field, so this is the address the pointer
|
||||
;; already held, said in the only way the language has of saying it. It used
|
||||
;; to be written at the call site as
|
||||
;; `(addr (.r (at (slice-from-ptr pixels n) 0)))`, which is the same address
|
||||
;; with a slice built and indexed on the way past. Once, here, with a name on
|
||||
;; it is better than once per caller.
|
||||
;;
|
||||
;; Neither call checks that the buffer is big enough, because neither can: the
|
||||
;; length raylib wants is width * height * bytes-per-pixel of the *texture*,
|
||||
;; and a pointer has no length. That is the deal every raylib pointer
|
||||
;; parameter offers.
|
||||
;; UpdateTexture over a Color buffer, such as the one LoadImageColors answers.
|
||||
;; The generated update-texture takes the `const void *` as (Ptr const u8).
|
||||
(defn update-texture-colors [texture Texture2D pixels (Ptr Color)] ()
|
||||
(update-texture texture (addr (.r pixels))))
|
||||
(update-texture texture ((Ptr u8) pixels)))
|
||||
|
||||
;; How a texture is sampled when it is drawn at anything other than its own
|
||||
;; size. The header says `int` on SetTextureFilter and means one of these six.
|
||||
@ -1538,12 +1507,12 @@
|
||||
;; hold exactly `glyph-count` entries — raylib allocates them that way in
|
||||
;; LoadFontData and every one of its own loops uses that bound — and
|
||||
;; `glyph-count` is a sibling *field*, which is why naming a count argument in
|
||||
;; `bindings` could never have covered these two. `slice-from-ptr` can.
|
||||
;; `bindings` could never have covered these two. `slice-from` can.
|
||||
;;
|
||||
;; **These two wrappers are where the promise is made, and they are the reason
|
||||
;; the promise is safe to make**: a caller of `font-recs` is trusting raylib's
|
||||
;; own invariant rather than remembering a number, and there is one place to
|
||||
;; fix if raylib ever changes it. Prefer them to writing `slice-from-ptr` at a
|
||||
;; fix if raylib ever changes it. Prefer them to writing `slice-from` at a
|
||||
;; call site.
|
||||
;;
|
||||
;; The one way to break them is to call either on a Font that was unloaded, or
|
||||
@ -1552,10 +1521,10 @@
|
||||
;; ordinary use-after-free a (Ptr T) already had; the slice does not own the
|
||||
;; storage and freeing through one is not expressible.
|
||||
(defn font-recs [font Font] [Rectangle]
|
||||
(slice-from-ptr (.recs font) (.glyph-count font)))
|
||||
(slice-from (.recs font) (.glyph-count font)))
|
||||
|
||||
(defn font-glyphs [font Font] [GlyphInfo]
|
||||
(slice-from-ptr (.glyphs font) (.glyph-count font)))
|
||||
(slice-from (.glyphs font) (.glyph-count font)))
|
||||
|
||||
;; The index into `recs` and `glyphs`, by linear search over glyph-count. Also
|
||||
;; pure CPU, and it is what pins glyph-count as the loop bound.
|
||||
@ -1628,7 +1597,7 @@
|
||||
|
||||
;; Every array a mesh owns is a pointer and a count held elsewhere in the
|
||||
;; struct: vertex-count vertices, triangle-count triangles. Reading one is
|
||||
;; (slice-from-ptr (.vertices mesh) (* 3 (.vertex-count mesh))).
|
||||
;; (slice-from (.vertices mesh) (* 3 (.vertex-count mesh))).
|
||||
(defstruct Mesh
|
||||
[vertex-count i32 triangle-count i32
|
||||
vertices (Ptr f32) texcoords (Ptr f32) texcoords-2 (Ptr f32)
|
||||
@ -1672,7 +1641,7 @@
|
||||
;; stops there and nothing past it is read. `char` is i8 in the header, and
|
||||
;; each byte is converted as it is copied.
|
||||
(defn- c-string-copy [p (Ptr i8)] string
|
||||
(let [s (slice-from-ptr p 2147483647)
|
||||
(let [s (slice-from p 2147483647)
|
||||
out (vec-new u8)
|
||||
i 0]
|
||||
(while (!= (at s i) 0)
|
||||
@ -1683,7 +1652,7 @@
|
||||
(defn- file-path-list-copy [files FilePathList] (Vec string)
|
||||
(let [out (vec-new string)
|
||||
n (i32 (.count files))
|
||||
paths (slice-from-ptr (.paths files) n)]
|
||||
paths (slice-from (.paths files) n)]
|
||||
(dotimes [i n]
|
||||
(push out (c-string-copy (at paths i))))
|
||||
out))
|
||||
|
||||
@ -525,7 +525,7 @@ notation reads as exactly one data item.</p>
|
||||
<tr><td><code>(Map K V)</code></td><td>open addressing, owning, copies the same way. The only map spelling: braces in type position are not a type</td><td>data + len + log2cap + its allocator</td></tr>
|
||||
<tr><td><code>(Pool T)</code></td><td>generational slab storage, owning, copies the same way</td><td>items + slots + its allocator</td></tr>
|
||||
<tr><td><code>(Handle T)</code></td><td>a reference into a pool that reports a dead referent</td><td>index and generation packed into an <code>i64</code></td></tr>
|
||||
<tr><td><code>(Ptr T)</code></td><td>raw pointer</td><td>a pointer</td></tr>
|
||||
<tr><td><code>(Ptr T)</code></td><td>raw pointer. <code>((Ptr U) p)</code> reads it as a pointer to <code>U</code>, and may add <code>const</code> but not drop it (the outer pointer only: what it points at is taken on trust); <code>(slice-from p n)</code> makes a <code>[T]</code> of the <code>n</code> elements behind it. Neither checks the memory</td><td>a pointer</td></tr>
|
||||
<tr><td><code>(Ptr const T)</code></td><td>a pointer nothing is written through: the address of read-only storage, and what a C <code>const T *</code> takes. A <code>(Ptr T)</code> converts to one, never the reverse</td><td>a pointer</td></tr>
|
||||
<tr><td><code>(Option T)</code></td><td><code>Some</code> / <code>None</code></td><td>tag byte + T</td></tr>
|
||||
<tr><td><code>(Fn [T ...] R)</code></td><td>a function value, which may have captured</td><td>a code address and an environment pointer</td></tr>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user