Skip to content

Commit 7bb6031

Browse files
committed
typing: defer failed-application classification (perf)
Operator selection reported every rejected candidate through EcUnify.classify_application, which peels the argument list one type at a time, head-normalising via `ty_hnorm (ty_subst (Tuni.subst (UniEnv.assubst ue)) _)` at each step. UniEnv.assubst is subst_of_uf, materialising the whole unifier by folding over UF.domain (every univar in the unienv), so each candidate cost O(arity * |unienv|) instead of the O(arity) incremental union-find of the previous single unification. Overload resolution rejects candidates constantly on the *success* path (any application of an overloaded name tries and drops the non-matching instances), so this classification ran pervasively even though the diagnostics it produces are only ever consumed when an application fails outright and an error is raised. On a large formosa-keccak module this turned a ~8s require into ~54s. Defer the classification: - EcUnify: select_outcome's KO now carries a `... Lazy.t`. select_op decides OK/KO with the cheap single `unify top texpected` and drops KO without ever forcing it; select_op_failures forces the thunks, for the error path only. classify_application is unchanged. - EcTyping.gen_select_op: the OK selection uses select_op; the failure list is a lazy closure forced only by tyerror_noop when building the UnappliedOp / UnknownVarOrOp error. select_pv likewise decides with a single unification and defers its own failure classification. Diagnostics are byte-identical (candidate order preserved). Keccak require back to baseline; tests/op-application-errors.ec passes; unit (84/84) and stdlib (128/128) green.
1 parent e07ffa0 commit 7bb6031

3 files changed

Lines changed: 85 additions & 56 deletions

File tree

src/ecTyping.ml

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -324,22 +324,28 @@ let select_local env (qs,s) =
324324
else None
325325

326326
(* -------------------------------------------------------------------- *)
327-
(* The program variable [name] if it applies to [psig]/[retty], else the
328-
reason it does not. *)
327+
(* The program variable [name] if it applies to [psig]/[retty], else (lazily)
328+
the reason it does not. *)
329329
let select_pv env side name ue tvi (psig, retty)
330-
: (_ * ty * EcUnify.unienv) list * (appcand * EcUnify.op_failure) list =
330+
: (_ * ty * EcUnify.unienv) list * (appcand * EcUnify.op_failure) list Lazy.t =
331331
if tvi <> None
332-
then ([], [])
332+
then ([], lazy [])
333333
else
334334
try
335335
let (pv, ty) = EcEnv.Var.lookup_progvar ?side name env in
336336
let subue = UE.copy ue in
337-
match EcUnify.classify_application env subue ty psig retty with
338-
| None -> ([(pv, ty, subue)], [])
339-
| Some f ->
340-
let pv0 = match pv with `Var p -> p | `Proj (p, _) -> p in
341-
([], [(`Pv (pv0, ty), f)])
342-
with EcEnv.LookupFailure _ -> ([], [])
337+
let expected = toarrow psig (ofdfl (fun () -> UE.fresh subue) retty) in
338+
match EcUnify.unify env subue ty expected with
339+
| () -> ([(pv, ty, subue)], lazy [])
340+
| exception EcUnify.UnificationFailure _ ->
341+
([], lazy (
342+
let subue = UE.copy ue in
343+
match EcUnify.classify_application env subue ty psig retty with
344+
| None -> []
345+
| Some f ->
346+
let pv0 = match pv with `Var p -> p | `Proj (p, _) -> p in
347+
[(`Pv (pv0, ty), f)]))
348+
with EcEnv.LookupFailure _ -> ([], lazy [])
343349

344350
(* -------------------------------------------------------------------- *)
345351
module OpSelect = struct
@@ -374,11 +380,9 @@ let gen_select_op
374380
(ue : EcUnify.unienv)
375381
(psig : EcTypes.dom * EcTypes.ty option)
376382

377-
: OpSelect.gopsel list * OpSelect.opfailure list
383+
: OpSelect.gopsel list * OpSelect.opfailure list Lazy.t
378384
=
379385

380-
let opfailures = ref [] in
381-
382386
let fpv me (pv, ty, ue) : OpSelect.gopsel =
383387
(`Pv (me, pv), ty, ue, (pv :> opmatch))
384388

@@ -417,30 +421,22 @@ let gen_select_op
417421
|> Option.to_list
418422
else [] in
419423

424+
let pvfailures = ref (lazy []) in
425+
420426
let ops () : OpSelect.gopsel list =
421-
let outcomes =
422-
EcUnify.select_op_outcomes ~filter:ue_filter tvi env name ue psig in
423-
let ops =
424-
List.filter_map
425-
(function EcUnify.OK r -> Some r | EcUnify.KO _ -> None) outcomes in
426-
opfailures := !opfailures @
427-
List.filter_map
428-
(function
429-
| EcUnify.KO (p, inst, ty, f) -> Some (`Op (p, inst, ty), f)
430-
| EcUnify.OK _ -> None)
431-
outcomes;
427+
let ops = EcUnify.select_op ~filter:ue_filter tvi env name ue psig in
432428
let ops = opsc |> ofold (fun opsc -> List.mbfilter (by_scope opsc)) ops in
433429
let ops = match List.mbfilter by_current ops with [] -> ops | ops -> ops in
434430
let ops = match List.mbfilter by_tc ops with [] -> ops | ops -> ops in
435431
(List.map fop ops)
436432

437433
and pvs () : OpSelect.gopsel list =
438-
let me, (pvs, pvfailures) =
434+
let me, (pvs, pvf) =
439435
match EcEnv.Memory.get_active_ss env, actonly with
440-
| None, true -> (None, ([], []))
436+
| None, true -> (None, ([], lazy []))
441437
| me , _ -> ( me, select_pv env me name ue tvi psig)
442438
in
443-
opfailures := !opfailures @ pvfailures;
439+
pvfailures := pvf;
444440
List.map (fpv me) pvs
445441
in
446442

@@ -460,7 +456,15 @@ let gen_select_op
460456
else
461457
select [locals; pvs; ops]
462458
in
463-
(selected, !opfailures)
459+
460+
let opfailures = lazy (
461+
Lazy.force !pvfailures
462+
@ List.map
463+
(fun (p, inst, ty, f) -> (`Op (p, inst, ty), f))
464+
(EcUnify.select_op_failures ~filter:ue_filter tvi env name ue psig)
465+
) in
466+
467+
(selected, opfailures)
464468

465469
(* -------------------------------------------------------------------- *)
466470
let select_exp_op env mode opsc name ue tvi psig =
@@ -475,10 +479,11 @@ let select_form_op env mode ~forcepv opsc name ue tvi psig =
475479
(* -------------------------------------------------------------------- *)
476480
(* [UnappliedOp] when candidates of that name exist but fail, else
477481
[UnknownVarOrOp]. *)
478-
let tyerror_noop env loc name esig retty (opfailures : OpSelect.opfailure list) =
479-
match opfailures with
482+
let tyerror_noop env loc name esig retty
483+
(opfailures : OpSelect.opfailure list Lazy.t) =
484+
match Lazy.force opfailures with
480485
| [] -> tyerror loc env (UnknownVarOrOp (name, esig))
481-
| _ -> tyerror loc env (UnappliedOp (name, esig, retty, opfailures))
486+
| opfailures -> tyerror loc env (UnappliedOp (name, esig, retty, opfailures))
482487

483488
(* -------------------------------------------------------------------- *)
484489
let select_proj env opsc name ue tvi recty =

src/ecUnify.ml

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ type op_instance = (EcIdent.t * ty) list
342342

343343
type select_outcome =
344344
| OK of select_result
345-
| KO of EcPath.path * op_instance * ty * op_failure
345+
| KO of (EcPath.path * op_instance * ty * op_failure) Lazy.t
346346
(* operator path, partial instantiation, declared operator type, reason *)
347347

348348
(* -------------------------------------------------------------------- *)
@@ -434,17 +434,31 @@ let select_op_outcomes
434434
filter oppath op && filter_on_tvi op
435435
in
436436

437-
let select (path, op) =
437+
let mk_ok (path, op) tip tvs top subue =
438+
let bd =
439+
match op.D.op_kind with
440+
| OB_nott nt ->
441+
let substnt () =
442+
let xs = List.map (snd_map (ty_subst tip)) nt.D.ont_args in
443+
let es = e_subst tip in
444+
let bd = es nt.D.ont_body in
445+
(xs, bd)
446+
in Some (Lazy.from_fun substnt)
447+
448+
| _ -> None
449+
450+
in OK ((path, tvs), top, subue, bd)
451+
in
452+
453+
let classify (path, op) =
438454
let subue = UniEnv.copy ue in
439455

440456
let (tip, tvs) = UniEnv.openty_r subue op.D.op_tparams tvi in
441457
let top = ty_subst tip op.D.op_ty in
442458

443459
let resolve ty = ty_subst (Tuni.subst (UniEnv.assubst subue)) ty in
444460

445-
let failure = classify_application env subue top psig retty in
446-
447-
match failure with
461+
match classify_application env subue top psig retty with
448462
| Some f ->
449463
let instance =
450464
List.combine op.D.op_tparams tvs
@@ -453,21 +467,24 @@ let select_op_outcomes
453467
| Tunivar _ -> None
454468
| _ -> Some (tp, resolve tv))
455469
in
456-
KO (path, instance, op.D.op_ty, f)
457-
| None ->
458-
let bd =
459-
match op.D.op_kind with
460-
| OB_nott nt ->
461-
let substnt () =
462-
let xs = List.map (snd_map (ty_subst tip)) nt.D.ont_args in
463-
let es = e_subst tip in
464-
let bd = es nt.D.ont_body in
465-
(xs, bd)
466-
in Some (Lazy.from_fun substnt)
470+
(path, instance, op.D.op_ty, f)
467471

468-
| _ -> None
472+
(* Unreachable: [select] only builds a [KO] once the fast unification has
473+
already rejected the candidate, and the two solve the same problem. *)
474+
| None -> assert false
475+
in
469476

470-
in OK ((path, tvs), top, subue, bd)
477+
let select (path, op) =
478+
let subue = UniEnv.copy ue in
479+
480+
let (tip, tvs) = UniEnv.openty_r subue op.D.op_tparams tvi in
481+
let top = ty_subst tip op.D.op_ty in
482+
483+
let texpected = tfun_expected subue ?retty psig in
484+
485+
match unify env subue top texpected with
486+
| () -> mk_ok (path, op) tip tvs top subue
487+
| exception UnificationFailure _ -> KO (lazy (classify (path, op)))
471488

472489
in
473490
List.map select (EcEnv.Op.all ~check:filter ~name env)
@@ -482,3 +499,15 @@ let select_op
482499
List.filter_map
483500
(function OK r -> Some r | KO _ -> None)
484501
(select_op_outcomes ?hidden ?filter tvi env name ue sig_)
502+
503+
(* -------------------------------------------------------------------- *)
504+
(* The candidates of that name that fail to apply, with the reason why. *)
505+
let select_op_failures
506+
?hidden ?filter (tvi : tvar_inst option)
507+
(env : EcEnv.env) (name : qsymbol) (ue : unienv)
508+
(sig_ : ty list * ty option)
509+
: (EcPath.path * op_instance * ty * op_failure) list
510+
=
511+
List.filter_map
512+
(function KO lz -> Some (Lazy.force lz) | OK _ -> None)
513+
(select_op_outcomes ?hidden ?filter tvi env name ue sig_)

src/ecUnify.mli

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,6 @@ type op_failure =
5050
(* Constrained type parameters of an operator (those bound while applying it). *)
5151
type op_instance = (EcIdent.t * ty) list
5252

53-
type select_outcome =
54-
| OK of select_result
55-
| KO of EcPath.path * op_instance * ty * op_failure
56-
(* operator path, partial instantiation, declared operator type, reason *)
57-
5853
(* [None] if [top] applies to [psig] (and [retty]), updating [ue]; otherwise
5954
[Some] of the first argument/result/arity failure. *)
6055
val classify_application :
@@ -70,12 +65,12 @@ val select_op :
7065
-> dom * ty option
7166
-> select_result list
7267

73-
val select_op_outcomes :
68+
val select_op_failures :
7469
?hidden:bool
7570
-> ?filter:(path -> operator -> bool)
7671
-> tvi
7772
-> EcEnv.env
7873
-> qsymbol
7974
-> unienv
8075
-> dom * ty option
81-
-> select_outcome list
76+
-> (path * op_instance * ty * op_failure) list

0 commit comments

Comments
 (0)