Run the verifier, because string needles cannot see what breaks
A call without a !dbg inside a function that has debug info is a hard LLVM rejection, not a warning — it turns every debug build into a clang error. So is a DISubprogram the compile unit does not reach. Neither is visible to an assertion about the text of the module, and both are the kind of thing that appears when this file grows a new call from somewhere other than a Tast node. The program the cases run through is chosen for those calls specifically: a bounds check, a condition signalled and handled, a restart transferred to, a defer on the way out. Every one of them is a call the backend invents. Emit.redefinition is the half that needed this. It had only ever run at the default debug:false, and it differs from Emit.program in exactly the places metadata goes wrong: hidden bodies, the by-name cell and global lookups, and flan_reload_install and flan_reload_call, which are raw defines with no subprogram that nonetheless contain calls. Both directions of `known' are covered, because they emit almost entirely different code.
This commit is contained in:
parent
5f5cc8bee9
commit
e3f352321d
@ -1157,6 +1157,77 @@ ERR@7 unexpected token: not the kind the caller was reading
|
||||
print_endline "FAIL the transfer channel appeared as a local variable"
|
||||
end;
|
||||
|
||||
(* LLVM's own verifier, over both entry points. String needles cannot see
|
||||
a DISubprogram the compile unit does not reach, or a call without a
|
||||
!dbg inside a function that has debug info — and that second one is a
|
||||
hard rejection, not a warning, so it would turn every debug build into
|
||||
a clang error rather than into anything visible here.
|
||||
|
||||
[redefinition] is the half that needs this most. It is only ever run at
|
||||
the default debug:false today, and it differs from [program] in exactly
|
||||
the places metadata goes wrong: hidden bodies, the by-name cell and
|
||||
global loads, and flan_reload_install and flan_reload_call, which are
|
||||
raw defines with no subprogram that nonetheless contain calls. *)
|
||||
if Sys.command "command -v opt > /dev/null 2>&1" = 0 then begin
|
||||
let verifies name ir =
|
||||
let f = Filename.concat scratch "flan-dwarf-verify.ll" in
|
||||
Out_channel.with_open_bin f (fun ch -> Out_channel.output_string ch ir);
|
||||
let log = Filename.concat scratch "flan-dwarf-verify.log" in
|
||||
let code =
|
||||
Sys.command
|
||||
(Printf.sprintf "opt -passes=verify -disable-output %s > %s 2>&1"
|
||||
(Filename.quote f) (Filename.quote log))
|
||||
in
|
||||
if code <> 0 then begin
|
||||
incr failures;
|
||||
Printf.printf "FAIL %s: LLVM's verifier rejected the module\n%s\n" name
|
||||
(In_channel.with_open_bin log In_channel.input_all)
|
||||
end;
|
||||
(try Sys.remove f with Sys_error _ -> ());
|
||||
(try Sys.remove log with Sys_error _ -> ())
|
||||
in
|
||||
(* A program with a bit of everything that emits a call the backend
|
||||
invents rather than one a Tast node asked for: a bounds check, a
|
||||
condition signalled and handled, a restart transferred to, a defer on
|
||||
the way out. Each would be a verifier rejection without a location. *)
|
||||
let src =
|
||||
"(defstruct Missing [id i32])\n\
|
||||
(defvar seen i64)\n\
|
||||
(defvar arr [4 i32])\n\
|
||||
(defn pick [xs [i32] i i32] i32 (at xs i))\n\
|
||||
(defn fetch [n i32] i32\n\
|
||||
\ (restart-case\n\
|
||||
\ (do (error (Missing {:id n})) 0)\n\
|
||||
\ (use-placeholder [] -1)))\n\
|
||||
(defn run [] i32\n\
|
||||
\ (defer (set seen (+ seen 1)))\n\
|
||||
\ (handler-bind [(Missing [m] (invoke-restart 'use-placeholder))]\n\
|
||||
\ (fetch 3)))\n\
|
||||
(defn main [] i32\n\
|
||||
\ (set (at arr 2) 9)\n\
|
||||
\ (let [s (slice arr 0 4)]\n\
|
||||
\ (print-i64 (i64 (pick s 2))) (newline)\n\
|
||||
\ (print-i64 (i64 (run))) (newline)\n\
|
||||
\ 0))\n"
|
||||
in
|
||||
let decls = Parse.program (Reader.read_all ~file:"<verify>" src) in
|
||||
let p = Check.program decls in
|
||||
verifies "the whole program, with debug info"
|
||||
(Emit.program ~debug:true ~pnames:(pnames_of decls) p);
|
||||
(* And a redefinition module against a host that has every name — the
|
||||
shape C-c C-c produces. *)
|
||||
verifies "a redefinition module, with debug info"
|
||||
(Emit.redefinition ~dev:true ~debug:true ~known:(fun _ -> true) p
|
||||
~fns:[ "fetch"; "run" ]);
|
||||
(* And one against a host that has none of them, which is the other
|
||||
path: every call goes through flan_dev_cell and every global through
|
||||
flan_dev_global, so the module is almost entirely different code. *)
|
||||
verifies "a redefinition of names the host does not have"
|
||||
(Emit.redefinition ~dev:true ~debug:true ~known:(fun _ -> false) p
|
||||
~fns:[ "fetch"; "run" ])
|
||||
end
|
||||
else print_endline "acceptance: the DWARF verifier cases skipped (no opt)";
|
||||
|
||||
(* A debug build and a release build must still be the same program. *)
|
||||
let debug_compile ?(dev = false) path =
|
||||
let exe =
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user