uninit on a union, and .field on one, are both refused
Everywhere else uninit is an opt-out from ZII and the bytes are whatever they were: a garbage f64 is a garbage number. A union is the one type where that is qualitatively worse. Its tag steers control flow, a tag no case names falls past every comparison in a match, and the block after those comparisons is unreachable -- which LLVM is entitled to assume cannot happen. So the one place where garbage becomes "the optimiser may do anything" is refused by name, with the zeroed form, which is a real case, named beside it. (.x u) on a union said "Shape is not a struct, so it has no fields", which is true and unhelpful. A union's fields belong to a case and which case is being held is what the tag says, so they are reached by match, whose arms bind the fields of the case they matched. The message says that.
This commit is contained in:
parent
ee15745718
commit
6c026cb32e
33
lib/check.ml
33
lib/check.ml
@ -1735,6 +1735,17 @@ and struct_target ctx (target : Ast.expr) : Tast.expr * string =
|
||||
| Types.Named n when Hashtbl.mem ctx.env.structs n -> t, n
|
||||
| Types.Ptr (Types.Named n) when Hashtbl.mem ctx.env.structs n ->
|
||||
mk t.Tast.loc (Types.Named n) (Tast.Deref t), n
|
||||
(* A union's fields belong to one case, and which case it is holding is only
|
||||
known after the tag has been read. [.field] would have to be a read that
|
||||
might be reading something else, so it is not one: [match] is how a union
|
||||
is opened, and it binds the fields it has proved are there. *)
|
||||
| (Types.Named n | Types.Ptr (Types.Named n))
|
||||
when Hashtbl.mem ctx.env.unions n ->
|
||||
fail target.Ast.loc
|
||||
"%s is a union, and a union's fields belong to a case — which one it is \
|
||||
holding is what the tag says, so they are reached by (match ...), \
|
||||
whose arms bind the fields of the case they matched"
|
||||
n
|
||||
| other ->
|
||||
fail target.Ast.loc "%s is not a struct, so it has no fields"
|
||||
(Types.to_string other)
|
||||
@ -3665,7 +3676,27 @@ let check_global env (d : Ast.decl) : Tast.global option =
|
||||
let ginit =
|
||||
match init with
|
||||
| Ast.Zeroed -> { Tast.e = Tast.Zero ty; ty; loc = d.Ast.dloc }
|
||||
| Ast.Uninit -> { Tast.e = Tast.Uninit ty; ty; loc = d.Ast.dloc }
|
||||
| Ast.Uninit ->
|
||||
(* Everywhere else [uninit] is an opt-out from ZII and the bytes are
|
||||
whatever they were: a garbage f64 is a garbage number. A union is
|
||||
the one type where that is qualitatively worse — the tag steers
|
||||
control flow, a tag no case names falls past every comparison in a
|
||||
[match], and the block after them is [unreachable], which LLVM is
|
||||
entitled to assume cannot happen. So the one place where garbage
|
||||
becomes "the optimiser may do anything" is refused by name, and the
|
||||
zeroed form, which is the first declared case, is named beside it. *)
|
||||
(match ty with
|
||||
| Types.Named un when Hashtbl.mem env.unions un ->
|
||||
fail d.Ast.dloc
|
||||
"%s is a union, and uninit on one is refused: its tag steers \
|
||||
every match, and a tag no case names has no arm to reach. Drop \
|
||||
the uninit — a zeroed %s is %s, which is a real case"
|
||||
(Types.to_string ty) un
|
||||
(match Hashtbl.find_opt env.unions un with
|
||||
| Some { Tast.cases = c :: _; _ } -> un ^ "." ^ c.Tast.vname
|
||||
| _ -> "its first case")
|
||||
| _ -> ());
|
||||
{ Tast.e = Tast.Uninit ty; ty; loc = d.Ast.dloc }
|
||||
| Ast.Init v -> check (ctx ()) ~want:ty v
|
||||
in
|
||||
Some { Tast.gname = n; gty = ty; ginit; gconst = false; gfolded = false }
|
||||
|
||||
@ -1740,6 +1740,18 @@ ERR@7 unexpected token: not the kind the caller was reading
|
||||
incr failures;
|
||||
Printf.printf "FAIL %s\n said: %S\n" name m
|
||||
end);
|
||||
(* uninit is an opt-out from ZII everywhere else and the bytes are just
|
||||
bytes. On a union they steer control flow: a tag no case names falls
|
||||
past every comparison in a match into the block LLVM is entitled to
|
||||
assume cannot be reached. *)
|
||||
refuses_src "uninit on a union global"
|
||||
"(defunion U [A B])\n(defvar g U uninit)\n(defn main [] i32 0)"
|
||||
"its tag steers every match";
|
||||
(* A union's fields belong to a case, so .field is not a read anyone can
|
||||
do without having read the tag first. match is how one is opened. *)
|
||||
refuses_src "reading a field of a union directly"
|
||||
"(defunion U [(A [x i32])])\n(defn f [u U] i32 (.x u))"
|
||||
"reached by (match ...)";
|
||||
(* And a zeroed one is fine, which is the other half of the same rule: it
|
||||
is the first declared case, all bytes zero, and needs no encoder. *)
|
||||
(let name = "a zeroed union global" in
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user