aboutsummaryrefslogtreecommitdiff
path: root/src/Compilers/Z/Bounds/Pipeline/ReflectiveTactics.v
blob: 9193ea17f7ea92e5264d7a064cec7efaa85b3091 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
(** * Reflective Pipeline: Tactics that execute the pipeline *)
(** N.B. This file should not need to be changed in normal
    modifications of the reflective transformations; to modify the
    transformations performed in the reflective pipeline; see
    Pipeline/Definition.v.  If the input format of the pre-reflective
    goal changes, prefer adding complexity to Pipeline/Glue.v to
    transform the goal and hypotheses into a uniform syntax to
    modifying this file.  This file will need to be modified if you
    perform heavy changes in the shape of the generic or ℤ-specific
    reflective machinery itself, or if you find bugs or slowness. *)
(** ** Preamble *)
Require Import Coq.ZArith.ZArith.
Require Import Crypto.Compilers.Syntax.
Require Import Crypto.Compilers.Intros.
Require Import Crypto.Compilers.Wf.
Require Import Crypto.Compilers.WfReflective.
Require Import Crypto.Compilers.RenameBinders.
Require Import Crypto.Compilers.Eta.
Require Import Crypto.Compilers.EtaInterp.
Require Import Crypto.Compilers.Z.Syntax.
Require Import Crypto.Compilers.Z.Syntax.Util.
Require Import Crypto.Compilers.Z.Bounds.Interpretation.
Require Import Crypto.Compilers.Z.Bounds.Relax.
Require Import Crypto.Compilers.Reify.
Require Import Crypto.Compilers.Z.Reify.
Require Import Crypto.Compilers.InterpSideConditions.
Require Import Crypto.Compilers.Z.InterpSideConditions.
Require Import Crypto.Compilers.Z.Bounds.Pipeline.Definition.
Require Import Crypto.Util.Tactics.Head.
Require Import Crypto.Util.Tactics.SubstLet.
Require Import Crypto.Util.Tactics.UnfoldArg.
Require Import Crypto.Util.Tactics.UnifyAbstractReflexivity.
Require Import Crypto.Util.FixedWordSizes.
Require Import Crypto.Util.Option.
Require Import bbv.WordScope.

(** The final tactic in this file, [do_reflective_pipeline], takes a
    goal of the form
<<
@Bounds.is_bounded_by (codomain T) bounds (fZ (cast_back_flat_const v))
  /\ cast_back_flat_const fW = fZ (cast_back_flat_const v)
>>

    where [fW] must be a context definition which is a single evar,
    and all other terms must be evar-free.  It fully solves the goal,
    instantiating [fW] with an appropriately-unfolded
    (reflection-definition-free) version of [fZ (cast_back_flat_const
    v)] which has been transformed by the reflective pipeline. *)

Module Export Exports.
  Export Crypto.Compilers.Reify. (* export for the instances for recursing under binders *)
  Export Crypto.Compilers.Z.Reify. (* export for the tactic redefinitions *)
  Export Crypto.Compilers.Z.Bounds.Pipeline.Definition.Exports.
End Exports.

(** ** Reification *)
(** The [do_reify] tactic handles goals of the form
<<
forall x, Interp _ ?e x = F
>>
    by reifying [F]. *)
Ltac do_reify :=
  unfold_second_arg Tuple.tuple;
  unfold_second_arg Tuple.tuple';
  cbv beta iota delta [Tuple.tuple Tuple.tuple'] in *;
  cbv beta iota delta [Syntax.interp_flat_type Syntax.interp_base_type];
  reify_context_variables;
  Reify_rhs; reflexivity.
(** ** Input Boundedness Side-Conditions *)
(** The tactic [handle_bounds_from_hyps] handles goals of the form
<<
Bounds.is_bounded_by (_, _, ..., _) _
>>
     by splitting them apart and looking in the context for hypotheses
     that prove the bounds. *)
Ltac handle_bounds_from_hyps :=
  repeat match goal with
         | _ => assumption
         | [ |- cast_back_flat_const _ = cast_back_flat_const _ ] => reflexivity
         | [ |- _ /\ _ ] => split
         | [ |- Bounds.is_bounded_by (_, _) _ ] => split
         end.
(** ** Unfolding [Interp] *)
(** The reduction strategies [interp_red], [extra_interp_red], and
    [constant_simplification] (the latter two defined in
    Pipeline/Definition.v) define the constants that get unfolded
    before instantiating the original evar with [Interp _
    vm_computed_reified_expression arguments]. *)
Declare Reduction interp_red
  := cbv [fst snd
              Interp (*InterpEta interp_op*) interp interp_eta interpf interpf_step
              interp_flat_type_eta interp_flat_type_eta_gen interp_flat_type
              interp_base_type interp_op
              SmartMap.SmartFlatTypeMap SmartMap.SmartFlatTypeMapUnInterp SmartMap.SmartFlatTypeMapInterp2
              SmartMap.smart_interp_flat_map
              codomain domain
              lift_op Zinterp_op cast_const
              ZToInterp interpToZ
         ].

(** ** Solving Side-Conditions of Equality *)
(** This section defines a number of different ways to solve goals of
    the form [LHS = RHS] where [LHS] may contain evars and [RHS] must
    not contain evars.  Most tactics use [abstract] to reduce the load
    on [Defined] and to catch looping behavior early. *)

(** The tactic [unify_abstract_cbv_interp_rhs_reflexivity] runs the interpretation
    reduction strategies in [RHS] and unifies the result with [LHS],
    and does not use the vm (and hence does not fully reduce things,
    which is important for efficiency). *)
Ltac unify_abstract_cbv_interp_rhs_reflexivity :=
  intros; clear;
  lazymatch goal with
  | [ |- ?LHS = ?RHS ]
    => let RHS' := (eval interp_red in RHS) in
       let RHS' := (eval extra_interp_red in RHS') in
       let RHS' := lazymatch do_constant_simplification with
                   | true => (eval constant_simplification in RHS')
                   | _ => RHS'
                   end in
       unify LHS RHS'; abstract exact_no_check (eq_refl RHS')
  end.

(** *** Boundedness Lemma Side Conditions *)
(** The tactic [handle_boundedness_side_condition] unfolds relevant
    identifiers in the side-condition of the boundedness lemma.  It
    then calls [boundedness_side_condition_solver], which can be
    overridden. *)
(** The tactic [boundedness_side_condition_solver] attempts to solve
    the algebraic side conditions of the boundedness lemma; it leaves
    them over if it cannot solve them. *)
Ltac boundedness_side_condition_solver :=
  repeat match goal with
         | [ |- _ /\ _ ] => apply conj
         | [ |- ?x = ?x ] => reflexivity
         end;
  try abstract ring.
Ltac handle_boundedness_side_condition :=
  post_intro_interp_flat_type_intros;
  cbv [id
         fst snd
         InterpSideConditions Compilers.InterpSideConditions.InterpSideConditions interp_side_conditions interpf_side_conditions interpf_side_conditions_gen
         Z.Syntax.Util.interped_op_side_conditions
         ExprInversion.invert_Abs
         Syntax.interp_op Syntax.lift_op
         Syntax.Zinterp_op
         SmartMap.SmartFlatTypeMapUnInterp SmartMap.SmartValf SmartMap.SmartFlatTypeMapInterp2
         Syntax.cast_const Syntax.ZToInterp Syntax.interpToZ];
  cbv [PointedProp.and_pointed_Prop PointedProp.to_prop];
  try match goal with
      | [ |- True ] => exact I
      end;
  boundedness_side_condition_solver.

(** ** Assemble the parts of Pipeline.Definition, in Gallina *)
(** In this section, we assemble [PreWfPipeline] and [PostWfPipeline],
    and add extra equality hypotheses to minimize the work we have to
    do in Ltac. *)
(** *** Gallina assembly imports *)
Require Import Crypto.Compilers.Syntax.
Require Import Crypto.Compilers.SmartMap.
Require Import Crypto.Compilers.Wf.
Require Import Crypto.Compilers.WfReflectiveGen.
Require Import Crypto.Compilers.WfReflective.
Require Import Crypto.Compilers.Eta.
Require Import Crypto.Compilers.EtaWf.
Require Import Crypto.Compilers.EtaInterp.
Require Import Crypto.Compilers.Z.Bounds.Pipeline.OutputType.
Require Import Crypto.Compilers.Z.Bounds.Pipeline.Definition.
Require Import Crypto.Compilers.Z.Syntax.
Require Import Crypto.Compilers.Z.Syntax.Equality.
Require Import Crypto.Compilers.Z.Syntax.Util.
Require Import Crypto.Compilers.Z.Bounds.Interpretation.
Require Import Crypto.Compilers.Z.Bounds.Relax.
Require Import Crypto.Util.PointedProp.
Require Import Crypto.Util.PartiallyReifiedProp.
Require Import Crypto.Util.Equality.

(** *** Gallina assembly *)
Section with_round_up_list.
  Context {allowable_lgsz : list nat}.

  Local Notation pick_typeb := (@Bounds.bounds_to_base_type (Bounds.round_up_to_in_list allowable_lgsz)) (only parsing).
  Local Notation pick_type v := (SmartFlatTypeMap pick_typeb v).

  Context (opts : PipelineOptions)
          {t : type base_type}
          {input_bounds : interp_flat_type Bounds.interp_base_type (domain t)}
          {given_output_bounds : interp_flat_type Bounds.interp_base_type (codomain t)}
          {v' : interp_flat_type interp_base_type (pick_type input_bounds)}
          {fZ : interp_flat_type interp_base_type (domain t)
                -> interp_flat_type interp_base_type (codomain t)}
          {final_e_evar : interp_flat_type interp_base_type (pick_type given_output_bounds)}.

  Class PipelineEvars :=
    {
      b : interp_flat_type Bounds.interp_base_type (codomain t);
      e' : Expr (pick_type input_bounds -> pick_type b);
      e_final_newtype : Expr (pick_type input_bounds -> pick_type given_output_bounds);
      e : Expr (domain t -> codomain t);
      e_pre_pkg : Expr (domain t -> codomain t);
      e_pkg : option (@ProcessedReflectivePackage allowable_lgsz)
    }.

  Context (evars : PipelineEvars)

          (** ** reification *)
          (rexpr_sig : { rexpr : Expr t | forall x, Interp rexpr x = fZ x }).

  Record PipelineSideConditions :=
    {
      (** ** pre-wf pipeline *)
      He : e = PreWfPipeline (proj1_sig rexpr_sig);
      (** ** proving wf *)
      He_unnatize_for_wf : forall var, unnatize_expr 0 (ExprEta' e (fun t => (nat * var t)%type)) = ExprEta' e _;
      Hwf : forall var1 var2,
          let P := (@reflect_wfT base_type base_type_eq_semidec_transparent op op_beq var1 var2 nil _ _ (ExprEta' e _) (ExprEta' e _)) in
          trueify P = P;
      (** ** post-wf-pre-bounds-pipeline *)
      Hpost_wf_pre_bounds : e_pre_pkg = PostWfPreBoundsPipeline opts e;
      (** ** post-wf-pipeline *)
      Hpost : e_pkg = PostWfBoundsPipeline _ opts e e_pre_pkg input_bounds;
      Hpost_correct : Some {| OutputType.input_expr := e ; OutputType.input_bounds := input_bounds ; output_bounds := b ; output_expr := e' |} = e_pkg;
      (** ** bounds relaxation *)
      Hbounds_relax : Bounds.is_tighter_thanb b given_output_bounds = true;
      Hbounds_sane : pick_type given_output_bounds = pick_type b;
      Hbounds_sane_refl
      : e_final_newtype
        = eq_rect _ (fun t => Expr (Arrow (pick_type input_bounds) t)) e' _ (eq_sym Hbounds_sane);
      (** ** instantiation of original evar *)
      Hevar : final_e_evar = InterpEta (t:=Arrow _ _) Syntax.interp_op e_final_newtype v';
      (** ** side conditions (boundedness) *)
      Hv1 : Bounds.is_bounded_by input_bounds (cast_back_flat_const v');
      Hv2 : intros_interp_flat_type_Prop (fun v => PointedProp.to_prop (InterpSideConditions e_pre_pkg v));
    }.

  Definition PipelineCorrect
             (side_conditions : PipelineSideConditions)
    : Bounds.is_bounded_by given_output_bounds (fZ (cast_back_flat_const v'))
      /\ cast_back_flat_const final_e_evar = fZ (cast_back_flat_const v').
  Proof using All.
    destruct side_conditions
      as [He He_unnatize_for_wf Hwf Hpost_wf_pre_bounds Hpost Hpost_correct Hbounds_relax Hbounds_sane Hbounds_sane_refl Hevar Hv1 Hv2].
    cbv [b e' e_final_newtype e e_pre_pkg e_pkg] in *.
    destruct evars as [b e' e_final_newtype e e_pre_pkg e_pkg];
      clear evars.
    destruct rexpr_sig as [? Hrexpr]; clear rexpr_sig.
    assert (Hwf' : Wf e)
      by (apply (proj1 (@Wf_ExprEta'_iff _ _ _ e));
          eapply reflect_Wf;
          [ .. | intros; split; [ eapply He_unnatize_for_wf | rewrite <- Hwf; apply trueify_true ] ];
          auto using base_type_eq_semidec_is_dec, op_beq_bl).
    clear Hwf He_unnatize_for_wf.
    symmetry in Hpost_correct.
    subst; cbv [proj1_sig] in *.
    rewrite eq_InterpEta, <- Hrexpr.
    pose proof (introsP_interp_flat_type Hv2) as Hv2'.
    eapply PostWfPipelineCorrect in Hpost_correct; [ | solve [ eauto ].. ].
    rewrite !@InterpPreWfPipeline in Hpost_correct.
    unshelve eapply relax_output_bounds; try eassumption; [].
    match goal with
    | [ |- context[Interp (@eq_rect ?A ?x ?P ?k ?y ?pf) ?v] ]
      => rewrite (@ap_transport A P _ x y pf (fun t e => Interp e v) k)
    end.
    rewrite <- transport_pp, concat_Vp; simpl.
    apply Hpost_correct.
  Qed.
End with_round_up_list.

(** ** Assembling the Pipeline, in Ltac *)
(** The tactic [refine_with_pipeline_correct] uses the
    [PipelineCorrect] lemma to create side-conditions.  It assumes the
    goal is in exactly the form given in the conclusion of the
    [PipelineCorrect] lemma. *)
(** The [refine_PipelineSideConditions_constructor] subtactic splits
    up the [PipelineSideConditions] record goal into multiple
    subgoals, to be discharged by automation lower in this file
    ([solve_post_reified_side_conditions]). *)
Ltac refine_PipelineSideConditions_constructor :=
  lazymatch goal with
  | [ |- PipelineSideConditions ?opts ?evars _ ]
    => simple refine {| Hevar := _ |};
       cbv [b e' e_final_newtype e e_pre_pkg e_pkg proj1_sig]
  end.
Ltac refine_with_pipeline_correct opts :=
  lazymatch goal with
  | [ |- _ /\ ?castback ?fW = ?fZ ?arg ]
    => let lem := open_constr:(@PipelineCorrect _ opts _ _ _ _ _ _ {| e_pkg := _ |}) in
       simple refine (lem _ _);
       subst fW fZ
  end;
  [ eexists
  | refine_PipelineSideConditions_constructor ].

(** The tactic [solve_side_conditions] uses the above
    reduction-and-proving-equality tactics to prove the
    side-conditions of [PipelineCorrect].  The order must match with
    [PipelineCorrect].  Which tactic to use was chosen in the
    following way:

    - The default is [unify_abstract_vm_compute_rhs_reflexivity]

    - If the [RHS] is already in [vm_compute]d form, use
      [unify_abstract_rhs_reflexivity] (saves a needless [vm_compute] which would be a
      costly no-op)

    - If the proof needs to be transparent and there are no evars and
      you want the user to see the fully [vm_compute]d term on error,
      use [vm_compute; reflexivity]

    - If the user should see an unreduced term and you're proving [_ =
      true], use [abstract vm_cast_no_check (eq_refl true)]

    - If you want to preserve binder names, use [unify_abstract_cbv_rhs_reflexivity]

    The other choices are tactics that are specialized to the specific
    side-condition for which they are used (reification, boundedness
    of input, reduction of [Interp], renaming). *)
Ltac solve_post_reified_side_conditions :=
  [>
   (** ** pre-wf pipeline *)
   unify_abstract_vm_compute_rhs_reflexivity |
   (** ** reflective wf side-condition 1 *)
   unify_abstract_vm_compute_rhs_reflexivity |
   (** ** reflective wf side-condition 2 *)
   unify_abstract_vm_compute_rhs_reflexivity |
   (** ** post-wf-pre-bounds-pipeline *)
   unify_abstract_vm_compute_rhs_reflexivity |
   (** ** post-wf pipeline *)
   unify_abstract_vm_compute_rhs_reflexivity |
   (** ** post-wf pipeline gives [Some _] *)
   unify_abstract_rhs_reflexivity |
   (** ** computed output bounds are not looser than the given output bounds *)
   (** we do subst and we don't [vm_compute] first because we want to
       get an error message that displays the bounds *)
   subst_let; clear; abstract vm_cast_no_check (eq_refl true) |
   (** ** types computed from given output bounds are the same as types computed from computed output bounds *)
   (** N.B. the proof must be exactly [eq_refl] because it's used in a
            later goal and needs to reduce *)
   subst_let; clear; vm_compute; reflexivity |
   (** ** removal of a cast across the equality proof above *)
   unify_abstract_compute_rhs_reflexivity |
   (** ** unfolding of [interp] constants *)
   unify_abstract_cbv_interp_rhs_reflexivity |
   (** ** boundedness of inputs *)
   abstract handle_bounds_from_hyps |
   (** ** boundedness side-condition *)
   handle_boundedness_side_condition ].
Ltac solve_side_conditions :=
  [>
   (** ** reification *)
   do_reify |
   .. ];
  solve_post_reified_side_conditions.

(** ** The Entire Pipeline *)
(** The [do_reflective_pipeline] tactic solves a goal of the form that
    is described at the top of this file, and is the public interface
    of this file. *)
Ltac do_reflective_pipeline opts :=
  refine_with_pipeline_correct opts; solve_side_conditions.
Notation default_PipelineOptions := default_PipelineOptions.