@@ -447,6 +447,166 @@ let t_ecall_hoare_bwd ((cttpt, _) : proofterm * form) (tc : tcenv1) =
447447 EcPhlAuto. t_auto ?conv:None ; (* Kill the conseq from the call rule *)
448448 ] tc
449449
450+ (* -------------------------------------------------------------------- *)
451+ (* Backward ecall on bdHoare/phoare goals (ecall without ->>).
452+ *
453+ * Mirrors t_ecall_hoare_bwd but for a bdHoare goal and a bdHoare/phoare
454+ * contract. We abstract program-variable arguments of the contract pterm
455+ * into fresh local idents, dispatch to [t_call None ctt] (which routes
456+ * via [t_call]'s (FbdHoareF, FbdHoareS) arm to [t_bdhoare_call]), then
457+ * re-generalize over the abstracted idents in the residual goals.
458+ *
459+ * The prefix is split from the call with [t_bdhoare_seq] under a fixed
460+ * trivial probability split (f1=f2=1%r, g1=g2=0%r, pR=true). That split
461+ * only discharges the resulting bound side-conditions when the goal — and
462+ * the contract — are lossless [= 1%r]; both are checked up-front so that
463+ * out-of-scope goals are rejected cleanly rather than failing deep inside
464+ * [t_call]. The residual goal handed back to the user is the prefix, lifted
465+ * to a lossless bdHoare goal so that further ecalls keep dispatching here.
466+ *)
467+ let t_ecall_bdhoare_bwd ((cttpt , _ ) : proofterm * form ) (tc : tcenv1 ) =
468+ let hyps = FApi. tc1_hyps tc in
469+ let env = EcEnv.LDecl. toenv hyps in
470+ let concl = destr_bdHoareS (FApi. tc1_goal tc) in
471+ let m = fst (concl.bhs_m) in
472+ let call, _ = pf_last_call !! tc concl.bhs_s in
473+
474+ (* The trivial probability split below (f1=f2=1, g1=g2=0) only discharges
475+ * the bound side-conditions when the goal is [= 1%r] (lossless). For any
476+ * other comparison or bound the construction would fail opaquely deep in
477+ * [t_call]/[apply]; reject up-front with an explanatory message instead. *)
478+ if concl.bhs_cmp <> FHeq || not (f_equal (bhs_bd concl).inv f_r1) then
479+ tc_error !! tc
480+ " backward ecall on phoare goals is only supported for lossless `= 1%%r' goals" ;
481+
482+ let pvs = PT. collect_pvars_from_pt cttpt in
483+ let ids, _, pvs_as_inv, subst = abstract_pvs hyps [m] pvs in
484+
485+ let cttpt =
486+ let pt_head, pt_args =
487+ match cttpt with
488+ | PTApply { pt_head; pt_args } -> (pt_head, pt_args)
489+ | _ -> assert false in
490+ let pt_args = List. map (PT. subst_pv_pt_arg env subst) pt_args in
491+ PTApply { pt_head; pt_args } in
492+
493+ let cttpt, ctt = EcLowGoal.LowApply. check `Elim cttpt (`Hyps (hyps, !! tc)) in
494+
495+ let ctt =
496+ EcReduction. h_red_opt EcReduction. full_red hyps ctt
497+ |> odfl ctt in
498+
499+ let ids_subst =
500+ List. fold_left2
501+ (fun s (id , _ ) pv -> EcSubst. add_flocal s id (inv_of_inv pv))
502+ EcSubst. empty ids pvs_as_inv in
503+
504+ let hf = destr_bdHoareF ctt in
505+
506+ (* The suffix call subgoal is synthesized with bound [= 1%r]; only a
507+ * lossless [= 1%r] contract applies to it. *)
508+ if hf.bhf_cmp <> FHeq || not (f_equal (bhf_bd hf).inv f_r1) then
509+ tc_error !! tc
510+ " backward ecall on phoare goals requires a lossless `= 1%%r' contract" ;
511+
512+ let fpre, fpost =
513+ (ss_inv_rebind (bhf_pr hf) m).inv, (ss_inv_rebind (bhf_po hf) m).inv
514+ in
515+
516+ let post =
517+ EcPhlCall. compute_hoare_call_post
518+ hyps m (fpre, POE. empty fpost) call
519+ (POE. empty (bhs_po concl).inv) in
520+ let post = EcSubst. subst_form ids_subst post in
521+
522+ (* Trivial probability split for FHeq with bound 1%r (lossless body):
523+ pR = true, f1=f2=1%r, g1=g2=0%r. Yields:
524+ - cond_phi : f_hoareS pre s1 post (user-level prefix, as hoare)
525+ - condf1 : pre s1 true 1%r (lossless prefix, trivial for det. code)
526+ - condf2 : phi s2 bhs_po 1%r (suffix bdhoare, target of t_call)
527+ - condg1 : pre s1 false 0%r (trivial)
528+ - condbd : 1*1+0*0 = bd (trivial when bd=1%r)
529+ - condnm : forall r1 r2 ... (trivial) *)
530+ let seq_info =
531+ let phi = { m; inv = post } in
532+ let pR = { m; inv = f_true } in
533+ let f1 = { m; inv = f_r1 } in
534+ let f2 = { m; inv = f_r1 } in
535+ let g1 = { m; inv = f_r0 } in
536+ let g2 = { m; inv = f_r0 } in
537+ (phi, pR, f1, f2, g1, g2)
538+ in
539+
540+ let tc = EcPhlSeq. t_bdhoare_seq (GapBefore cpos1_last) seq_info tc in
541+ (* Subgoal order from t_bdhoare_seq (with f1≠0, f2≠0, g1=0):
542+ [cond_phi; condf1; condf2; condg1; condbd; condnm]
543+ We apply the exists+intros+t_call pipeline to condf2 (the suffix).
544+ The intros chain leaves a single bdhoare goal on which t_call produces
545+ two subgoals (contract obligation + conseq); we then dispatch via
546+ t_seqsub. *)
547+ let condf2_tactic =
548+ FApi. t_seqs [
549+ t_hr_exists_intro_r pvs_as_inv;
550+ t_hr_exists_elim_r ~bound: (List. length ids);
551+ t_intros_i (List. fst ids);
552+ FApi. t_seqsub
553+ (EcPhlCall. t_call None ctt)
554+ [ EcLowGoal.Apply. t_apply_bwd_hi ~dpe: true cttpt;
555+ EcPhlAuto. t_auto ?conv:None ; ];
556+ ]
557+ in
558+
559+ (* t_bdhoare_seq_r auto-closes condnm via t_try, so the count is 5 (or 6 if
560+ that auto fails). Use t_onalli with an index-based dispatcher to be
561+ robust to either count. Order (0-indexed):
562+ 0 -> cond_phi (HOARE prefix): lift to BDHOARE [pre ==> phi] FHeq 1%r
563+ via t_hoareS_conseq_bdhoare. This is sound under losslessness of
564+ the prefix — which the user proves as part of the residual goal.
565+ Subsequent ecalls then dispatch through our bdhoare arm and accept
566+ phoare contracts.
567+ 2 -> condf2 (suffix call dance)
568+ others -> auto (trivial bound conditions). *)
569+ let tc =
570+ FApi. t_onalli
571+ (fun i ->
572+ if i = 0 then EcPhlConseq. t_hoareS_conseq_bdhoare
573+ else if i = 2 then condf2_tactic
574+ else EcPhlAuto. t_auto ?conv:None )
575+ tc
576+ in
577+
578+ tc
579+
580+ (* -------------------------------------------------------------------- *)
581+ let process_ecall_bdhoare
582+ (dir : APT.pdirection )
583+ (pterm : APT.pecall )
584+ (tc : tcenv1 )
585+ =
586+ if dir <> `Backward then
587+ tc_error !! tc " forward ecall on bdHoare/phoare goals is not supported" ;
588+
589+ let (ctt_path, ctt_tvi, ctt_args) = pterm in
590+ let hyps = FApi. tc1_hyps tc in
591+ let concl = destr_bdHoareS (FApi. tc1_goal tc) in
592+
593+ (* Type-check contract (lemma) - apply it fully to find the bdHoare contract *)
594+ let ptenv = PT. ptenv_of_penv (LDecl. push_active_ss concl.bhs_m hyps) !! tc in
595+ let contract = PT. process_pterm ptenv (APT. FPNamed (ctt_path, ctt_tvi)) in
596+ let contract, _ = PT. process_pterm_args_app contract ctt_args in
597+ let contract = PT. apply_pterm_to_max_holes hyps contract in
598+
599+ assert (PT. can_concretize contract.PT. ptev_env);
600+ let contract = PT. concretize contract in
601+
602+ let call, _ = pf_last_call !! tc concl.bhs_s in
603+
604+ check_contract_type
605+ ~phoare: true ~noexn: false ~loc: (L. loc ctt_path) ~name: (L. unloc ctt_path)
606+ !! tc hyps (`Single call) (snd contract);
607+
608+ t_ecall_bdhoare_bwd contract tc
609+
450610(* -------------------------------------------------------------------- *)
451611let process_ecall_hoare
452612 (dir : APT.pdirection )
@@ -624,7 +784,12 @@ let process_ecall
624784 tc_error !! tc " cannot provide a side for Hoare goals" ;
625785 process_ecall_hoare dir pterm tc
626786
787+ | FbdHoareS _ ->
788+ if Option. is_some oside then
789+ tc_error !! tc " cannot provide a side for bdHoare/phoare goals" ;
790+ process_ecall_bdhoare dir pterm tc
791+
627792 | FequivS _ ->
628793 process_ecall_equiv dir oside pterm tc
629794
630- | _ -> tc_error_noXhl ~kinds: [`Hoare `Stmt ; `Equiv `Stmt ] !! tc
795+ | _ -> tc_error_noXhl ~kinds: [`Hoare `Stmt ; `PHoare `Stmt ; ` Equiv `Stmt ] !! tc
0 commit comments