diff --git a/test/test_acceptance.ml b/test/test_acceptance.ml index 4271d21..5d5ae0c 100644 --- a/test/test_acceptance.ml +++ b/test/test_acceptance.ml @@ -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:"" 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 =