mirror of
https://github.com/c-cube/sidekick.git
synced 2025-12-07 11:45:41 -05:00
refactor(sat): fix proof construction at level 0; improve preprocessing
This commit is contained in:
parent
4d3b278754
commit
7b15ea7280
1 changed files with 150 additions and 116 deletions
|
|
@ -48,6 +48,7 @@ module Make(Plugin : PLUGIN)
|
||||||
val pa : var -> t
|
val pa : var -> t
|
||||||
val na : var -> t
|
val na : var -> t
|
||||||
module AVec : Vec_sig.S with type elt := t
|
module AVec : Vec_sig.S with type elt := t
|
||||||
|
module ATbl : CCHashtbl.S with type key = t
|
||||||
end = struct
|
end = struct
|
||||||
include Int_id.Make()
|
include Int_id.Make()
|
||||||
let[@inline] neg i = (i lxor 1)
|
let[@inline] neg i = (i lxor 1)
|
||||||
|
|
@ -59,6 +60,7 @@ module Make(Plugin : PLUGIN)
|
||||||
let[@inline] var a = Var0.of_int_unsafe (a lsr 1)
|
let[@inline] var a = Var0.of_int_unsafe (a lsr 1)
|
||||||
let[@inline] na v = (((v:var:>int) lsl 1) lor 1)
|
let[@inline] na v = (((v:var:>int) lsl 1) lor 1)
|
||||||
module AVec = VecSmallInt
|
module AVec = VecSmallInt
|
||||||
|
module ATbl = CCHashtbl.Make(CCInt)
|
||||||
end
|
end
|
||||||
type atom = Atom0.t
|
type atom = Atom0.t
|
||||||
|
|
||||||
|
|
@ -81,6 +83,9 @@ module Make(Plugin : PLUGIN)
|
||||||
module AVec = Atom0.AVec
|
module AVec = Atom0.AVec
|
||||||
(** Vector of atoms *)
|
(** Vector of atoms *)
|
||||||
|
|
||||||
|
module ATbl = Atom0.ATbl
|
||||||
|
(** Hashtbl of atoms *)
|
||||||
|
|
||||||
module CVec = Clause0.CVec
|
module CVec = Clause0.CVec
|
||||||
(** Vector of clauses *)
|
(** Vector of clauses *)
|
||||||
|
|
||||||
|
|
@ -118,6 +123,7 @@ module Make(Plugin : PLUGIN)
|
||||||
a_form: lit Vec.t;
|
a_form: lit Vec.t;
|
||||||
(* TODO: store watches in clauses instead *)
|
(* TODO: store watches in clauses instead *)
|
||||||
a_watched: Clause0.CVec.t Vec.t;
|
a_watched: Clause0.CVec.t Vec.t;
|
||||||
|
a_proof_lvl0: proof_step ATbl.t; (* atom -> proof for it to be true at level 0 *)
|
||||||
|
|
||||||
stat_n_atoms: int Stat.counter;
|
stat_n_atoms: int Stat.counter;
|
||||||
|
|
||||||
|
|
@ -145,6 +151,7 @@ module Make(Plugin : PLUGIN)
|
||||||
a_form=Vec.create();
|
a_form=Vec.create();
|
||||||
a_watched=Vec.create();
|
a_watched=Vec.create();
|
||||||
a_seen=Bitvec.create();
|
a_seen=Bitvec.create();
|
||||||
|
a_proof_lvl0=ATbl.create 16;
|
||||||
stat_n_atoms;
|
stat_n_atoms;
|
||||||
c_store={
|
c_store={
|
||||||
c_lits=Vec.create();
|
c_lits=Vec.create();
|
||||||
|
|
@ -200,6 +207,9 @@ module Make(Plugin : PLUGIN)
|
||||||
let[@inline] level self a = Var.level self (var a)
|
let[@inline] level self a = Var.level self (var a)
|
||||||
let[@inline] marked_both self a = marked self a && marked self (neg a)
|
let[@inline] marked_both self a = marked self a && marked self (neg a)
|
||||||
|
|
||||||
|
let proof_lvl0 self a = ATbl.get self.a_proof_lvl0 a
|
||||||
|
let set_proof_lvl0 self a p = ATbl.replace self.a_proof_lvl0 a p
|
||||||
|
|
||||||
let pp self fmt a = Lit.pp fmt (lit self a)
|
let pp self fmt a = Lit.pp fmt (lit self a)
|
||||||
|
|
||||||
let pp_a self fmt v =
|
let pp_a self fmt v =
|
||||||
|
|
@ -437,7 +447,7 @@ module Make(Plugin : PLUGIN)
|
||||||
let alloc_var_uncached_ ?default_pol:(pol=true) self (form:lit) : var =
|
let alloc_var_uncached_ ?default_pol:(pol=true) self (form:lit) : var =
|
||||||
let {v_count; v_of_lit; v_level; v_heap_idx; v_weight;
|
let {v_count; v_of_lit; v_level; v_heap_idx; v_weight;
|
||||||
v_reason; v_seen; v_default_polarity; stat_n_atoms;
|
v_reason; v_seen; v_default_polarity; stat_n_atoms;
|
||||||
a_is_true; a_seen; a_watched; a_form; c_store=_;
|
a_is_true; a_seen; a_watched; a_form; c_store=_; a_proof_lvl0=_;
|
||||||
} = self in
|
} = self in
|
||||||
|
|
||||||
let v_idx = v_count in
|
let v_idx = v_count in
|
||||||
|
|
@ -878,93 +888,140 @@ module Make(Plugin : PLUGIN)
|
||||||
if i >= Array.length arr then []
|
if i >= Array.length arr then []
|
||||||
else Array.to_list (Array.sub arr i (Array.length arr - i))
|
else Array.to_list (Array.sub arr i (Array.length arr - i))
|
||||||
|
|
||||||
(* Eliminates atom duplicates in clauses *)
|
(* get/build the proof for [a], which must be an atom true at level0.
|
||||||
let eliminate_duplicates store clause : clause =
|
This uses a cache *)
|
||||||
let trivial = ref false in
|
let rec proof_of_atom_lvl0_ (self:t) (a:atom) : proof_step =
|
||||||
let duplicates = ref [] in
|
assert (Atom.is_true self.store a && Atom.level self.store a = 0);
|
||||||
let res = ref [] in
|
|
||||||
Clause.iter store clause
|
|
||||||
~f:(fun a ->
|
|
||||||
if Atom.marked store a then duplicates := a :: !duplicates
|
|
||||||
else (
|
|
||||||
Atom.mark store a;
|
|
||||||
res := a :: !res
|
|
||||||
));
|
|
||||||
List.iter
|
|
||||||
(fun a ->
|
|
||||||
if Atom.marked_both store a then trivial := true;
|
|
||||||
Store.clear_var store (Atom.var a))
|
|
||||||
!res;
|
|
||||||
if !trivial then (
|
|
||||||
raise Trivial
|
|
||||||
) else if !duplicates = [] then (
|
|
||||||
clause
|
|
||||||
) else (
|
|
||||||
let removable = Clause.removable store clause in
|
|
||||||
Clause.make_l store ~removable !res
|
|
||||||
(Clause.proof_step store clause)
|
|
||||||
)
|
|
||||||
|
|
||||||
(* TODO: do it in place in a vec? *)
|
begin match Atom.proof_lvl0 self.store a with
|
||||||
(* Partition literals for new clauses, into:
|
| Some p -> p
|
||||||
|
| None ->
|
||||||
|
|
||||||
|
let p =
|
||||||
|
match Atom.reason self.store a with
|
||||||
|
| None -> assert false
|
||||||
|
| Some Decision -> assert false (* no decisions at level0 *)
|
||||||
|
| Some (Bcp c2 | Bcp_lazy (lazy c2)) ->
|
||||||
|
let steps = ref [] in
|
||||||
|
(* recurse, so we get the whole level-0 resolution graph *)
|
||||||
|
Clause.iter self.store c2
|
||||||
|
~f:(fun a2 ->
|
||||||
|
if not (Var.equal (Atom.var a) (Atom.var a2)) then (
|
||||||
|
let p2 = proof_of_atom_lvl0_ self (Atom.neg a2) in
|
||||||
|
steps := p2 :: !steps;
|
||||||
|
));
|
||||||
|
|
||||||
|
Proof.emit_redundant_clause
|
||||||
|
(Iter.return (Atom.lit self.store a))
|
||||||
|
~hyps:Iter.(cons (Clause.proof_step self.store c2) (of_list !steps))
|
||||||
|
self.proof
|
||||||
|
in
|
||||||
|
|
||||||
|
Atom.set_proof_lvl0 self.store a p; (* put in cache *)
|
||||||
|
p
|
||||||
|
end
|
||||||
|
|
||||||
|
(* Preprocess clause, by doing the following:
|
||||||
|
|
||||||
|
- Partition literals for new clauses, into:
|
||||||
- true literals (maybe makes the clause trivial if the lit is proved true at level 0)
|
- true literals (maybe makes the clause trivial if the lit is proved true at level 0)
|
||||||
- unassigned literals, yet to be decided
|
- unassigned literals, yet to be decided
|
||||||
- false literals (not suitable to watch, those at level 0 can be removed from the clause)
|
- false literals (not suitable to watch, those at level 0 can be removed from the clause)
|
||||||
|
|
||||||
and order them as such in the result.
|
and order them as such in the result.
|
||||||
Also, removes literals that are false at level0, and returns a proof for
|
|
||||||
their removal.
|
|
||||||
*)
|
|
||||||
let partition_atoms_ store atoms : atom list * proof_step list =
|
|
||||||
let proofs = ref [] in
|
|
||||||
let add_proof p = proofs := p :: !proofs in
|
|
||||||
|
|
||||||
let rec loop trues unassigned falses i =
|
- Also, removes literals that are false at level0, and returns a proof for
|
||||||
if i >= Array.length atoms then (
|
their removal.
|
||||||
trues @ unassigned @ falses
|
- Also, removes duplicates.
|
||||||
) else (
|
*)
|
||||||
let a = atoms.(i) in
|
let preprocess_clause_ (self:t) (c:Clause.t) : Clause.t =
|
||||||
|
let store = self.store in
|
||||||
|
let res0_proofs = ref [] in (* steps of resolution at level 0 *)
|
||||||
|
let add_proof_ p = res0_proofs := p :: !res0_proofs in
|
||||||
|
|
||||||
|
let trues = Vec.create () in
|
||||||
|
let unassigned = Vec.create() in
|
||||||
|
let falses = Vec.create() in
|
||||||
|
|
||||||
|
(* cleanup marks used to detect duplicates *)
|
||||||
|
let cleanup () =
|
||||||
|
Clause.iter store c
|
||||||
|
~f:(fun a -> Store.clear_var store (Atom.var a));
|
||||||
|
in
|
||||||
|
|
||||||
|
let consider_atom (a:atom) : unit =
|
||||||
|
if not (Atom.marked store a) then (
|
||||||
|
Atom.mark store a;
|
||||||
|
if Atom.marked_both store a then (
|
||||||
|
raise Trivial
|
||||||
|
);
|
||||||
|
|
||||||
if Atom.is_true store a then (
|
if Atom.is_true store a then (
|
||||||
let lvl = Atom.level store a in
|
let lvl = Atom.level store a in
|
||||||
if lvl = 0 then
|
if lvl = 0 then (
|
||||||
(* Atom true at level 0 gives a trivially true clause *)
|
(* Atom true at level 0 gives a trivially true clause *)
|
||||||
raise_notrace Trivial
|
raise Trivial
|
||||||
else
|
);
|
||||||
(a :: trues) @ unassigned @ falses @
|
Vec.push trues a;
|
||||||
(arr_to_list atoms (i + 1))
|
|
||||||
) else if Atom.is_false store a then (
|
) else if Atom.is_false store a then (
|
||||||
let lvl = Atom.level store a in
|
let lvl = Atom.level store a in
|
||||||
if lvl = 0 then (
|
if lvl = 0 then (
|
||||||
match Atom.reason store a with
|
|
||||||
| Some (Bcp cl | Bcp_lazy (lazy cl)) ->
|
|
||||||
(* Atom var false at level 0 can be eliminated from the clause,
|
(* Atom var false at level 0 can be eliminated from the clause,
|
||||||
but we need to kepp in mind that we used another clause to simplify it. *)
|
but we need to kepp in mind that we used another clause to simplify it. *)
|
||||||
add_proof (Clause.proof_step store cl);
|
Log.debugf 50
|
||||||
loop trues unassigned falses (i + 1)
|
(fun k->k"(@[sat.preprocess-clause.resolve-away-lvl0@ %a@])"
|
||||||
| None | Some Decision ->
|
(Atom.debug store) a);
|
||||||
(* The var must have a reason, and it cannot be a decision/assumption,
|
|
||||||
since its level is 0. *)
|
let p = proof_of_atom_lvl0_ self (Atom.neg a) in
|
||||||
assert false
|
add_proof_ p;
|
||||||
) else (
|
) else (
|
||||||
loop trues unassigned (a::falses) (i + 1)
|
Vec.push falses a;
|
||||||
)
|
)
|
||||||
) else (
|
) else (
|
||||||
loop trues (a::unassigned) falses (i + 1)
|
Vec.push unassigned a
|
||||||
)
|
);
|
||||||
)
|
)
|
||||||
in
|
in
|
||||||
|
|
||||||
let l = loop [] [] [] 0 in
|
begin
|
||||||
l, !proofs
|
try
|
||||||
|
Clause.iter store c ~f:consider_atom;
|
||||||
|
cleanup()
|
||||||
|
with e ->
|
||||||
|
cleanup();
|
||||||
|
raise e
|
||||||
|
end;
|
||||||
|
|
||||||
|
(* merge all atoms together *)
|
||||||
|
let atoms = trues in
|
||||||
|
Vec.append ~into:atoms unassigned;
|
||||||
|
Vec.append ~into:atoms falses;
|
||||||
|
let atoms = Vec.to_array atoms in
|
||||||
|
|
||||||
|
if Array.length atoms = Clause.n_atoms store c then (
|
||||||
|
(* no change except in the order of literals *)
|
||||||
|
assert (!res0_proofs = []);
|
||||||
|
Clause.make_a store atoms
|
||||||
|
~removable:(Clause.removable store c)
|
||||||
|
(Clause.proof_step store c)
|
||||||
|
) else (
|
||||||
|
(* some atoms were removed by resolution with level-0 clauses *)
|
||||||
|
Log.debugf 30 (fun k->k"(@[sat.add-clause.resolved-lvl-0@ :into [@[%a@]]@])"
|
||||||
|
(Atom.debug_a store) atoms);
|
||||||
|
let proof =
|
||||||
|
let lits =
|
||||||
|
Clause.atoms_a store c
|
||||||
|
|> Iter.of_array
|
||||||
|
|> Iter.map (Atom.lit store)
|
||||||
|
in
|
||||||
|
Proof.emit_redundant_clause lits
|
||||||
|
~hyps:Iter.(cons (Clause.proof_step self.store c) (of_list !res0_proofs))
|
||||||
|
self.proof
|
||||||
|
in
|
||||||
|
Clause.make_a
|
||||||
|
store atoms proof
|
||||||
|
~removable:(Clause.removable store c)
|
||||||
|
)
|
||||||
|
|
||||||
(* Making a decision.
|
|
||||||
Before actually creatig a new decision level, we check that
|
|
||||||
all propagations have been done and propagated to the theory,
|
|
||||||
i.e that the theoriy state indeed takes into account the whole
|
|
||||||
stack of literals
|
|
||||||
i.e we have indeed reached a propagation fixpoint before making
|
|
||||||
a new decision *)
|
|
||||||
let new_decision_level st =
|
let new_decision_level st =
|
||||||
assert (st.th_head = AVec.size st.trail);
|
assert (st.th_head = AVec.size st.trail);
|
||||||
assert (st.elt_head = AVec.size st.trail);
|
assert (st.elt_head = AVec.size st.trail);
|
||||||
|
|
@ -976,7 +1033,6 @@ module Make(Plugin : PLUGIN)
|
||||||
|
|
||||||
Atom clause is attached (to its watching lits) when it is first added,
|
Atom clause is attached (to its watching lits) when it is first added,
|
||||||
either because it is assumed or learnt.
|
either because it is assumed or learnt.
|
||||||
|
|
||||||
*)
|
*)
|
||||||
let attach_clause (self:t) c =
|
let attach_clause (self:t) c =
|
||||||
let store = self.store in
|
let store = self.store in
|
||||||
|
|
@ -991,7 +1047,7 @@ module Make(Plugin : PLUGIN)
|
||||||
(* Backtracking.
|
(* Backtracking.
|
||||||
Used to backtrack, i.e cancel down to [lvl] excluded,
|
Used to backtrack, i.e cancel down to [lvl] excluded,
|
||||||
i.e we want to go back to the state the solver was in
|
i.e we want to go back to the state the solver was in
|
||||||
when decision level [lvl] was created. *)
|
after decision level [lvl] was created and fully propagated. *)
|
||||||
let cancel_until (self:t) lvl =
|
let cancel_until (self:t) lvl =
|
||||||
let store = self.store in
|
let store = self.store in
|
||||||
assert (lvl >= 0);
|
assert (lvl >= 0);
|
||||||
|
|
@ -1457,23 +1513,6 @@ module Make(Plugin : PLUGIN)
|
||||||
cancel_until self (max cr.cr_backtrack_lvl 0);
|
cancel_until self (max cr.cr_backtrack_lvl 0);
|
||||||
record_learnt_clause ~pool:self.clauses_learnt self confl cr
|
record_learnt_clause ~pool:self.clauses_learnt self confl cr
|
||||||
|
|
||||||
(* Return a clause like [c], but maybe with a proof involving [proofs] *)
|
|
||||||
let maybe_resolve_with_ (self:t) (c:clause) (proofs:proof_step list) : clause =
|
|
||||||
if proofs=[] then c
|
|
||||||
else (
|
|
||||||
let proof =
|
|
||||||
let lits =
|
|
||||||
Clause.atoms_a self.store c
|
|
||||||
|> Iter.of_array
|
|
||||||
|> Iter.map (Atom.lit self.store)
|
|
||||||
in
|
|
||||||
Proof.emit_redundant_clause lits ~hyps:(Iter.of_list proofs) self.proof
|
|
||||||
in
|
|
||||||
Clause.make_a
|
|
||||||
self.store (Clause.atoms_a self.store c) proof
|
|
||||||
~removable:(Clause.removable self.store c)
|
|
||||||
)
|
|
||||||
|
|
||||||
(* Add a new clause, simplifying, propagating, and backtracking if
|
(* Add a new clause, simplifying, propagating, and backtracking if
|
||||||
the clause is false in the current trail *)
|
the clause is false in the current trail *)
|
||||||
let add_clause_ (self:t) ~pool (init:clause) : unit =
|
let add_clause_ (self:t) ~pool (init:clause) : unit =
|
||||||
|
|
@ -1483,24 +1522,16 @@ module Make(Plugin : PLUGIN)
|
||||||
trivial clause could end up being not decided on, which is a bug. *)
|
trivial clause could end up being not decided on, which is a bug. *)
|
||||||
Clause.iter store init ~f:(fun x -> insert_var_order self (Atom.var x));
|
Clause.iter store init ~f:(fun x -> insert_var_order self (Atom.var x));
|
||||||
try
|
try
|
||||||
let c = eliminate_duplicates store init in
|
(* preprocess to remove dups, sort literals, etc. *)
|
||||||
Log.debugf 30 (fun k -> k "(@[sat.dups-removed@ %a@])" (Clause.debug store) c);
|
let clause = preprocess_clause_ self init in
|
||||||
let atoms, proofs = partition_atoms_ store (Clause.atoms_a store c) in
|
|
||||||
let clause =
|
|
||||||
(* just update order of atoms *)
|
|
||||||
let c_atoms = Clause.atoms_a store c in
|
|
||||||
List.iteri (fun i a -> c_atoms.(i) <- a) atoms;
|
|
||||||
c
|
|
||||||
in
|
|
||||||
(* update proof as well *)
|
|
||||||
let clause = maybe_resolve_with_ self clause proofs in
|
|
||||||
|
|
||||||
assert (Clause.removable store clause = Clause.removable store init);
|
assert (Clause.removable store clause = Clause.removable store init);
|
||||||
|
|
||||||
Log.debugf 5 (fun k->k "(@[sat.new-clause@ @[<hov>%a@]@])" (Clause.debug store) clause);
|
Log.debugf 5 (fun k->k "(@[sat.new-clause@ @[<hov>%a@]@])" (Clause.debug store) clause);
|
||||||
|
let atoms = Clause.atoms_a self.store clause in
|
||||||
match atoms with
|
match atoms with
|
||||||
| [] ->
|
| [||] ->
|
||||||
report_unsat self @@ US_false clause
|
report_unsat self @@ US_false clause
|
||||||
| [a] ->
|
| [|a|] ->
|
||||||
cancel_until self 0;
|
cancel_until self 0;
|
||||||
if Atom.is_false store a then (
|
if Atom.is_false store a then (
|
||||||
(* cannot recover from this *)
|
(* cannot recover from this *)
|
||||||
|
|
@ -1513,7 +1544,9 @@ module Make(Plugin : PLUGIN)
|
||||||
add_clause_to_vec_ ~pool self clause;
|
add_clause_to_vec_ ~pool self clause;
|
||||||
enqueue_bool self a ~level:0 (Bcp clause)
|
enqueue_bool self a ~level:0 (Bcp clause)
|
||||||
)
|
)
|
||||||
| a::b::_ ->
|
| _ ->
|
||||||
|
let a = atoms.(0) in
|
||||||
|
let b = atoms.(1) in
|
||||||
add_clause_to_vec_ ~pool self clause;
|
add_clause_to_vec_ ~pool self clause;
|
||||||
if Atom.is_false store a then (
|
if Atom.is_false store a then (
|
||||||
(* Atom need to be sorted in decreasing order of decision level,
|
(* Atom need to be sorted in decreasing order of decision level,
|
||||||
|
|
@ -1524,10 +1557,12 @@ module Make(Plugin : PLUGIN)
|
||||||
) else (
|
) else (
|
||||||
attach_clause self clause;
|
attach_clause self clause;
|
||||||
if Atom.is_false store b &&
|
if Atom.is_false store b &&
|
||||||
not (Atom.is_true store a) &&
|
not (Atom.has_value store a) then (
|
||||||
not (Atom.is_false store a) then (
|
(* unit, propagate [a] *)
|
||||||
let lvl = List.fold_left (fun m a -> max m (Atom.level store a)) 0 atoms in
|
let lvl = Array.fold_left (fun m a -> max m (Atom.level store a)) 0 atoms in
|
||||||
cancel_until self lvl;
|
cancel_until self lvl;
|
||||||
|
Log.debugf 50 (fun k->k"(@[sat.add-clause.propagate@ %a@ :lvl %d@])"
|
||||||
|
(Atom.debug store) a lvl);
|
||||||
enqueue_bool self a ~level:lvl (Bcp clause)
|
enqueue_bool self a ~level:lvl (Bcp clause)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -2160,22 +2195,21 @@ module Make(Plugin : PLUGIN)
|
||||||
|
|
||||||
(* resolve against the root cause of [a] being false *)
|
(* resolve against the root cause of [a] being false *)
|
||||||
let resolve_with_a (a:atom) : unit =
|
let resolve_with_a (a:atom) : unit =
|
||||||
assert (Atom.is_false self.store a);
|
assert (Atom.is_false self.store a && Atom.level self.store a=0);
|
||||||
if not (Var.marked self.store (Atom.var a)) then (
|
if not (Var.marked self.store (Atom.var a)) then (
|
||||||
|
Log.debugf 50 (fun k->k"resolve lvl0 :atom %a" (Atom.debug self.store) a);
|
||||||
AVec.push to_unmark a;
|
AVec.push to_unmark a;
|
||||||
Var.mark self.store (Atom.var a);
|
Var.mark self.store (Atom.var a);
|
||||||
|
|
||||||
match Atom.reason self.store a with
|
let p = proof_of_atom_lvl0_ self (Atom.neg a) in
|
||||||
| None -> assert false
|
lvl0 := p :: !lvl0;
|
||||||
| Some Decision -> res := a :: !res (* keep assumption *)
|
|
||||||
| Some (Bcp c2 | Bcp_lazy (lazy c2)) ->
|
|
||||||
lvl0 := Clause.proof_step self.store c2 :: !lvl0;
|
|
||||||
(* NOTE: no need to recurse, we just need to depend on [c2]
|
|
||||||
and its proof will be required later *)
|
|
||||||
)
|
)
|
||||||
in
|
in
|
||||||
|
|
||||||
Clause.iter self.store c ~f:resolve_with_a;
|
Clause.iter self.store c
|
||||||
|
~f:(fun a ->
|
||||||
|
if Atom.level self.store a = 0 then resolve_with_a a
|
||||||
|
);
|
||||||
AVec.iter to_unmark ~f:(fun a -> Var.unmark self.store (Atom.var a));
|
AVec.iter to_unmark ~f:(fun a -> Var.unmark self.store (Atom.var a));
|
||||||
AVec.clear to_unmark;
|
AVec.clear to_unmark;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue