Form's image format, measured instead of assumed
24 bytes, align 8, payload at offset 8. Those three numbers are the whole
agreement between the compiler and a dlopened macro -- the compiler writes a
Form into raw memory a field at a time and reads one back the same way -- and
they were written down in a handoff note and asserted nowhere. Nothing at run
time would notice a disagreement of one byte; the macro would simply return a
different form than it built.
So they go through the oracle the DWARF cases already use: ptrtoint of a
getelementptr through null, constant-folded by llc and read back out of the
.quad. The offsets and the size that oracle already answered. Alignment it did
not, and reading [2 x i64] out of the emitted type and concluding 8 would be
asserting the layout against itself -- the circularity BUILT.md rejected when
it turned down a _Static_assert. It is asked instead: the offset of field 1 in
{ i8, Form } is alignof(Form), because a member sits at the first offset its
own alignment allows.
Checked by breaking it both ways before restoring: 25 for the size and 16 for
the alignment each fail, and name which number moved.
The llc plumbing is now one run_oracle over a module of folded constants, with
llvm_members and llvm_align as the two questions asked through it.
This commit is contained in:
parent
1ee54d6b56
commit
3ae2c089ec
@ -1966,28 +1966,24 @@ ERR@7 unexpected token: not the kind the caller was reading
|
|||||||
(* LLVM's own answer, for the same struct type text the DWARF describes.
|
(* LLVM's own answer, for the same struct type text the DWARF describes.
|
||||||
The type definitions are lifted straight out of the emitted module, so
|
The type definitions are lifted straight out of the emitted module, so
|
||||||
there is no second spelling of the layout to get wrong. *)
|
there is no second spelling of the layout to get wrong. *)
|
||||||
let llvm_members ir sname nfields =
|
(* The type definitions lifted straight out of an emitted module, so the
|
||||||
let tydefs =
|
oracle never carries a second spelling of a layout. *)
|
||||||
lines_of ir
|
let tydefs_of ir =
|
||||||
|> List.filter (fun l ->
|
lines_of ir
|
||||||
String.length l > 0 && l.[0] = '%' && index_of l " = type " >= 0)
|
|> List.filter (fun l ->
|
||||||
in
|
String.length l > 0 && l.[0] = '%' && index_of l " = type " >= 0)
|
||||||
let sty = Printf.sprintf "%%\"%s\"" sname in
|
|> List.map (fun l -> l ^ "\n")
|
||||||
let b = Buffer.create 512 in
|
|> String.concat ""
|
||||||
List.iter (fun l -> Buffer.add_string b (l ^ "\n")) tydefs;
|
in
|
||||||
for i = 0 to nfields - 1 do
|
(* Hand LLVM a module of constant-folded ptrtoint expressions and read the
|
||||||
Buffer.add_string b
|
.quad it writes for each. Every layout question below is asked this way:
|
||||||
(Printf.sprintf
|
the answer comes from the backend that lays the type out, not from a
|
||||||
"@o%d = constant i64 ptrtoint (ptr getelementptr (%s, ptr null, i32 0, i32 %d) to i64)\n"
|
table written beside the code that would have to be wrong in the same
|
||||||
i sty i)
|
way to agree. *)
|
||||||
done;
|
let run_oracle src =
|
||||||
Buffer.add_string b
|
|
||||||
(Printf.sprintf
|
|
||||||
"@sz = constant i64 ptrtoint (ptr getelementptr (%s, ptr null, i32 1) to i64)\n"
|
|
||||||
sty);
|
|
||||||
let ll = Filename.concat scratch "flan-dwarf-oracle.ll" in
|
let ll = Filename.concat scratch "flan-dwarf-oracle.ll" in
|
||||||
let asm = Filename.concat scratch "flan-dwarf-oracle.s" in
|
let asm = Filename.concat scratch "flan-dwarf-oracle.s" in
|
||||||
Out_channel.with_open_bin ll (fun ch -> Out_channel.output_string ch (Buffer.contents b));
|
Out_channel.with_open_bin ll (fun ch -> Out_channel.output_string ch src);
|
||||||
let llc = try Sys.getenv "FLAN_LLC" with Not_found -> "llc" in
|
let llc = try Sys.getenv "FLAN_LLC" with Not_found -> "llc" in
|
||||||
let code =
|
let code =
|
||||||
Sys.command
|
Sys.command
|
||||||
@ -2026,6 +2022,40 @@ ERR@7 unexpected token: not the kind the caller was reading
|
|||||||
Some (List.rev !acc)
|
Some (List.rev !acc)
|
||||||
end
|
end
|
||||||
in
|
in
|
||||||
|
(* Every member's byte offset and the whole type's size, LLVM's answer. *)
|
||||||
|
let llvm_members ir sname nfields =
|
||||||
|
let sty = Printf.sprintf "%%\"%s\"" sname in
|
||||||
|
let b = Buffer.create 512 in
|
||||||
|
Buffer.add_string b (tydefs_of ir);
|
||||||
|
for i = 0 to nfields - 1 do
|
||||||
|
Buffer.add_string b
|
||||||
|
(Printf.sprintf
|
||||||
|
"@o%d = constant i64 ptrtoint (ptr getelementptr (%s, ptr null, i32 0, i32 %d) to i64)\n"
|
||||||
|
i sty i)
|
||||||
|
done;
|
||||||
|
Buffer.add_string b
|
||||||
|
(Printf.sprintf
|
||||||
|
"@sz = constant i64 ptrtoint (ptr getelementptr (%s, ptr null, i32 1) to i64)\n"
|
||||||
|
sty);
|
||||||
|
run_oracle (Buffer.contents b)
|
||||||
|
in
|
||||||
|
(* Alignment, which no getelementptr states directly. Put the type after a
|
||||||
|
single byte and ask where it lands: a struct member sits at the first
|
||||||
|
offset its own alignment allows, so the offset of field 1 in
|
||||||
|
{ i8, T } *is* alignof(T). Reading [2 x i64] out of the emitted type and
|
||||||
|
concluding 8 would be asserting the layout against itself, which is the
|
||||||
|
circularity BUILT.md already rejected for _Static_assert. *)
|
||||||
|
let llvm_align ir sname =
|
||||||
|
let sty = Printf.sprintf "%%\"%s\"" sname in
|
||||||
|
let b = Buffer.create 512 in
|
||||||
|
Buffer.add_string b (tydefs_of ir);
|
||||||
|
Buffer.add_string b (Printf.sprintf "%%alignprobe = type { i8, %s }\n" sty);
|
||||||
|
Buffer.add_string b
|
||||||
|
"@al = constant i64 ptrtoint (ptr getelementptr (%alignprobe, ptr null, i32 0, i32 1) to i64)\n";
|
||||||
|
match run_oracle (Buffer.contents b) with
|
||||||
|
| None -> None
|
||||||
|
| Some qs -> List.assoc_opt "al" qs
|
||||||
|
in
|
||||||
(* The case itself: the DWARF a source text produces must agree with LLVM
|
(* The case itself: the DWARF a source text produces must agree with LLVM
|
||||||
on every member's offset, and on the struct's size. *)
|
on every member's offset, and on the struct's size. *)
|
||||||
let layout_case name src sname fields =
|
let layout_case name src sname fields =
|
||||||
@ -2105,6 +2135,64 @@ ERR@7 unexpected token: not the kind the caller was reading
|
|||||||
(defn main [] i32 (let [n (N.A {.x 3})] (match n (A x) x _ 1)))\n")
|
(defn main [] i32 (let [n (N.A {.x 3})] (match n (A x) x _ 1)))\n")
|
||||||
"N" [ "tag"; "payload" ];
|
"N" [ "tag"; "payload" ];
|
||||||
|
|
||||||
|
(* -- Form: the one layout two programs have to agree on --------
|
||||||
|
Every layout above is checked because a debugger reads it. This one is
|
||||||
|
checked because the *compiler* reads it. A macro is compiled into a .so
|
||||||
|
and dlopened into the compiler, and the compiler then writes a Form into
|
||||||
|
raw memory a field at a time and reads one back the same way; nothing at
|
||||||
|
run time would notice if the two sides disagreed by a byte. The image
|
||||||
|
format is three numbers -- 24 bytes, align 8, payload at offset 8 -- and
|
||||||
|
the marshaller in lib/expand.ml is written to them, so here is where they
|
||||||
|
stop being an assumption.
|
||||||
|
|
||||||
|
They are not arbitrary. Form's widest cases are (Str [s string]) and
|
||||||
|
(List [xs [Form]]); a string and a slice are both ptr+len, 16 bytes at
|
||||||
|
align 8. So the tag is 4 padded to 8, the payload is 16, and the total
|
||||||
|
is 24. Adding a case with a wider member -- two f64s and a pointer, say
|
||||||
|
-- moves every one of these numbers, and this is what says so before the
|
||||||
|
first macro hands back a Form the compiler misreads. *)
|
||||||
|
let form_src =
|
||||||
|
"(defn shape [f Form] i32\n\
|
||||||
|
\ (match f (Int _n) 1 (Str _s) 2 (List xs) (i32 (len xs)) _ 0))\n\
|
||||||
|
(defn main [] i32 (shape (Form.Int {.i 1})))\n"
|
||||||
|
in
|
||||||
|
layout_case "DWARF offsets agree with LLVM: Form" form_src
|
||||||
|
"Form" [ "tag"; "payload" ];
|
||||||
|
(* The three numbers by name, so a failure says which one moved rather than
|
||||||
|
leaving it to be read out of an offset table. *)
|
||||||
|
(let ir = debug_ir form_src in
|
||||||
|
let want =
|
||||||
|
[ ("o0", 0, "the tag is at byte"); ("o1", 8, "the payload is at byte");
|
||||||
|
("sz", 24, "a Form is this many bytes wide:") ]
|
||||||
|
in
|
||||||
|
match llvm_members ir "Form" 2 with
|
||||||
|
| None -> Printf.printf "acceptance: Form's image format - llc unavailable, unchecked\n"
|
||||||
|
| Some oracle ->
|
||||||
|
List.iter
|
||||||
|
(fun (k, expect, what) ->
|
||||||
|
match List.assoc_opt k oracle with
|
||||||
|
| Some got when got <> expect ->
|
||||||
|
incr failures;
|
||||||
|
Printf.printf
|
||||||
|
"FAIL Form's image format\n %s %d, the marshaller says %d\n"
|
||||||
|
what got expect
|
||||||
|
| Some _ -> ()
|
||||||
|
| None ->
|
||||||
|
incr failures;
|
||||||
|
Printf.printf
|
||||||
|
"FAIL Form's image format\n the oracle gave no %s\n" k)
|
||||||
|
want;
|
||||||
|
(match llvm_align ir "Form" with
|
||||||
|
| Some 8 -> ()
|
||||||
|
| Some got ->
|
||||||
|
incr failures;
|
||||||
|
Printf.printf
|
||||||
|
"FAIL Form's image format\n align %d, the marshaller says 8\n" got
|
||||||
|
| None ->
|
||||||
|
incr failures;
|
||||||
|
print_endline
|
||||||
|
"FAIL Form's image format\n the oracle gave no alignment"));
|
||||||
|
|
||||||
(* Permuting the fields must actually move them. Asserting that the two
|
(* Permuting the fields must actually move them. Asserting that the two
|
||||||
orderings disagree is what makes the two cases above a test: an offset
|
orderings disagree is what makes the two cases above a test: an offset
|
||||||
table that ignored declaration order would satisfy both. *)
|
table that ignored declaration order would satisfy both. *)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user