Skip to content

Commit 8529550

Browse files
committed
Add a Tactics module in the bootstrapped plugin to make use of ERASEcast
1 parent f06f22d commit 8529550

File tree

8 files changed

+199
-48
lines changed

8 files changed

+199
-48
lines changed

.vscode/rocq-verified-extraction-main.code-workspace

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,16 @@
44
"path": ".."
55
}
66
],
7-
"settings": {}
7+
"settings": {
8+
"coqtop.args": [
9+
"-Q", "theories", "Malfunction",
10+
"-Q", "examples", "Malfunction.Examples",
11+
"-Q", "plugin/plugin", "Malfunction.Plugin",
12+
"-Q", "plugin/plugin-bootstrap", "VerifiedExtraction",
13+
"-I", "plugin",
14+
"-Q", "benchmarks", "VerifiedExtraction.Benchmarks"
15+
],
16+
"coqtop.binPath": "_opam/bin",
17+
"coqtop.coqidetopExe": "coqidetop"
18+
}
819
}

examples/erase-conv/fib.v

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
From VerifiedExtraction Require Import Loader.
2+
3+
Set Verified Extraction Build Directory "_build".
4+
5+
(* Set Debug "verified-extraction". *)
6+
7+
Definition foo := (@eq_refl bool false <<<: (false = negb true)).
8+
9+
From Stdlib Require Import Arith.
10+
11+
Fixpoint fib (n : nat) :=
12+
match n with
13+
| 0 => 0
14+
| 1 => 1
15+
| S (S n as m) => fib m + fib n
16+
end.
17+
18+
Time Definition fib2 := Eval compute in fib 2.
19+
Check (@eq_refl nat fib2 <<<: (fib2 = fib 2)).
20+
21+
Time Definition fib23 := Eval compute in fib 23.
22+
(* > 1.5s *)
23+
Time Definition efib23 := (@eq_refl nat fib23 <<<: (fib23 = fib 23)).
24+
(* Runtime is lower (< 1s ) even with compilation + dynlink + reification *)

lib/rocq_verified_extraction_plugin/lib/verified_extraction.ml

Lines changed: 75 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -334,8 +334,9 @@ let push_line buf line =
334334
let string_of_buffer buf = Bytes.to_string (Buffer.to_bytes buf)
335335

336336
let execute cmd =
337-
debug Pp.(fun () -> str "Executing: " ++ str cmd ++ str " in environemt: " ++
338-
prlist_with_sep spc str (Array.to_list (Unix.environment ())));
337+
debug Pp.(fun () -> str "Executing: " ++ str cmd);
338+
(* ++ str " in environemt: " ++ *)
339+
(* prlist_with_sep spc str (Array.to_list (Unix.environment ()))); *)
339340
let (stdout, stdin, stderr) = Unix.open_process_full cmd (Unix.environment ()) in
340341
let continue = ref true in
341342
let outbuf, errbuf = Buffer.create 100, Buffer.create 100 in
@@ -575,7 +576,7 @@ struct
575576
let cstrs = apply_reordering qhd m cstrs in
576577
if Obj.is_block v then
577578
let ord = Obj.tag v in
578-
let () = debug Pp.(fun () -> str (Printf.sprintf "Reifying constructor block of tag %i" ord)) in
579+
(* let () = debug Pp.(fun () -> str (Printf.sprintf "Reifying constructor block of tag %i" ord)) in *)
579580
let coqidx =
580581
try find_nth_non_constant ord cstrs
581582
with Not_found -> ill_formed env sigma ty
@@ -585,28 +586,28 @@ struct
585586
let ctx = EConstr.Vars.smash_rel_context cstr.cs_args in
586587
let vargs = List.init (List.length ctx) (Obj.field v) in
587588
let args' = List.map2 (fun decl v ->
588-
let argty = check_reifyable_value env sigma
589+
let argty = check_reifyable_value_type env sigma
589590
(Context.Rel.Declaration.get_type decl) in
590591
aux argty v) (List.rev ctx) vargs in
591592
Term.applistc (Constr.mkConstructU ((hd, coqidx + 1), u)) (params @ args')
592593
else (* Constant constructor *)
593594
let ord = (Obj.magic v : int) in
594-
let () = debug Pp.(fun () -> str @@ Printf.sprintf "Reifying constant constructor: %i" ord) in
595+
(* let () = debug Pp.(fun () -> str @@ Printf.sprintf "Reifying constant constructor: %i" ord) in *)
595596
let coqidx =
596597
try find_nth_constant ord cstrs
597598
with Not_found -> ill_formed env sigma ty
598599
in
599600
let coqidx = find_reverse_mapping qhd m coqidx in
600-
let () = debug Pp.(fun () -> str @@ Printf.sprintf "Reifying constant constructor: %i is %i in Rocq" ord coqidx) in
601+
(* let () = debug Pp.(fun () -> str @@ Printf.sprintf "Reifying constant constructor: %i is %i in Rocq" ord coqidx) in *)
601602
Term.applistc (Constr.mkConstructU ((hd, coqidx + 1), u)) params
602603
| IsPrimitive (c, u, _args) ->
603604
if Environ.is_array_type env c then
604-
CErrors.user_err Pp.(str "Primitive arrays are not supported yet in MetaRocq r Extractioneification")
605+
CErrors.user_err Pp.(str "Primitive arrays are not supported yet in MetaRocq reification")
605606
else if Environ.is_float64_type env c then
606607
Constr.mkFloat (Obj.magic v)
607608
else if Environ.is_int63_type env c then
608609
Constr.mkInt (Obj.magic v)
609-
else CErrors.user_err Pp.(str "Unsupported primitive type in MetaRocq r Extractioneification")
610+
else CErrors.user_err Pp.(str "Unsupported primitive type in MetaRocq reification")
610611
in aux ty v
611612

612613
let reify opts env sigma tyinfo result =
@@ -647,28 +648,32 @@ let compile opts names tyinfos fname =
647648
Printf.sprintf "%s cmx %s -shared -package %s %s" malfunction optimize
648649
(String.concat "," packages) fname
649650
in
650-
let _out, _err = execute opts compile_cmd in (* we now have fname . cmx *)
651+
let _out, _err = time opts Pp.(str"Malfunction Compilation") (execute opts) compile_cmd in (* we now have fname . cmx *)
651652
let cmxfile = Filename.chop_extension fname ^ ".cmx" in
652653
let cmxsfile = Filename.chop_extension fname ^ ".cmxs" in
653654
(* Build the shared library *)
654655
let link_cmd =
655656
Printf.sprintf "%s opt -shared -package %s -o %s %s" ocamlfind
656657
(String.concat "," packages) cmxsfile cmxfile
657658
in
658-
let _out, _err = execute opts link_cmd in
659+
let _out, _err = time opts Pp.(str "Compilation to a dynamically loadable library") (execute opts) link_cmd in
659660
Some (SharedLib (names, tyinfos, cmxsfile))
660661
| Standalone link_coq ->
661662
let output = Filename.chop_extension fname in
662663
let flags, packages =
663664
if link_coq then
664-
"-thread -linkpkg", String.concat "," (statically_linked_pkgs @ packages)
665-
else "-thread -linkpkg", String.concat "," packages
665+
"-thread -linkpkg", statically_linked_pkgs @ packages
666+
else "-thread -linkpkg", packages
667+
in
668+
let packagesopt =
669+
if CList.is_empty packages then ""
670+
else "-package " ^ String.concat "," packages
666671
in
667672
let compile_cmd =
668-
Printf.sprintf "%s compile %s %s -package %s -o %s %s"
669-
malfunction optimize flags packages output fname
673+
Printf.sprintf "%s compile %s %s %s -o %s %s"
674+
malfunction optimize flags packagesopt output fname
670675
in
671-
let _out, _err = time opts Pp.(str "Compilation") (execute opts) compile_cmd in (* we now have fname . cmx *)
676+
let _out, _err = time opts Pp.(str "Malfunction Compilation") (execute opts) compile_cmd in (* we now have fname . cmx *)
672677
notice opts Pp.(fun () -> str "Compiled to " ++ str output);
673678
Some (StandaloneProgram output)
674679

@@ -709,18 +714,6 @@ type malfunction_compilation_function =
709714
malfunction_pipeline_config -> malfunction_program_type -> TemplateProgram.template_program ->
710715
string list * string
711716

712-
let decompose_argument env sigma c =
713-
let rec aux c =
714-
let fn, args = EConstr.decompose_app sigma c in
715-
match EConstr.kind sigma fn with
716-
| Construct (cstr, u) when Environ.QGlobRef.equal env (ConstructRef cstr) (Rocqlib.lib_ref "core.prod.intro") ->
717-
(match CArray.to_list args with
718-
| [ _; _; fst; snd ]
719-
-> aux fst @ [Reify.check_reifyable_thunk_or_value env sigma snd]
720-
|_ -> [Reify.check_reifyable_thunk_or_value env sigma c])
721-
| _ -> [Reify.check_reifyable_thunk_or_value env sigma c]
722-
in aux c
723-
724717
let set_opam_env opts =
725718
let path = Unix.getenv "PATH" in
726719
let opam_binpath =
@@ -738,18 +731,13 @@ let set_opam_env opts =
738731

739732
let extract_and_run
740733
(compile_malfunction : malfunction_compilation_function)
741-
?loc opts env sigma c dest : (Constr.t list) option =
742-
let opts = make_options loc opts in
743-
let () = set_opam_env opts in
744-
let prog = time opts Pp.(str"Quoting") (Ast_quoter.quote_term_rec ~bypass:opts.bypass_qeds env) sigma (EConstr.to_constr sigma c) in
734+
?loc opts env sigma (c: EConstr.t) (tyinfos : Reify.reifyable_type list) dest : Constr.t list option =
735+
let () = time opts Pp.(str"Setting opam env") set_opam_env opts in
736+
let prog = time opts Pp.(str"Quoting") (Ast_quoter.quote_term_rec ~bypass:opts.bypass_qeds ~with_universes:false env sigma) (EConstr.to_constr sigma c) in
745737
let pt = match opts.program_type with
746738
| Some (Standalone _) | None -> Standalone_binary
747739
| Some Plugin -> Shared_library ("Rocq_verified_extraction_plugin__Verified_extraction", "register_plugin")
748740
in
749-
let tyinfos =
750-
try decompose_argument env sigma c
751-
with e -> if not (opts.load || opts.run) then [] else raise e
752-
in
753741
let run_pipeline opts prog = compile_malfunction opts.malfunction_pipeline_config pt prog in
754742
let names, eprog = time opts Pp.(str"Extraction") (run_pipeline opts) prog in
755743
let names = if opts.load then
@@ -770,13 +758,15 @@ let extract_and_run
770758
let dest = match dest with
771759
| None -> Feedback.msg_notice Pp.(str eprog); None
772760
| Some fname ->
773-
let fname = build_fname fname in
774-
let oc = open_out fname in (* Does not raise? *)
775-
let () = output_string oc eprog in
776-
let () = output_char oc '\n' in
777-
close_out oc;
778-
notice opts Pp.(fun () -> str"Extracted code written to " ++ str fname);
779-
Some fname
761+
let write () =
762+
let fname = build_fname fname in
763+
let oc = open_out fname in (* Does not raise? *)
764+
let () = output_string oc eprog in
765+
let () = output_char oc '\n' in
766+
close_out oc;
767+
notice opts Pp.(fun () -> str"Extracted code written to " ++ str fname);
768+
Some fname
769+
in time opts Pp.(str"Serializing code") write ()
780770
in
781771
match dest with
782772
| None -> None
@@ -786,9 +776,9 @@ let extract_and_run
786776
let temp = fname ^ ".tmp" in
787777
ignore (execute opts (Printf.sprintf "%s fmt < %s > %s && mv %s %s" malfunction fname temp temp fname))
788778
else ();
789-
match compile opts names tyinfos fname with
779+
match time opts Pp.(str"Compiling") (compile opts names tyinfos) fname with
790780
| None -> None
791-
| Some result -> run opts env sigma result
781+
| Some result -> time opts Pp.(str"Running") (run opts env sigma) result
792782

793783
let print_results env sigma = function
794784
| None -> ()
@@ -824,6 +814,46 @@ let eval ?loc opts gr =
824814
let env, sigma, c = eval_plugin_gen ?loc opts gr in
825815
c
826816

817+
let prc = Printer.pr_constr_env
818+
819+
let erase_eval_function compile_malfunction : Eraseconv.erase_evaluation_function =
820+
fun env term ty ->
821+
let sigma = Evd.from_env env in
822+
debug Pp.(fun () -> str"Erasure evaluation called on term: " ++ prc env sigma term ++ str" of type " ++
823+
prc env sigma ty);
824+
let opts = [ProgramType Plugin; Time; Load; Run] in
825+
let opts = make_options None opts in
826+
let tyinfo = time opts Pp.(str"Checking reifyability") (Reify.check_reifyable_thunk_or_value_type env sigma) (EConstr.of_constr ty) in
827+
let res = time opts Pp.(str"Extracting, running and reifying") (extract_and_run compile_malfunction opts env sigma (EConstr.of_constr term) [tyinfo]) None in
828+
match res with
829+
| None ->
830+
debug Pp.(fun () -> str"Didn't reify to a value");
831+
Error ()
832+
| Some [res] ->
833+
debug Pp.(fun () -> str"Reified to value: " ++ Printer.pr_constr_env env sigma res);
834+
Ok res
835+
| Some l -> assert false
836+
837+
let install_erase_conv compile_malfunction =
838+
Eraseconv.install_erase_conv (erase_eval_function compile_malfunction)
839+
840+
let decompose_argument env sigma c : Reify.reifyable_type list =
841+
let rec aux c =
842+
let fn, args = EConstr.decompose_app sigma c in
843+
match EConstr.kind sigma fn with
844+
| Construct (cstr, u) when Environ.QGlobRef.equal env (ConstructRef cstr) (Rocqlib.lib_ref "core.prod.intro") ->
845+
(match CArray.to_list args with
846+
| [ _; _; fst; snd ]
847+
-> aux fst @ [Reify.check_reifyable_thunk_or_value env sigma snd]
848+
|_ -> [Reify.check_reifyable_thunk_or_value env sigma c])
849+
| _ -> [Reify.check_reifyable_thunk_or_value env sigma c]
850+
in aux c
851+
827852
let extract compile_malfunction ?loc opts env sigma c dest =
828-
let res = extract_and_run compile_malfunction ?loc opts env sigma c dest in
853+
let opts = make_options loc opts in
854+
let tyinfos =
855+
try decompose_argument env sigma c
856+
with e -> if not (opts.load || opts.run) then [] else raise e
857+
in
858+
let res = extract_and_run compile_malfunction ?loc opts env sigma c tyinfos dest in
829859
print_results env sigma res

lib/rocq_verified_extraction_plugin/lib/verified_extraction.mli

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,6 @@ val extract :
8787

8888
val eval_plugin : ?loc:Loc.t -> malfunction_command_args list -> Libnames.qualid -> unit
8989

90-
val eval : ?loc:Loc.t -> malfunction_command_args list -> Libnames.qualid -> Constr.t
90+
val eval : ?loc:Loc.t -> malfunction_command_args list -> Libnames.qualid -> Constr.t
91+
92+
val install_erase_conv : malfunction_compilation_function -> unit

plugin/plugin-bootstrap/Tactics.v

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
From VerifiedExtraction Require Import Loader.
2+
3+
Inductive value_of {A} : A -> Type :=
4+
| value_is a : value_of a.
5+
Definition get_value_of {A} {a : A} (c : value_of a) : A :=
6+
let 'value_is a := c in a.
7+
8+
Lemma value_of_eq {A} {a : A} (c : value_of a) : get_value_of c = a.
9+
Proof. destruct c. reflexivity. Defined.
10+
11+
From Ltac2 Require Import Ltac2.
12+
13+
(** Runs erasure evaluation only once, in the kernel (if paired with Std.exact).
14+
Beware that no check is performed that the cast is correct when using this in a tactic.
15+
It will be checked at Qed-time only. *)
16+
Ltac2 erase_nocheck c v :=
17+
let vty := Constr.type c in
18+
let c := Constr.Unsafe.make (Constr.Unsafe.Cast constr:(@value_is $vty $v) Constr.Cast.erase constr:(@value_of $vty $c)) in
19+
c.
20+
21+
(** Runs erasure evaluation twice, first to find the value during elaboration,
22+
then to check in the kernel *)
23+
Ltac2 erase_compute c :=
24+
let vty := Constr.type c in
25+
let c := Constr.pretype preterm:(@value_is $vty _ <<<: @value_of $vty $c) in
26+
c.
27+
28+
(** Helper to extract the value from an `value_of` term and apply `value_of_eq` to
29+
produce an equality. *)
30+
Ltac2 show_value c :=
31+
let ty := Constr.type c in
32+
match Constr.Unsafe.kind ty with
33+
| Constr.Unsafe.App _ args =>
34+
let car := Array.get args 0 in
35+
let computed := Array.get args 1 in
36+
let term := constr:(@get_value_of $car $computed $c) in
37+
let cres := eval hnf in $term in
38+
Std.exact_no_check constr:(value_of_eq $c : $computed = $cres)
39+
| _ => Control.throw (Invalid_argument None)
40+
end.
41+
42+
(** Wrappers and notations for the primitives. *)
43+
44+
(** Use `erase_nocheck t v` to get a proof through erasure evaluation that `t` is equal to `v`.
45+
The evaluation will happen only when this proof is typechecked by the kernel.
46+
If `v` is not a value, standard conversion will apply to convert it to `t`'s value found by erased evaluation. *)
47+
Ltac2 erase_nocheck0 t v := show_value (erase_nocheck t v).
48+
Ltac2 Notation "erase_nocheck" t(constr) v(constr) := erase_nocheck0 t v.
49+
50+
(** Use `erase_compute t` to find out the value `v` through erasure evaluation and get a proof that `t` equals `v`.
51+
This requires running evaluation twice. I.e. if used in a proof it will run evaluation at the tactic call and again at
52+
qed time.
53+
*)
54+
Ltac2 erase_compute0 t := show_value (erase_compute t).
55+
Ltac2 Notation "erase_compute" t(constr) := erase_compute0 t.
56+
57+
(** Just compute the value associated to `t` during elaboration and throw away the proof that it comes from `t`.
58+
This runs evaluation only once. *)
59+
Ltac2 erase_forget0 t :=
60+
let vty := Constr.type t in
61+
let c := Constr.pretype preterm:(@value_is $vty _ <<<: @value_of $vty $t) in
62+
match Constr.Unsafe.kind_nocast c with
63+
| Constr.Unsafe.App _compute args => Std.exact_no_check (Array.get args 1)
64+
| _ => Control.throw (Invalid_argument None)
65+
end.
66+
Ltac2 Notation "erase_forget" t(constr) := erase_forget0 t.
67+
68+
Ltac2 Notation "value_of" c(constr) := show_value c.
69+
70+
(* Tests *)
71+
Definition foo (x : unit) := true.
72+
73+
Definition test := ltac2:(erase_nocheck (foo tt) true).
74+
Check test : foo tt = true.
75+
76+
Definition testevar := ltac2:(erase_compute (foo tt)).
77+
Check testevar : foo tt = true.
78+
79+
Definition foo_tt_value := ltac2:(erase_forget (foo tt)).
80+
Check eq_refl : foo_tt_value = true.

plugin/plugin-bootstrap/g_verified_extraction_malfunction.mlg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,8 @@ let compile_malfunction conf pt p =
292292

293293
let extract = extract compile_malfunction
294294

295+
let () = install_erase_conv compile_malfunction
296+
295297
}
296298

297299
VERNAC COMMAND EXTEND Malfunction_Verified_Extraction CLASSIFIED AS QUERY

plugin/plugin/g_verified_extraction_ocaml.mlg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ let compile_malfunction conf pt p =
7979

8080
let extract = extract compile_malfunction
8181

82+
let () = install_erase_conv compile_malfunction
83+
8284
}
8385

8486
VERNAC COMMAND EXTEND Verified_Extraction CLASSIFIED AS QUERY

theories/PrintMli.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Require Import Nat List.
1+
From Stdlib Require Import Nat List.
22
From Stdlib Require Import Strings.Ascii.
33
From MetaRocq.Template Require Import Loader All Checker.
44
From MetaRocq.Utils Require Import bytestring.

0 commit comments

Comments
 (0)