No top-level value in the compiler goes unreferenced
This commit is contained in:
parent
3636f31cba
commit
e511174d3c
@ -5478,7 +5478,7 @@ error that could actually be clicked.
|
|||||||
|
|
||||||
The source cache in `loc.ml` is process-lifetime, which is right for `flan build` — a fresh process per run. The
|
The source cache in `loc.ml` is process-lifetime, which is right for `flan build` — a fresh process per run. The
|
||||||
daemon is long-lived and never calls `report`; the interactive path draws no squiggle, it takes a location and a
|
daemon is long-lived and never calls `report`; the interactive path draws no squiggle, it takes a location and a
|
||||||
message. `Loc.forget_sources` exists for the day that changes.
|
message. A daemon that did draw one would have to reset the cache when a file changes.
|
||||||
|
|
||||||
### Collecting, and where it stops
|
### Collecting, and where it stops
|
||||||
|
|
||||||
@ -5504,7 +5504,7 @@ file-shaped pile of nonsense. First error, stop. That is a decision, not an omis
|
|||||||
Changing the error type without touching `dev.ml` and `session.ml` needed a compatible way to get one location and
|
Changing the error type without touching `dev.ml` and `session.ml` needed a compatible way to get one location and
|
||||||
one message out. The answer is that **the single-diagnostic exception is still the single-diagnostic exception**.
|
one message out. The answer is that **the single-diagnostic exception is still the single-diagnostic exception**.
|
||||||
`Session.eval` and the daemon evaluate one form and have one failure to report; they keep catching `Loc.Error` and
|
`Session.eval` and the daemon evaluate one form and have one failure to report; they keep catching `Loc.Error` and
|
||||||
take the pair out of it with `Loc.summary`. Only a driver that compiles a whole file raises `Loc.Errors`.
|
read `dloc` and `dmsg` out of it. Only a driver that compiles a whole file raises `Loc.Errors`.
|
||||||
|
|
||||||
That guarantee is **structural and not conventional**. `Parse.program` / `Check.program` stop at the first refusal;
|
That guarantee is **structural and not conventional**. `Parse.program` / `Check.program` stop at the first refusal;
|
||||||
`Parse.program_all` / `Check.program_all` collect. Two names rather than one function with a `~keep_going` label,
|
`Parse.program_all` / `Check.program_all` collect. Two names rather than one function with a `~keep_going` label,
|
||||||
|
|||||||
13
lib/check.ml
13
lib/check.ml
@ -747,10 +747,6 @@ and captured_set ctx loc name =
|
|||||||
"Return the new value, or keep it in a local of this fn")
|
"Return the new value, or keep it in a local of this fn")
|
||||||
| None -> ()
|
| None -> ()
|
||||||
|
|
||||||
(* A binding this body captured, as opposed to one it declared. Used where the
|
|
||||||
difference matters and nowhere else. *)
|
|
||||||
let is_captured ctx name = List.mem_assoc name ctx.caught
|
|
||||||
|
|
||||||
let scoped ctx f =
|
let scoped ctx f =
|
||||||
let saved = ctx.scope in
|
let saved = ctx.scope in
|
||||||
let r = f () in
|
let r = f () in
|
||||||
@ -2338,11 +2334,6 @@ let widen loc (want : Types.t) (e : Tast.expr) =
|
|||||||
if Types.equal want e.Tast.ty then e
|
if Types.equal want e.Tast.ty then e
|
||||||
else mk loc want (Tast.Prim (Tast.Cast want, [ e ]))
|
else mk loc want (Tast.Prim (Tast.Cast want, [ e ]))
|
||||||
|
|
||||||
let unboxable t =
|
|
||||||
match t with
|
|
||||||
| Types.Int Types.I64 | Types.Float Types.F64 | Types.Bool -> true
|
|
||||||
| _ -> false
|
|
||||||
|
|
||||||
(* The sentence a refusal at this boundary gives. It names the type and says
|
(* The sentence a refusal at this boundary gives. It names the type and says
|
||||||
which direction failed, because "expected dyn, found (Vec i64)" would read
|
which direction failed, because "expected dyn, found (Vec i64)" would read
|
||||||
as a type error the programmer could fix by writing something else, and
|
as a type error the programmer could fix by writing something else, and
|
||||||
@ -2353,7 +2344,7 @@ let no_dyn_yet loc ~into t extra =
|
|||||||
(Types.to_string t) (if into then "dyn" else "a written type") extra
|
(Types.to_string 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
|
(* M2 item 3: a typed container crossing into dyn as a view. The element set
|
||||||
is exactly [unboxable] above — i64, f64, bool — and that is not a smaller
|
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
|
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
|
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
|
pointer into the collector's heap, while a typed container's storage is
|
||||||
@ -3416,7 +3407,7 @@ let rec check ctx ?want (e : Ast.expr) : Tast.expr =
|
|||||||
runtime owns the storage the way (vec-new dyn) does, keys and values are
|
runtime owns the storage the way (vec-new dyn) does, keys and values are
|
||||||
both dyn words, and a typed want other than dyn refuses through [expect]
|
both dyn words, and a typed want other than dyn refuses through [expect]
|
||||||
like any other dyn value would. The literal lowers to a fresh slot — a
|
like any other dyn value would. The literal lowers to a fresh slot — a
|
||||||
rooted one, because a slot of type dyn is what [dyn_roots] counts — so
|
rooted one, because a slot of type dyn is what [Emit.root_plan] counts — so
|
||||||
the map stays reachable across the allocations its own entries make. *)
|
the map stays reachable across the allocations its own entries make. *)
|
||||||
| Ast.MapLit (tag, kvs) ->
|
| Ast.MapLit (tag, kvs) ->
|
||||||
let m = fresh_slot ctx Types.Dyn in
|
let m = fresh_slot ctx Types.Dyn in
|
||||||
|
|||||||
19
lib/emit.ml
19
lib/emit.ml
@ -35,8 +35,6 @@
|
|||||||
through a [(Ptr Cursor)] becomes a [getelementptr] on the pointer, not on
|
through a [(Ptr Cursor)] becomes a [getelementptr] on the pointer, not on
|
||||||
a copy of the struct. *)
|
a copy of the struct. *)
|
||||||
|
|
||||||
let fail = Loc.fail
|
|
||||||
|
|
||||||
(* The assertions below this line are not diagnostics. Every one of them says
|
(* The assertions below this line are not diagnostics. Every one of them says
|
||||||
the checker admitted something it refuses — a type with no layout, a case
|
the checker admitted something it refuses — a type with no layout, a case
|
||||||
that is not a case of its data type, arithmetic on a struct — so no program
|
that is not a case of its data type, arithmetic on a struct — so no program
|
||||||
@ -112,11 +110,6 @@ let xfer_param = "%xfer"
|
|||||||
to have had all along. *)
|
to have had all along. *)
|
||||||
let env_param = "%env"
|
let env_param = "%env"
|
||||||
|
|
||||||
(* What a call through a [(Fn ...)] value passes when it has no environment —
|
|
||||||
a value made out of a name, or one widened from a [CFn]. Spelled once so
|
|
||||||
the sites cannot drift. *)
|
|
||||||
let no_env = "ptr null"
|
|
||||||
|
|
||||||
(* The condition's own name, for the message an unhandled [error] prints. The
|
(* The condition's own name, for the message an unhandled [error] prints. The
|
||||||
checker has already refused anything that is not a struct. *)
|
checker has already refused anything that is not a struct. *)
|
||||||
let struct_name_of (t : Types.t) =
|
let struct_name_of (t : Types.t) =
|
||||||
@ -936,7 +929,7 @@ type f = {
|
|||||||
It is a count and not a saved depth because the ABI offers
|
It is a count and not a saved depth because the ABI offers
|
||||||
[flan_dyn_root_pop(n)] and no way to read the stack's height; it can be a
|
[flan_dyn_root_pop(n)] and no way to read the stack's height; it can be a
|
||||||
count, rather than needing one, because the number is a static property of
|
count, rather than needing one, because the number is a static property of
|
||||||
the function that [dyn_roots] works out before a line of the body is
|
the function that [root_plan] works out before a line of the body is
|
||||||
emitted. That matters: [ret] runs *during* emission, and a count
|
emitted. That matters: [ret] runs *during* emission, and a count
|
||||||
accumulated as roots were discovered would be short at every early
|
accumulated as roots were discovered would be short at every early
|
||||||
return. *)
|
return. *)
|
||||||
@ -1370,16 +1363,12 @@ let root_plan m (fn : Tast.fn) : rootplan =
|
|||||||
!agg;
|
!agg;
|
||||||
rpins = !pins }
|
rpins = !pins }
|
||||||
|
|
||||||
let dyn_roots m (fn : Tast.fn) =
|
|
||||||
let p = root_plan m fn in
|
|
||||||
List.length p.rslots + p.rdyn + List.length p.ragg
|
|
||||||
|
|
||||||
(* The next pre-made root slot for a dyn temporary. They are all minted, zeroed
|
(* The next pre-made root slot for a dyn temporary. They are all minted, zeroed
|
||||||
and pushed in the entry block before a line of the body is emitted, and this
|
and pushed in the entry block before a line of the body is emitted, and this
|
||||||
only hands them out — which is what makes the pushes and the pops balance by
|
only hands them out — which is what makes the pushes and the pops balance by
|
||||||
construction rather than by the body being walked the same way twice.
|
construction rather than by the body being walked the same way twice.
|
||||||
|
|
||||||
[dyn_roots] counts the same nodes the emission visits, so the supply runs
|
[root_plan] counts the same nodes the emission visits, so the supply runs
|
||||||
out only if those two disagree. If it ever does, the fallback is an ordinary
|
out only if those two disagree. If it ever does, the fallback is an ordinary
|
||||||
unrooted slot: one temporary the collector cannot see is a bug to find,
|
unrooted slot: one temporary the collector cannot see is a bug to find,
|
||||||
where a root stack that pops more than it pushed is memory corruption. *)
|
where a root stack that pops more than it pushed is memory corruption. *)
|
||||||
@ -3357,7 +3346,7 @@ and prim f (e : Tast.expr) (p : Tast.prim) (args : Tast.expr list) =
|
|||||||
(* A dyn word is spilled into a rooted slot the instant it exists. It is
|
(* A dyn word is spilled into a rooted slot the instant it exists. It is
|
||||||
an SSA value otherwise, and an SSA value is invisible to a collector
|
an SSA value otherwise, and an SSA value is invisible to a collector
|
||||||
that finds its roots by address — the next allocation could be the one
|
that finds its roots by address — the next allocation could be the one
|
||||||
that frees what this is holding. [dyn_roots] counted this call, so the
|
that frees what this is holding. [root_plan] counted this call, so the
|
||||||
slot below is one the entry block has already pushed.
|
slot below is one the entry block has already pushed.
|
||||||
|
|
||||||
The value carries on being used as a register: the store is what the
|
The value carries on being used as a register: the store is what the
|
||||||
@ -3574,7 +3563,7 @@ let emit_fn m ?(hidden = false) ?(pnames = []) (fn : Tast.fn) =
|
|||||||
a debugging convenience and a release build does without it, while a
|
a debugging convenience and a release build does without it, while a
|
||||||
collector that cannot find its roots is a collector that frees live
|
collector that cannot find its roots is a collector that frees live
|
||||||
values. Every build pays this, and only a function that has a dyn in it
|
values. Every build pays this, and only a function that has a dyn in it
|
||||||
pays anything — [dyn_roots] is zero otherwise and not a line is emitted,
|
pays anything — [root_plan] is empty otherwise and not a line is emitted,
|
||||||
which is what makes an annotated program's IR identical with and without
|
which is what makes an annotated program's IR identical with and without
|
||||||
--no-gc.
|
--no-gc.
|
||||||
|
|
||||||
|
|||||||
@ -143,10 +143,6 @@ exception Error of diag
|
|||||||
and never raised by a path that checks a single form. *)
|
and never raised by a path that checks a single form. *)
|
||||||
exception Errors of diag list
|
exception Errors of diag list
|
||||||
|
|
||||||
(** The one location and one message a caller with a single line to print gets
|
|
||||||
out of a diagnostic. Notes are dropped here on purpose. *)
|
|
||||||
let summary (d : diag) = (d.dloc, d.dmsg)
|
|
||||||
|
|
||||||
let before (a : t) (b : t) =
|
let before (a : t) (b : t) =
|
||||||
if a.line <> b.line then compare a.line b.line else compare a.col b.col
|
if a.line <> b.line then compare a.line b.line else compare a.col b.col
|
||||||
|
|
||||||
@ -210,8 +206,6 @@ let caught s f =
|
|||||||
| x -> Some x
|
| x -> Some x
|
||||||
| exception Error d -> s.found <- d :: s.found; None
|
| exception Error d -> s.found <- d :: s.found; None
|
||||||
|
|
||||||
let any s = s.found <> []
|
|
||||||
|
|
||||||
(** Raise everything found, in the order it was found, or return if the pass
|
(** Raise everything found, in the order it was found, or return if the pass
|
||||||
was clean. *)
|
was clean. *)
|
||||||
let finish s =
|
let finish s =
|
||||||
@ -255,8 +249,6 @@ let lines_of file =
|
|||||||
Hashtbl.replace source_cache file v;
|
Hashtbl.replace source_cache file v;
|
||||||
v
|
v
|
||||||
|
|
||||||
let forget_sources () = Hashtbl.reset source_cache
|
|
||||||
|
|
||||||
let source_line (t : t) =
|
let source_line (t : t) =
|
||||||
if t.line <= 0 then None
|
if t.line <= 0 then None
|
||||||
else
|
else
|
||||||
|
|||||||
38
lib/x86.ml
38
lib/x86.ml
@ -355,7 +355,6 @@ let and_imm b ~dst n = grp1_imm b ~ext:4 ~dst n
|
|||||||
let sub_imm b ~dst n = grp1_imm b ~ext:5 ~dst n
|
let sub_imm b ~dst n = grp1_imm b ~ext:5 ~dst n
|
||||||
let cmp_imm b ~dst n = grp1_imm b ~ext:7 ~dst n
|
let cmp_imm b ~dst n = grp1_imm b ~ext:7 ~dst n
|
||||||
|
|
||||||
let neg_r b ~dst = rex b ~w:true ~r:0 ~x:0 ~m:dst; u8 b 0xf7; modrm_r b ~r:3 ~m:dst
|
|
||||||
let not_r b ~dst = rex b ~w:true ~r:0 ~x:0 ~m:dst; u8 b 0xf7; modrm_r b ~r:2 ~m:dst
|
let not_r b ~dst = rex b ~w:true ~r:0 ~x:0 ~m:dst; u8 b 0xf7; modrm_r b ~r:2 ~m:dst
|
||||||
let test_rr b ~a ~c = rex b ~w:true ~r:c ~x:0 ~m:a; u8 b 0x85; modrm_r b ~r:c ~m:a
|
let test_rr b ~a ~c = rex b ~w:true ~r:c ~x:0 ~m:a; u8 b 0x85; modrm_r b ~r:c ~m:a
|
||||||
|
|
||||||
@ -696,7 +695,7 @@ type fnctx = {
|
|||||||
|
|
||||||
A count rather than a running tally for [emit.ml]'s reason: the epilogue
|
A count rather than a running tally for [emit.ml]'s reason: the epilogue
|
||||||
is emitted after the body, but the pushes are decided before it, by
|
is emitted after the body, but the pushes are decided before it, by
|
||||||
[Emit.dyn_roots], which is deliberately the *same* function both backends
|
[Emit.root_plan], which is deliberately the *same* function both backends
|
||||||
call. The pushes and the pops balance because one counter decides both
|
call. The pushes and the pops balance because one counter decides both
|
||||||
ends, and the two backends root the same nodes because there is one
|
ends, and the two backends root the same nodes because there is one
|
||||||
counter and not two. *)
|
counter and not two. *)
|
||||||
@ -943,7 +942,7 @@ let scoped f g =
|
|||||||
|
|
||||||
(* ── Moving values ───────────────────────────────────────────────────── *)
|
(* ── Moving values ───────────────────────────────────────────────────── *)
|
||||||
|
|
||||||
(* Scalar in [reg] <- [rbp+off], and back. A bool is a byte; everything else
|
(* Scalar in [reg] <- [rbp+off]. A bool is a byte; everything else
|
||||||
is its own width, widened on load. *)
|
is its own width, widened on load. *)
|
||||||
let load_scalar f ~reg ~off (t : Types.t) =
|
let load_scalar f ~reg ~off (t : Types.t) =
|
||||||
if is_float t then fload f.b ~dst:reg ~mm:(Frame off) ~f64:(f64_of t)
|
if is_float t then fload f.b ~dst:reg ~mm:(Frame off) ~f64:(f64_of t)
|
||||||
@ -951,26 +950,6 @@ let load_scalar f ~reg ~off (t : Types.t) =
|
|||||||
let size = match t with Types.Bool -> 1 | _ -> max 1 (sizeof f.md t) in
|
let size = match t with Types.Bool -> 1 | _ -> max 1 (sizeof f.md t) in
|
||||||
load_int f.b ~dst:reg ~mm:(Frame off) ~size ~signed:(signed_of t)
|
load_int f.b ~dst:reg ~mm:(Frame off) ~size ~signed:(signed_of t)
|
||||||
|
|
||||||
let store_scalar f ~reg ~off (t : Types.t) =
|
|
||||||
if is_float t then fstore f.b ~src:reg ~mm:(Frame off) ~f64:(f64_of t)
|
|
||||||
else
|
|
||||||
let size = match t with Types.Bool -> 1 | _ -> max 1 (sizeof f.md t) in
|
|
||||||
store_int f.b ~src:reg ~mm:(Frame off) ~size
|
|
||||||
|
|
||||||
(* Through a pointer rather than a frame offset: the same two, with the
|
|
||||||
address already in a register. *)
|
|
||||||
let load_scalar_at f ~reg ~base ~disp (t : Types.t) =
|
|
||||||
if is_float t then fload f.b ~dst:reg ~mm:(Reg (base, disp)) ~f64:(f64_of t)
|
|
||||||
else
|
|
||||||
let size = match t with Types.Bool -> 1 | _ -> max 1 (sizeof f.md t) in
|
|
||||||
load_int f.b ~dst:reg ~mm:(Reg (base, disp)) ~size ~signed:(signed_of t)
|
|
||||||
|
|
||||||
let store_scalar_at f ~reg ~base ~disp (t : Types.t) =
|
|
||||||
if is_float t then fstore f.b ~src:reg ~mm:(Reg (base, disp)) ~f64:(f64_of t)
|
|
||||||
else
|
|
||||||
let size = match t with Types.Bool -> 1 | _ -> max 1 (sizeof f.md t) in
|
|
||||||
store_int f.b ~src:reg ~mm:(Reg (base, disp)) ~size
|
|
||||||
|
|
||||||
(* n bytes from the address in rsi to the address in rdi. *)
|
(* n bytes from the address in rsi to the address in rdi. *)
|
||||||
let blockcopy f n =
|
let blockcopy f n =
|
||||||
if n > 0 then begin
|
if n > 0 then begin
|
||||||
@ -981,13 +960,6 @@ let blockcopy f n =
|
|||||||
rep_movsb f.b
|
rep_movsb f.b
|
||||||
end
|
end
|
||||||
|
|
||||||
let copy_frames f ~dst ~src n =
|
|
||||||
if n > 0 then begin
|
|
||||||
lea f.b ~dst:rdi ~mm:(Frame dst);
|
|
||||||
lea f.b ~dst:rsi ~mm:(Frame src);
|
|
||||||
blockcopy f n
|
|
||||||
end
|
|
||||||
|
|
||||||
let zero_frame f ~dst n =
|
let zero_frame f ~dst n =
|
||||||
if n > 0 then begin
|
if n > 0 then begin
|
||||||
note f (Printf.sprintf "rep stosb: %d bytes of zero, which is what this backend \
|
note f (Printf.sprintf "rep stosb: %d bytes of zero, which is what this backend \
|
||||||
@ -1448,7 +1420,7 @@ let with_pad f tag g =
|
|||||||
hands them out, which is what makes the pushes and the pops balance by
|
hands them out, which is what makes the pushes and the pops balance by
|
||||||
construction rather than by the body being walked the same way twice.
|
construction rather than by the body being walked the same way twice.
|
||||||
|
|
||||||
[Emit.dyn_roots] counts the same nodes this emission visits, so the supply
|
[Emit.root_plan] counts the same nodes this emission visits, so the supply
|
||||||
runs out only if those two disagree — and since both backends call that one
|
runs out only if those two disagree — and since both backends call that one
|
||||||
function, disagreeing would be one of them visiting a node the other does
|
function, disagreeing would be one of them visiting a node the other does
|
||||||
not. The fallback is an ordinary unrooted temporary, for [emit.ml]'s
|
not. The fallback is an ordinary unrooted temporary, for [emit.ml]'s
|
||||||
@ -3006,7 +2978,7 @@ and call_rt f ~sym ~args ~rty dst =
|
|||||||
the collector finds its roots by address. [dst] is not enough: it is
|
the collector finds its roots by address. [dst] is not enough: it is
|
||||||
often a temporary inside a [scoped] that the bump allocator is about to
|
often a temporary inside a [scoped] that the bump allocator is about to
|
||||||
hand out again, and it is never a slot anything was pushed for.
|
hand out again, and it is never a slot anything was pushed for.
|
||||||
[Emit.dyn_roots] counted this call, so the slot below is one the entry
|
[Emit.root_plan] counted this call, so the slot below is one the entry
|
||||||
block has already zeroed and pushed.
|
block has already zeroed and pushed.
|
||||||
|
|
||||||
Here rather than in [call_native], which is [call_c]'s as well: a dyn
|
Here rather than in [call_native], which is [call_c]'s as well: a dyn
|
||||||
@ -3744,7 +3716,7 @@ let emit_fn (md : Emit.m) ~externs ~fns ?(ext = fun _ -> false)
|
|||||||
everything below it: the shadow stack is a debugging convenience a release
|
everything below it: the shadow stack is a debugging convenience a release
|
||||||
build does without, while a collector that cannot find its roots is a
|
build does without, while a collector that cannot find its roots is a
|
||||||
collector that frees live values. Only a function with a dyn in it pays
|
collector that frees live values. Only a function with a dyn in it pays
|
||||||
anything, because [Emit.dyn_roots] is zero otherwise and not an
|
anything, because [Emit.root_plan] is empty otherwise and not an
|
||||||
instruction is emitted — which is what keeps every dyn-free program in the
|
instruction is emitted — which is what keeps every dyn-free program in the
|
||||||
survey byte for byte what it was before this lane.
|
survey byte for byte what it was before this lane.
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user