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