1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858(**************************************************************************)(* This file is part of the Codex semantics library. *)(* *)(* Copyright (C) 2013-2025 *)(* CEA (Commissariat à l'énergie atomique et aux énergies *)(* alternatives) *)(* *)(* you can redistribute it and/or modify it under the terms of the GNU *)(* Lesser General Public License as published by the Free Software *)(* Foundation, version 2.1. *)(* *)(* It is distributed in the hope that it will be useful, *)(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)(* GNU Lesser General Public License for more details. *)(* *)(* See the GNU Lesser General Public License version 2.1 *)(* for more details (enclosed in the file LICENSE). *)(* *)(**************************************************************************)moduleIn_bits=Units.In_bits(** This file corresponds to the symbolic expression domain described
in [POPL'23] and [PLDI'24]. *)moduleLog=Tracelog.Make(structletcategory="Domains.Term_domain"end);;[@@@warning"-33"](* Terms.() gives spurious warning in 4.04.2 *)(* Should we use first-order formula (over-approximation) or horn
clauses (exact) to the SMT solver. *)letoption_translate:[`First_order|`Horn]=`Horn;;(* How should we pretty-print terms: only their value (easier to read),
symbolically (better when there are issues with evaluation), or print both
(more complete debugging info). *)typepretty_terms=|Value|Symbolic|Both|Relationletoption_pretty_terms=refValueletset_pretty_termsx=option_pretty_terms:=x(* Option to introduce a fresh variable at every fixpoint
step. Mandatory if we do not push/pop.
Results: faster on almost all benchmarks, except papabench;
probably the variable order is better that way for bdd
construction. It also fixes a precision issues in t072. *)letoption_fresh_variable_every_time=(notTerm_based.Propagation.option_push_and_pop)||(notTerm_based.Propagation.option_push_and_pop_on_restart)||true;;(* If true, whenever the result of a computation is a constant, we
simplify the constraint using the constant. This allows to detect
equality more often, and to have fewer variables.
It shows a small gain on the benchmarks. *)letoption_semantic_simplification_constraints=truemoduleTC=Operator.Function_symbolmoduleTerms_SMT=Terms.SmtmoduleMake(Terms:Terms.Sig.TERMS)(Domain:Term_based_sig.Domain_SwithmoduleTerms=Terms):Sig.BASE_WITH_INTEGERwithtypebinary=TC.binaryTerms.tandtypeinteger=TC.integerTerms.tandtypeboolean=TC.booleanTerms.tandtypeenum=TC.enumTerms.t=structletname()="Term_domain("^(Domain.name)^")";;letunique_id()=Sig.Fresh_id.fresh@@name();;type'aidentifier='aTerms.tmoduleTypes=structtypebinary=TC.binaryidentifiertypeinteger=TC.integeridentifiertypeboolean=TC.booleanidentifiertypeenum=TC.enumidentifierendincludeTypesmoduleIdentifier=structletprettyfmtx=Format.fprintffmt"%a"Terms.prettyx(* This is used by the above domains to decide whether the values
are the same, and if they should be serialized. It is important
to serialize if the constraints are equal and the domains are
different, else we loose information about conditions. *)let_equalab=letres=Terms.equalabinres;;(* Actually, this check is done really often, and this
approximation yields the correct result most of the
time. Moreover, if we do not detect equality here, we will
probably do it in fixpoint_step or nondet. Using this
implementation gave me a 10-20% speedup in execution time. *)letequalab=a==b;;(* This does not check for equality of the [domain] fields. It is not
necessary for the only current use case: a map indexed by [t]s to
implement focusing. *)letcompare=Terms.comparelethash=Terms.hashletto_int=Terms.hashendmoduleBinary=structtypet=binaryincludeIdentifierendmoduleInteger=structtypet=integerincludeIdentifierendmoduleBoolean=structtypet=booleanincludeIdentifierendmoduleEnum=structtypet=enumincludeIdentifierend(**************** Context ****************)typecontext={unique_id:int;(* Used for SMT queries. Could be computed form context_. *)mutablepath_condition:Boolean.t;level:int;(* The signature of operations like biadd does not return a new context,
so we change it inplace for these operations. This imperative signature may be useful
e.g. to use the optimisations in Apron. *)mutabledomain:Domain.t};;(* Pair of constraints *)moduleTC_Binary=structtypet=TC.binaryidentifierincludeIdentifierendmoduleTC_Integer=structtypet=TC.integeridentifierincludeIdentifierendmoduleTC_Boolean=structtypet=TC.booleanidentifierincludeIdentifierendmoduleTC_Enum=structtypet=TC.enumidentifierincludeIdentifierendmodulePair(Key:Datatype_sig.S)=structtypet=Key.t*Key.tletcompare(a1,b1)(a2,b2)=letc=Key.comparea1a2inifc<>0thencelseKey.compareb1b2letequal(a1,b1)(a2,b2)=Key.equala1a2&&Key.equalb1b2lethash(a,b)=Hashing.hash2(Key.hasha)(Key.hashb)endmoduleMapPair=structmoduleKey=structtype'keyt='keyidentifierletto_int=Terms.hash(* This implementation is correct, as here physical equality
implies type equality. *)letpolyeq:'at->'bt->('a,'b)PatriciaTree.cmp=funab->letopenPatriciaTreeinif(Obj.magica==Obj.magicb)then(Obj.magicEq)elseDiff;;endmoduleValue2=structtype('key,'value)t='keyTerms.tendmoduleMap2=PatriciaTree.MakeHeterogeneousMap(Key)(Value2)moduleValue1=structtype('key,'value)t='keyTerms.tMap2.tendmoduleMap1=PatriciaTree.MakeHeterogeneousMap(Key)(Value1)typet=unitMap2.tMap1.tletfind:'aKey.t->'aKey.t->t->'aKey.t=funkey1key2map1->letmap2=Map1.findkey1map1inMap2.findkey2map2letmem:'aKey.t->'aKey.t->t->bool=funkey1key2map1->matchMap1.findkey1map1with|exceptionNot_found->false|map2->Map2.memkey2map2;;letadd:'aKey.t->'aKey.t->'aKey.t->t->t=funkey1key2valuemap1->letmap2=matchMap1.findkey1map1with|exceptionNot_found->Map2.singletonkey2value|map2->Map2.addkey2valuemap2inMap1.addkey1map2map1;;letempty=Map1.emptyendmoduleCommon=structmoduleKey=structincludeTerms.Anyletto_int=get_id_intendmoduleMap=PatriciaTree.MakeMap(Key)moduleSet=PatriciaTree.MakeSet(Key)end(* Set of pairs of constraints. *)moduleSetPair=structopenCommontypet=Set.tMap.tletmemkey1key2map1=matchMap.findkey1map1with|exceptionNot_found->false|map2->Set.memkey2map2;;letaddkey1key2valuemap1=letset2=matchMap.findkey1map1with|exceptionNot_found->Set.singletonkey2|set2->Set.addkey2set2inMap.addkey1set2map1;;letempty=Map.emptyend(**************** Context ****************)moduleContext=structtypet=contextletlevelctx=ctx.levelletcopyx={xwithpath_condition=x.path_condition;domain=x.domain}letassignctxnewctx=ctx.domain<-newctx.domain;ctx.path_condition<-newctx.path_condition(* Having a ConsLeftEmpty/ConsRightEmpty here could maybe allow
getting rid of the empty constructor (that would become a
special case of phi instead). But we would still have to
create new name for the new phi variable, and not reuse the
left or the right term.
TODO: Think how this could allow the implementation of sum
and option types in the base language with a "normal"
(without option types, but with phi that can have empty
arguments) SSA for the integer language. If that works, it
would be very nice. *)type'amapping=|EmptyMapping:unitmapping|ConsSame:'aTerms.t*'bmapping->('aidentifier*'b)mapping|ConsDifferent:'aTerms.t*'aTerms.t*'bmapping->('aidentifier*'b)mapping;;(* MAYBE: it could be further simplified into a set of pairs, but
then tuple order might change between fixpoint iterations (but
possibly not, if we create fresh variables with ids always in
the same order.
However, the handling differs between nondet and fixpoint step,
so possibly not a good idea.
One easy simplification would be to get rid of ConsSame. *)type'ain_tuple={mapping:'amapping;}[@@unboxed]type'ain_acc=bool*'ain_tupletype'aout_tuple={phi:MapPair.t}type('a,'b)result=Result:bool*'somein_tuple*(t->'someout_tuple->'a*'bout_tuple)->('a,'b)resulttypeempty_tuple=unitletempty_tuple()={mapping=EmptyMapping;}endopenContext(* Initialize all the constants so that we know they are in, and we do not need to change
the context for them (we can just return the identifier). *)letroot_domain()=letdomain=Domain.topinletdomain=Domain.Boolean_Forward.true_domainTerms.Build.Boolean.true_inletdomain=Domain.Boolean_Forward.false_domainTerms.Build.Boolean.false_inletdomain=Domain.boolean_emptydomainTerms.Build.Boolean.emptyinletdomain=Domain.Integer_Forward.iconstZ.zerodomainTerms.Build.Integer.zeroinletdomain=Domain.Integer_Forward.iconstZ.onedomainTerms.Build.Integer.oneinletdomain=Domain.integer_emptydomainTerms.Build.Integer.emptyindomain;;letunique_id_ref=ref0;;letget_unique_id()=incrunique_id_ref;!unique_id_ref;;letroot_context()=letctx={unique_id=get_unique_id();path_condition=Terms.Build.Boolean.true_;level=0;domain=root_domain()}inctx;;letcontext_prettyfmtctx=Format.fprintffmt"Context{@[<hv>id=%d,@ %a@]}"ctx.unique_idDomain.prettyctx.domain(**************** Transfer functions ****************)(* We use the state monad using domain as the state. *)type'am=Domain.t->'a*Domain.tletreturnx=fundomain->x,domainlet(>>=)mf=fundom1->let(res,dom2)=mdom1in(fres)dom2(* Special version of the run function of the State monad. *)letrunctxf=letdom=ctx.domaininlet(res:'aidentifier),dom=fdomin(* Codex_log.feedback "run: constraint = %a" Terms.pretty (Domain.to_constraint dom); *)(* Codex_log.feedback "run: created identifier %a" Identifier.pretty res; *)ctx.domain<-dom;res;;(* include Operator.Builtin.Make(Types)(Context) *)(* This optimization is very important, else in particular we build
complex formula that are actually empty. *)(* Note: We can just build the constrain here, because the value for
the boolean constants is already evaluated. *)letopt_booleanxdomain=matchDomain.Query.booleandomainxwith|Lattices.Quadrivalent.True->Terms.Build.Boolean.true_,domain|Lattices.Quadrivalent.False->Terms.Build.Boolean.false_,domain|Lattices.Quadrivalent.Bottom->Terms.Build.Boolean.empty,domain|_->x,domain;;letar0constrainfdomaindomain=constrain,fdomaindomainconstrain;;letar0_boolean=ar0letar0_integerctx=ar0letar0_binary=ar0letar0_enum=ar0leticonstk=ar0(Terms.Build.Integer.iconstk)(Domain.Integer_Forward.iconstk);;letbiconst~sizek=ar0(Terms.Build.Binary.biconst~sizek)(Domain.Binary_Forward.biconst~sizek);;letopt_integerxdomain=ifoption_semantic_simplification_constraintsthenletvalue=Domain.Query.integerdomainxinmatchDomain.Query.Integer_Lattice.is_singletonvaluewith|None->x,domain|Somek->iconstkdomainelsex,domain;;letopt_binary~sizexdomain=ifoption_semantic_simplification_constraintsthenletvalue=Domain.Query.binary~sizedomainxinmatchDomain.Query.Binary_Lattice.is_singleton~sizevaluewith|None->x,domain|Somek->biconst~sizekdomainelsex,domain;;letopt_enumxdomain=x,domain;;letar1fconstrainfdomainadomain=letres=fconstrainainletdom=fdomaindomainaresinres,dom;;let_ar1_integer_integer=ar1letar1_integer_integerctxfconstrainfdomaina=runctx@@(ar1fconstrainfdomaina>>=opt_integer);;let_ar1_binary_binary~size=ar1letar1_binary_binary~sizectxfconstrainfdomaina=runctx@@(ar1fconstrainfdomaina>>=opt_binary~size);;letar1_boolean_binary~sizectxfconstrainfdomaina=runctx@@(ar1fconstrainfdomaina>>=opt_binary~size);;letar1_boolean_booleanctxfconstrainfdomaina=runctx@@(ar1fconstrainfdomaina>>=opt_boolean);;letar1_enum_booleanctxfconstrainfdomaina=runctx@@(ar1fconstrainfdomaina>>=opt_boolean);;letar2ctxfconstrainfdomainabdom=letres=fconstrainabinletdom=fdomaindomabresinres,dom;;letar2'ctxfabdom=let(constrain,dom)=fdomabinconstrain,dom;;letar2_binary_binary_binary'~sizectxfab=runctx@@(ar2'ctxfab>>=opt_binary~size)letar2_binary_binary_binary~sizectxfconstrainfdomainab=runctx@@(ar2ctxfconstrainfdomainab>>=opt_binary~size)letar2_integer_integer_integerctxfconstrainfdomainab=runctx@@(ar2ctxfconstrainfdomainab>>=opt_integer)letar2_boolean_boolean_booleanctxfconstrainfdomainab=(ar2ctxfconstrainfdomainab>>=opt_boolean);;letar2_integer_integer_boolean=(ifoption_semantic_simplification_constraintsthenar2_boolean_boolean_booleanelsear2);;letar2_binary_binary_boolean~size=ifoption_semantic_simplification_constraintsthenar2_boolean_boolean_booleanelsear2;;letar2_integer_integer_booleanctxfconstrainfdomainab=runctx@@ar2_integer_integer_booleanctxfconstrainfdomainab;;letar2_binary_binary_boolean~sizectxfconstrainfdomainab=runctx@@ar2_binary_binary_boolean~sizectxfconstrainfdomainab;;letar2_boolean_boolean_booleanctxfconstrainfdomainab=runctx@@ar2_boolean_boolean_booleanctxfconstrainfdomainab;;(* It is more precise to assume individual simple constraints; this function does that. *)letrecassumectxcondposdomain=(* Codex_log.feedback "rec assuming %b %a %a" pos Terms.pretty cond Domain.pretty domain; *)letjoinabposdomain=Codex_log.warning"This was never tested";letdom1=assumectxaposdomaininletdom2=assumectxbposdomaininlettup=Immutable_array.emptyinmatchdom1,dom2with|None,dom|dom,None->dom|Somedoma,Somedomb->Some(Domain.nondet~doma~tupa:tup~domb~tupb:tup~tupres:tup)inletinterabposdomain=matchassumectxaposdomainwith|None->None|Somedomain->assumectxbposdomaininmatchpos,condwith|_,Terms.(Bool{term=T1{tag=TC.Not;a=Terms.Bool_asa}})->assumectxa(notpos)domain|true,Terms.(Bool{term=T2{tag=TC.And;a=Terms.Bool_asa;b=Terms.Bool_asb}})->interabposdomain(* Same, but we cannot factorize with OCaml 4.05 *)|false,Terms.(Bool{term=T2{tag=TC.Or;a=Terms.Bool_asa;b=Terms.Bool_asb}})->interabposdomain|true,Terms.(Bool{term=T2{tag=TC.Or;a=Terms.Bool_asa;b=Terms.Bool_asb}})->joinabposdomain|false,Terms.(Bool{term=T2{tag=TC.And;a=Terms.Bool_asa;b=Terms.Bool_asb}})->joinabposdomain|_->matchpos,Domain.Query.Boolean_Lattice.to_quadrivalent@@Domain.Query.booleandomaincondwith|(_,Lattices.Quadrivalent.Bottom)|true,Lattices.Quadrivalent.False|false,Lattices.Quadrivalent.True->None|true,Lattices.Quadrivalent.True|false,Lattices.Quadrivalent.False->Somedomain|(true,Lattices.Quadrivalent.Top)->Domain.assumedomaincond|(false,Lattices.Quadrivalent.Top)->(* Need to evaluate the condition before propagation. *)letncond=Terms.Build.Boolean.notcondinletdomain=Domain.Boolean_Forward.notdomaincondncondinDomain.assumedomainncondletassumectxcond=(* Codex_log.feedback "Term_domain.newassume %b %b" (cond = Bottom) (ctx.domain = None); *)(* Codex_log.feedback "Term_domain.newassume: non bottom case %a" Terms.pretty constraincond; *)letdomain=assumectxcondtruectx.domaininmatchdomainwith|None->None|Somedomain->begin(* Test in case the condition became bottom after the propagation *)matchDomain.Query.Boolean_Lattice.to_quadrivalent@@Domain.Query.booleandomaincondwith|Lattices.Quadrivalent.(False|Bottom)->None|Lattices.Quadrivalent.(True|Top)->Some{domain;unique_id=get_unique_id();path_condition=Terms.Build.Boolean.(&&)condctx.path_condition;level=ctx.level}end;;(* Eventually, we did not assume simple constraints individually, and it performs better. *)letassumectxcond=(* Codex_log.feedback "Term_domain.newassume %b %b" (cond = Bottom) (ctx.domain = None); *)(* Codex_log.feedback "Term_domain.newassume: non bottom case %a" Terms.pretty constraincond; *)matchDomain.Query.Boolean_Lattice.to_quadrivalent@@Domain.Query.booleanctx.domaincondwith|Lattices.Quadrivalent.True->Somectx|Lattices.Quadrivalent.(False|Bottom)->None|Lattices.Quadrivalent.Top->beginmatchDomain.assumectx.domaincondwith|None->None|Somedomain->beginmatchDomain.Query.Boolean_Lattice.to_quadrivalent@@Domain.Query.booleandomaincondwith|Lattices.Quadrivalent.(False|Bottom)->None|Lattices.Quadrivalent.(True|Top)->Some{domain;unique_id=get_unique_id();path_condition=Terms.Build.Boolean.(&&)condctx.path_condition;level=ctx.level}endend;;letimperative_assumectxcond=matchassumectxcondwith|None->raiseSig.Bottom(* Should not happen, the condition
should only limit fresh values, not
create a bottom. *)|Somenewctx->beginctx.domain<-newctx.domain;ctx.path_condition<-Terms.Build.Boolean.(&&)condctx.path_conditionendmoduleBoolean_Forward=structlet(||)ctx=ar2_boolean_boolean_booleanctxTerms.Build.Boolean.(||)Domain.Boolean_Forward.(||)let(&&)ctx=ar2_boolean_boolean_booleanctxTerms.Build.Boolean.(&&)Domain.Boolean_Forward.(&&)letnotctx=ar1_boolean_booleanctxTerms.Build.Boolean.notDomain.Boolean_Forward.not(* Note: we avoid creating those every time. *)lettrue_ctx=runctx@@ar0_booleanTerms.Build.Boolean.true_Domain.Boolean_Forward.true_lettrue_=letres=Terms.Build.Boolean.true_infunctx->resletfalse_ctx=runctx@@ar0_booleanTerms.Build.Boolean.false_Domain.Boolean_Forward.false_letfalse_=letres=Terms.Build.Boolean.false_infunctx->resendmoduleInteger_Forward'=structletilectx=ar2_integer_integer_booleanctxTerms.Build.Integer.ileDomain.Integer_Forward.ileletieqctx=ar2_integer_integer_booleanctxTerms.Build.Integer.ieqDomain.Integer_Forward.ieqleticonstkctx=runctx@@ar0_integerctx(Terms.Build.Integer.iconstk)(Domain.Integer_Forward.iconstk)letonectx=iconstZ.onectxletone=letres=Terms.Build.Integer.oneinfunctx->resletzeroctx=iconstZ.zeroctxletzero=letres=Terms.Build.Integer.zeroinfunctx->resletixorctx=ar2_integer_integer_integerctxTerms.Build.Integer.ixorDomain.Integer_Forward.ixorletiorctx=ar2_integer_integer_integerctxTerms.Build.Integer.iorDomain.Integer_Forward.iorletiandctx=ar2_integer_integer_integerctxTerms.Build.Integer.iandDomain.Integer_Forward.iandletishrctx=ar2_integer_integer_integerctxTerms.Build.Integer.ishrDomain.Integer_Forward.ishrletishlctx=ar2_integer_integer_integerctxTerms.Build.Integer.ishlDomain.Integer_Forward.ishlletimodctx=ar2_integer_integer_integerctxTerms.Build.Integer.imodDomain.Integer_Forward.imodletidivctx=ar2_integer_integer_integerctxTerms.Build.Integer.idivDomain.Integer_Forward.idivletimulctx=ar2_integer_integer_integerctxTerms.Build.Integer.imulDomain.Integer_Forward.imulletiaddctx=ar2_integer_integer_integerctxTerms.Build.Integer.iaddDomain.Integer_Forward.iaddletisubctx=ar2_integer_integer_integerctxTerms.Build.Integer.isubDomain.Integer_Forward.isubletitimeskctx=ar1_integer_integerctx(Terms.Build.Integer.itimesk)(Domain.Integer_Forward.itimesk)endmoduleInteger_Forward=structincludeInteger_Forward'(* Rewrite x + x as 2 * x. *)letiaddctxa'b'=matcha',b'with|a,b->ifa==bthenlettwo=Z.of_int2inlettwo=(Terms.Build.Integer.iconsttwo)inletconstrain=Terms.Build.Integer.imultwoainctx.domain<-Domain.Integer_Forward.imulctx.domaintwoaconstrain;constrainelseiaddctxa'b';;endmoduleEnum_Forward=structletcaseof~casectx=ar1_enum_booleanctx(Terms.Build.Enum.caseof~case)(Domain.Enum_Forward.caseof~case)letenum_const~casectx=runctx@@ar0_enum(Terms.Build.Enum.enum_const~case)(Domain.Enum_Forward.enum_const~case)endmoduleBinary_Forward=structmoduleNo_Rewrite=structletdefaultfconstrainfdomaindomab=letconstrain=fconstrainabinletdomain=fdomaindomabconstraininconstrain,domainletbiadd~size~flags=default(Terms.Build.Binary.biadd~size~flags)(Domain.Binary_Forward.biadd~size~flags)letbisub~size~flags=default(Terms.Build.Binary.bisub~size~flags)(Domain.Binary_Forward.bisub~size~flags)letbimul~size~flags=default(Terms.Build.Binary.bimul~size~flags)(Domain.Binary_Forward.bimul~size~flags)letbxor~size=default(Terms.Build.Binary.bxor~size)(Domain.Binary_Forward.bxor~size)letbor~size=default(Terms.Build.Binary.bor~size)(Domain.Binary_Forward.bor~size)letband~size=default(Terms.Build.Binary.band~size)(Domain.Binary_Forward.band~size)letbashr~size=default(Terms.Build.Binary.bashr~size)(Domain.Binary_Forward.bashr~size)letblshr~size=default(Terms.Build.Binary.blshr~size)(Domain.Binary_Forward.blshr~size)letbshl~size~flags=default(Terms.Build.Binary.bshl~size~flags)(Domain.Binary_Forward.bshl~size~flags)letbisdiv~size=default(Terms.Build.Binary.bisdiv~size)(Domain.Binary_Forward.bisdiv~size)letbiudiv~size=default(Terms.Build.Binary.biudiv~size)(Domain.Binary_Forward.biudiv~size)letbismod~size=default(Terms.Build.Binary.bismod~size)(Domain.Binary_Forward.bismod~size)letbiumod~size=default(Terms.Build.Binary.biumod~size)(Domain.Binary_Forward.biumod~size)letbextract~size~index~oldsizedoma=letconstrain=Terms.Build.Binary.bextract~size~index~oldsizeainletdomain=Domain.Binary_Forward.bextract~size~index~oldsizedomaconstraininconstrain,domainend(* Rewrite shifts + additions/soustractions into multiplication by a
constant. This is a common pattern in binary analysis. *)moduleRewrite=structincludeNo_Rewriteletextract=function|Terms.(Binary{term=T2{tag=TC.Bimul_size;a=Binary{term=T0{tag=TC.Biconst(_size2,k)}};b=Binary_asb}})->(k,b)|Terms.(Binary{term=T2{tag=TC.Bimul_size;a=Binary_asa;b=Binary{term=T0{tag=TC.Biconst(_size2,k)}}}})->(k,a)|x->(Z.one,x);;letextract_sum=function|Terms.(Binary{term=T2{tag=TC.Biadd_size;a=Binary_asa;b=Binary{term=T0{tag=TC.Biconst(_size2,k)};}}})->(a,k)|Terms.(Binary{term=T2{tag=TC.Biadd_size;a=Binary{term=T0{tag=TC.Biconst(_size2,k)}};b=Binary_asb;}})->(b,k)|x->(x,Z.zero)letrecbimul~size~flagsdomainab=matcha,bwith|Terms.(Binary{term=T0{tag=TC.Biconst(_size1,k1)}}),Terms.(Binary{term=T2{tag=TC.Bimul_size;a=Binary{term=T0{tag=TC.Biconst(_size2,k2)}};b=Binary_asx}})->bitimes~size~flagsdomain(Z.mulk1k2)x|Terms.(Binary{term=T2{tag=TC.Bimul_size;a=Binary{term=T0{tag=TC.Biconst(_size2,k2)}};b=Binary_asx}}),Terms.(Binary{term=T0{tag=TC.Biconst(_size1,k1)}})->bitimes~size~flagsdomain(Z.mulk1k2)x|_->No_Rewrite.bimul~size~flagsdomainabandbitimes~size~flagsdomainkx=letOperator.Flags.Bimul.{nsw;nuw}=Operator.Flags.Bimul.unpackflagsinifZ.equalkZ.zerothen(Terms.Build.Binary.biconst~sizeZ.zero),domainelseifZ.equalkZ.onethen(x,domain)elseletdefault()=letconstr=(Terms.Build.Binary.biconst~sizek)in(* optional, as domains should handle evaluation of constants themselves *)letdomain=Domain.Binary_Forward.biconst~sizekdomainconstrinNo_Rewrite.bimul~size~flagsdomainconstrxin(* Simplify k*(x' + c) into k*(x' - d), with 0<d<c, when possible.
Useful when the compiler does e.g. 16*(N+(2^28-1)) instead of
straightforwardly 16*(N-1), for optimization reasons. When that happens,
when dividing by 16 to get the index in an array, we have another
rewrite that returns N+(2^28-1) and that's actually incorrect, so
we want to return N-1 instead. It looks like bad engineering that
the other rewrite (in [bisdiv]) may be unsound, and that we have
to fix it here, though. *)(* The rewrite is based on the following theorem:
Let k,x,c be in [0,2^size - 1]. Assume k*c < 2^size. Let r be the
remainer in the Euclidean division of kc by 2^size. Then:
- If k divides (2^size-r) and r > 2^size - k*c, then by defining
d = (2^size-r) / k, we have
* k(x+c) congruent to k(x-d) modulo 2^size, and
* 0 < d < c.
- Otherwise, then there exists no d that satisfies the two
constraints above.
*)letx',c=extract_sumxinlettwo_pow_size=Z.shift_leftZ.one(size:>int)inletkc=Z.mulkcinletq,r=Z.ediv_remkctwo_pow_sizeinletcond1=Z.equalqZ.zeroinletcond2=Z.equalZ.zero@@Z.erem(Z.subtwo_pow_sizer)kinletcond3=Z.gtr(Z.subtwo_pow_sizekc)inifcond1&&cond2&&cond3thenletd=Z.divexact(Z.subtwo_pow_sizer)kinletconstr_d=Terms.Build.Binary.biconst~sizedinletdomain=Domain.Binary_Forward.biconst~sizeddomainconstr_dinletinterm,domain=No_Rewrite.bisub~size~flags:(Operator.Flags.Bisub.pack~nsw~nuw~nusw:false)domainx'constr_dinletconstr_k=Terms.Build.Binary.biconst~sizekinletdomain=Domain.Binary_Forward.biconst~sizekdomainconstr_kinNo_Rewrite.bimul~size~flagsdomainconstr_kintermelsedefault()(* XXX: need to check for signed overflow, otherwise unsound *)andbisdiv~sizedomainab=matchbwith|Terms.(Binary{term=T0{tag=TC.Biconst(_size,k)}})whennot@@Z.equalkZ.zero->letka,va=extractainifZ.equalZ.zero(Z.remkak)thenbitimes~size~flags:(Operator.Flags.Bimul.pack~nsw:false~nuw:false)domain(Z.divexactkak)vaelseNo_Rewrite.bisdiv~sizedomainab|_->No_Rewrite.bisdiv~sizedomainabandbiadd~(size:In_bits.t)~flagsdomainab=matcha,bwith|Terms.(Binary{term=T0{tag=TC.Biconst(_size,k)}}),_whenZ.equalkZ.zero->b,domain|_,Terms.(Binary{term=T0{tag=TC.Biconst(_size,k)}})whenZ.equalkZ.zero->a,domain|Terms.(Binary{term=T2{tag=TC.Bisub_size';a=Binary_asx;b=Binary{term=T0{tag=TC.Biconst(_size1,k1)}}}},Binary{term=T0{tag=TC.Biconst(_size2,k2)}})whenZ.equalk1k2->(* (x-k)+k -> x *)x,domain|Terms.(Binary{term=T0{tag=TC.Biconst(_size2,k2)}},Binary{term=T2{tag=TC.Bisub_size';a=Binary_asx;b=Binary{term=T0{tag=TC.Biconst(_size1,k1)}}}})whenZ.equalk1k2->(* k+(x-k) -> x *)x,domain|Terms.(Binary{term=T2{tag=TC.Biadd_size';a=Binary_asx;b=Binary{term=T0{tag=TC.Biconst(_size1,k1)}}}},Binary{term=T0{tag=TC.Biconst(_size2,k2)}})whenZ.equal(Z.rem(Z.addk1k2)(Z.shift_leftZ.one(size:>int)))Z.zero->(* (x+k1)+k2 -> x when k1+k2 = 0 mod 2^size *)x,domain|Terms.(Binary{term=T2{tag=TC.Biadd_size';a=Binary{term=T0{tag=TC.Biconst(_size1,k1)}};b=Binary_asx}},Binary{term=T0{tag=TC.Biconst(_size2,k2)}})whenZ.equal(Z.rem(Z.addk1k2)(Z.shift_leftZ.one(size:>int)))Z.zero->(* (k1+x)+k2 -> x when k1+k2 = 0 mod 2^size *)x,domain|Terms.(Binary{term=T2{tag=TC.Biadd_size;a=Binary{term=T0{tag=TC.Biconst(_size1,k1)}};b=Binary_asx}},Binary{term=T0{tag=TC.Biconst(_size2,k2)}})|Terms.(Binary{term=T0{tag=TC.Biconst(_size2,k2)}},Binary{term=T2{tag=TC.Biadd_size;a=Binary{term=T0{tag=TC.Biconst(_size1,k1)}};b=Binary_asx}})|Terms.(Binary{term=T2{tag=TC.Biadd_size;a=Binary_asx;b=Binary{term=T0{tag=TC.Biconst(_size1,k1)}}}},Binary{term=T0{tag=TC.Biconst(_size2,k2)}})|Terms.(Binary{term=T0{tag=TC.Biconst(_size2,k2)}},Binary{term=T2{tag=TC.Biadd_size;a=Binary_asx;b=Binary{term=T0{tag=TC.Biconst(_size1,k1)}}}})->letk=Z.addk2k1inletc=Terms.Build.Binary.biconst~sizekinletdomain=Domain.Binary_Forward.biconst~sizekdomaincinletres=Terms.Build.Binary.biadd~size~flagsxcinletdomain=Domain.Binary_Forward.biadd~size~flagsdomainxcresinres,domain|_->let(ka,va)=extractaand(kb,vb)=extractbinletOperator.Flags.Biadd.{nsw;nuw;nusw}=Operator.Flags.Biadd.unpackflagsinifTerms.equalvavbthenbitimes~size~flags:(Operator.Flags.Bimul.pack~nsw~nuw)domain(Z.addkakb)vaelseNo_Rewrite.biadd~size~flagsdomainabandbisub~size~flagsdomainab=matcha,bwith|_,Terms.(Binary{term=T0{tag=TC.Biconst(_size,k)}})whenZ.equalkZ.zero->a,domain(*
| Terms.(Binary{term=T2{tag=TC.Biadd _size;
a=Binary{term=T0{tag=TC.Biconst(_size1,k1)}};
b=Binary _ as x}},
Binary{term=T0{tag=TC.Biconst (_size2, k2)}})
when Z.equal k1 k2 ->
x, domain
| Terms.(Binary{term=T2{tag=TC.Biadd _size;
a=Binary _ as x;
b=Binary{term=T0{tag=TC.Biconst(_size1,k1)}}}},
Binary{term=T0{tag=TC.Biconst (_size2, k2)}})
when Z.equal k1 k2 ->
x, domain
*)|Terms.Binary{term=T2{tag=TC.Biadd_size;a=Binary_asx;b=Binary_asy}},_whenBinary.equalxb->y,domain|Terms.Binary{term=T2{tag=TC.Biadd_size;a=Binary_asx;b=Binary_asy}},_whenBinary.equalyb->x,domain|_->letOperator.Flags.Bisub.{nsw;nuw;nusw}=Operator.Flags.Bisub.unpackflagsinlet(ka,va)=extractaand(kb,vb)=extractbinifTerms.equalvavb(* k_a*x - k_b*x -> (k_a - k_b)*x *)thenbitimes~size~flags:(Operator.Flags.Bimul.pack~nsw~nuw)domain(Z.subkakb)vaelseifTerms.equalvab(* k_a*x - x -> (k_a - 1)*x *)thenbitimes~size~flags:(Operator.Flags.Bimul.pack~nsw~nuw)domain(Z.predka)vaelseNo_Rewrite.bisub~size~flagsdomainabletband~sizedomainab=ifTerms.equalabthen(* a & a *)a,domainelseNo_Rewrite.band~sizedomainabletrecbextract~size~index~oldsizedomainc=if(size=oldsize)&&(index=In_bits.zero)thenc,domainelsebeginmatchcwith|Terms.(Binary{term=T2{tag=Bconcat(size1,size2);a;b}})->if((index:>int)<(size2:>int))&&(size<=size2)then(bextract~size~index~oldsize:size2domainb)elseif((index:>int)>=(size2:>int))&&(size<=size1)then(bextract~size~index:In_bits.(index-size2)~oldsize:size1domaina)elseNo_Rewrite.bextract~size~index~oldsizedomainc|_->No_Rewrite.bextract~size~index~oldsizedomaincendendmoduleR=Rewriteletbiadd~size~flagsctx=ar2_binary_binary_binary'~sizectx@@R.biadd~size~flagsletbisub~size~flagsctx=ar2_binary_binary_binary'~sizectx@@R.bisub~size~flagsletbimul~size~flagsctx=ar2_binary_binary_binary'~sizectx@@R.bimul~size~flagsletbxor~sizectx=ar2_binary_binary_binary'~sizectx@@R.bxor~sizeletband~sizectx=ar2_binary_binary_binary'~sizectx@@R.band~sizeletbor~sizectx=ar2_binary_binary_binary'~sizectx@@R.bor~sizeletbashr~sizectx=ar2_binary_binary_binary'~sizectx@@R.bashr~sizeletblshr~sizectx=ar2_binary_binary_binary'~sizectx@@R.blshr~sizeletbshl~size~flagsctx=ar2_binary_binary_binary'~sizectx@@R.bshl~size~flagsletbisdiv~sizectx=ar2_binary_binary_binary'~sizectx@@R.bisdiv~sizeletbiudiv~sizectx=ar2_binary_binary_binary'~sizectx@@R.biudiv~sizeletbismod~sizectx=ar2_binary_binary_binary'~sizectx@@R.bismod~sizeletbiumod~sizectx=ar2_binary_binary_binary'~sizectx@@R.biumod~sizeletbconcat~size1~size2ctx=ar2_binary_binary_binary~size:In_bits.(size1+size2)ctx(Terms.Build.Binary.bconcat~size1~size2)(Domain.Binary_Forward.bconcat~size1~size2)letbextract~size~index~oldsizectx=ar1_binary_binary~sizectx(Terms.Build.Binary.bextract~size~index~oldsize)(Domain.Binary_Forward.bextract~size~index~oldsize)letbiconst~sizekctx=runctx@@ar0_binary(Terms.Build.Binary.biconst~sizek)(Domain.Binary_Forward.biconst~sizek)letbeq~sizectx=ar2_binary_binary_boolean~sizectx(Terms.Build.Binary.beq~size)(Domain.Binary_Forward.beq~size)(* Quite ugly: we use ar2' just to compare the constraints. This optimises equality. *)letbeq~sizectxab=letexceptionOptofboolintrybeginlet_x,_dom=ar2'ctx(fundomainab->raise(Opt(Terms.equalab)))abctx.domainin(* Return boolean_empty? *)assertfalseendwithOptres->ifresthenBoolean_Forward.true_ctxelsebeq~sizectxab;;letbiule~sizectx=ar2_binary_binary_boolean~sizectx(Terms.Build.Binary.biule~size)(Domain.Binary_Forward.biule~size)letbisle~sizectx=ar2_binary_binary_boolean~sizectx(Terms.Build.Binary.bisle~size)(Domain.Binary_Forward.bisle~size)letbsext~size~oldsizectx=ar1_binary_binary~sizectx(Terms.Build.Binary.bsext~size~oldsize)(Domain.Binary_Forward.bsext~size~oldsize)letbuext~size~oldsizectx=ar1_binary_binary~sizectx(Terms.Build.Binary.buext~size~oldsize)(Domain.Binary_Forward.buext~size~oldsize)letbchoose~sizecondctx=ar1_binary_binary~sizectx(Terms.Build.Binary.bchoose~sizecond)(Domain.Binary_Forward.bchoose~sizecond)letbofbool~sizectx=ar1_boolean_binary~sizectx(Terms.Build.Binary.bofbool~size)(Domain.Binary_Forward.bofbool~size)letbconcat~size1~size2ctx=ar2_binary_binary_binary~size:In_bits.(size1+size2)ctx(Terms.Build.Binary.bconcat~size1~size2)(Domain.Binary_Forward.bconcat~size1~size2)(* let bextract ~size ~index ~oldsize ctx = ar1_binary_binary ~size ctx (Terms.Build.Binary.bextract ~size ~index ~oldsize) (Domain.Binary_Forward.bextract ~size ~index ~oldsize) *)letbextract~size~index~oldsizectxa=letres,dom=R.bextract~size~index~oldsizectx.domainainctx.domain<-dom;res(* let bextract ~size ~index ~oldsize ctx = ar1_binary_binary ~size ctx (Terms.Build.Binary.bextract ~size ~index ~oldsize) (Domain.Binary_Forward.bextract ~size ~index ~oldsize) *)letbiconst~sizekctx=runctx@@ar0_binary(Terms.Build.Binary.biconst~sizek)(Domain.Binary_Forward.biconst~sizek)letbuninit~size_=assertfalseletbshift~size~offset~max_=assertfalseletbindex~size_=assertfalseletvalid~size_=assertfalseletvalid_ptr_arith~size_=assertfalse(** --------------------------- Bit-stealing operations --------------------------- **)(* Simplication 1 : ((p + t) + k) & m = 0 ==> (t - k) = 0 if m = 2^{n}-1 /\ k \in [-m;m] /\ (p & m) = 0 /\ (t & ~m) = 0 *)(* example : ((p + tag) - 3) & 7 = 0 ==> (tag - 3) = 0 if (p & 7) = 0 /\ (tag & ~7) = 0 *)(* Simplication 2 : (((p + t) + k)[0..l]) & m = 0 ==> (t - k) = 0 if m = 2^{n}-1 /\ n < l /\ k \in [-m;m] /\ (p & m) = 0 /\ (t & ~m) = 0 *)(* example : ((p + tag) - 3) & 7 = 0 ==> (tag - 3) = 0 if (p & 7) = 0 /\ (tag & ~7) = 0 *)letbeq~sizectxab=matcha,bwith|Terms.(Binary{term=T0{tag=TC.Biconst(_size4,r)}}),Terms.(Binary{term=T2{tag=TC.Band_size1;a=(Binary{term=T0{tag=TC.Biconst(_size3,l)}});b=(Binary{term=T2{tag=TC.Biadd_size2;a=(Binary{term=T2{tag=TC.Biadd_size6;a=Binary_;b=Binary_}});b=(Binary{term=T0{tag=TC.Biconst(_size7,k)}})}})}})whenZ.equalZ.zeror->assertfalse|Terms.(Binary{term=T0{tag=TC.Biconst(_size4,r)}}),Terms.(Binary{term=T2{tag=TC.Band_size1;a=(Binary{term=T0{tag=TC.Biconst(_size3,l)}}asmask);b=(Binary{term=T1{tag=TC.Bextract{size;index;oldsize};a=(Binary{term=T2{tag=TC.Biadd_size2;a=(Binary{term=T0{tag=TC.Biconst(_size7,k)}}asofs);b=(Binary{term=T2{tag=TC.Biadd{size=_size8;flags};a=Binary_astag;b=Binary_asptr}})}})}})}})whenZ.equalZ.zeror->(* if m = 2^{n}-1 /\ k \in [-m;m] /\ (p & m) = 0 /\ (t & ~m) = 0 & (i = 0 && msb <= j) *)Log.debug(funp->p"l : %a"Z.pp_printl);letlsb=Z.trailing_zeroslin(* position of the least significant bit *)Log.debug(funp->p"lsb : %d"lsb);letm=Z.shift_rightllsbinLog.debug(funp->p"m : %a"Z.pp_printm);letmsucc=Z.succminletmsb=Z.log2msuccinLog.debug(funp->p"msb : %d"msb);letc1=Z.equalmsucc(Z.shift_leftZ.onemsb)in(* m = 2^{n}-1 *)Log.debug(funp->p"is_mask : %b"c1);letk=Z.signed_extractklsbmsbinLog.debug(funp->p"offset : %a"Z.pp_printk);letc2=Z.leq(Z.negm)k&&Z.leqkmin(* k in [-m;m] *)Log.debug(funp->p"is offset in [-mask;mask] : %b"c2);letzero=biconst~size:oldsizeZ.zeroctxinletc3=beq~size:oldsizectx(band~size:oldsizectxptrmask)zeroinLog.debug(funp->p"c3 : %a"(Domain.boolean_prettyctx.domain)c3);letneg_mask=biconst~size:oldsize(Z.lognotl)ctxinLog.debug(funp->p"neg_mask : %a"(Domain.binary_pretty~size:oldsizectx.domain)neg_mask);letc4=beq~size:oldsizectx(band~size:oldsizectxtagneg_mask)zeroinLog.debug(funp->p"c4 : %a"(Domain.boolean_prettyctx.domain)c4);letc5=matchDomain.Query.(Boolean_Lattice.to_quadrivalent@@booleanctx.domain@@Boolean_Forward.(&&)ctxc3c4)with|Lattices.Quadrivalent.True->true|_->falseinLog.debug(funp->p"c5 : %b"c5);letc6=index=In_bits.zero&&msb<=(size:>int)in(* *)Log.debug(funp->p"c6 : %b"c6);Log.debug(funp->p"c1 /\ c2 /\ c5 /\ c6 : %b"(c1&&c2&&c5&&c6));ifc1&&c2&&c5&&c6thenbeq~size:oldsizectx(biadd~size~flagsctxtagofs)zero(* (tag + offset) = 0 *)elseassertfalse|_->beq~sizectxab(* let rec beq_aux ~size ctx a b =
Codex_log.debug "Term_domain.beq (aux) %a %a" Identifier.pretty a Identifier.pretty b ;
match a,b with (* to improve *)
| Terms.(Binary{term=T2{tag=TC.Band _size1; a=(Binary{term=T0{tag=TC.Biconst(_size2,k)}} as u); b=Binary _ as v}}), r
| Terms.(Binary{term=T2{tag=TC.Band _size1; a=Binary _ as v; b=(Binary{term=T0{tag=TC.Biconst(_size2,k)}} as u)}}), r
| r, Terms.(Binary{term=T2{tag=TC.Band _size1; a=(Binary{term=T0{tag=TC.Biconst(_size2,k)}} as u); b=Binary _ as v}})
| r, Terms.(Binary{term=T2{tag=TC.Band _size1; a=Binary _ as v; b=(Binary{term=T0{tag=TC.Biconst(_size2,k)}} as u)}}) ->
begin
Codex_log.debug "Term_domain.beq reaching case ((x + y) & v) = r" ;
(* ((x + y) & v) = r *)
match v with
| Terms.(Binary{term=T1{tag=TC.Bextract{size;index;oldsize};a=x}}) when 0 = index && (Z.to_int k) < size ->
begin
let zero = biconst ~size:(oldsize - size) Z.zero ctx in
let r = bconcat ~size1:(oldsize - size) ~size2:size ctx zero r in
let u = bconcat ~size1:(oldsize - size) ~size2:size ctx zero u in
let v = band ~size:oldsize ctx x u in
Codex_log.debug "beq %a %a" Identifier.pretty v Identifier.pretty r ;
beq_aux ~size:oldsize ctx v r
end
| Terms.(Binary{term=T2{tag=TC.Biadd _size1; a=(Binary{term=T0{tag=TC.Biconst(_size2,l)}} as x); b=Binary _ as y}})
| Terms.(Binary{term=T2{tag=TC.Biadd _size1; a=Binary _ as y; b=(Binary{term=T0{tag=TC.Biconst(_size2,l)}} as x)}}) ->
begin
(* ((x + (m + n)) & v) = r *)
match y with
| Terms.(Binary{term=T2{tag=TC.Biadd _size1; a=Binary _ as m; b=Binary _ as n}}) ->
let r1 = band ~size ctx m u in
let r2 = band ~size ctx n u in
begin
match r1,r2 with
| Terms.(Binary{term=T0{tag=TC.Biconst(_size,i)}}), _ ->
let r3 = bisub ~size ~nsw:false ~nuw:false ~nusw:false ctx r r1 in
let r4 = biadd ~size ~nsw:false ~nuw:false ~nusw:false ctx r3 x in
beq ~size ctx r4 r3
| _, Terms.(Binary{term=T0{tag=TC.Biconst(_size,i)}}) ->
let r3 = bisub ~size ~nsw:false ~nuw:false ~nusw:false ctx r r2 in
let r4 = biadd ~size ~nsw:false ~nuw:false ~nusw:false ctx r3 x in
beq ~size ctx r4 r3
| _ -> assert false
end
| _ -> assert false
end
| _ -> beq ~size ctx a b
end
| _ -> beq ~size ctx a b ;; *)(*
let bisle ~size ctx a b =
Codex_log.debug "Term_domain.bisle %a %a" Terms.pretty a Terms.pretty b ;
let res = propagate_comp ~size bisle ctx a b in
Codex_log.debug "Term_domain.bisle returning %a" Terms.pretty res ;
res
let beq ~size ctx a b =
let res = propagate_comp ~size beq ctx a b in
(* let res = beq ~size ctx a b in *)
res
*)end(* Note: we do not create them every time *)(* let integer_empty ctx = let res = ar0_integer ctx Terms.Build.Integer.empty Domain.integer_empty in run ctx res *)letinteger_empty=letres=Terms.Build.Integer.emptyinfunctx->res(* let boolean_empty = let res = ar0_boolean Terms.Build.Boolean.empty Domain.boolean_empty in fun ctx -> run ctx res *)letboolean_empty=letres=Terms.Build.Boolean.emptyinfunctx->resletenum_empty=letres=Terms.Build.Enum.emptyinfunctx->resletboolean_empty=letres=Terms.Build.Boolean.emptyinfunctx->res(* MAYBE: Avoid re-creation in most common cases *)letbinary_empty~sizectx=letres=ar0_binary(Terms.Build.Binary.empty~size)(Domain.binary_empty~size)inrunctxres;;letboolean_unknownctx=runctx@@ar0_boolean(Terms.Build.Boolean.unknown~level:ctx.level)(Domain.boolean_unknown)letbinary_unknown~sizectx=runctx@@ar0_binary(Terms.Build.Binary.unknown~level:ctx.level~size)(Domain.binary_unknown~size)letenum_unknown~enumsizectx=runctx@@ar0_enum(Terms.Build.Enum.unknown~level:ctx.level)(Domain.enum_unknown~enumsize)letinteger_unknownctx=runctx@@ar0_integerctx(Terms.Build.Integer.unknown~level:ctx.level)(Domain.integer_unknown);;;;(**************** Pretty printing ****************)moduletypePretty_Terms=sigvalboolean_pretty:Context.t->Format.formatter->boolean->unitvalinteger_pretty:Context.t->Format.formatter->integer->unitvalbinary_pretty:size:In_bits.t->Context.t->Format.formatter->binary->unitvalenum_pretty:Context.t->Format.formatter->enum->unitendmodulePretty_Both:Pretty_Terms=structletboolean_prettyctxfmtx=(* Format.fprintf fmt "value %a domain %a"
* (Domain.boolean_pretty x.domain) x.constrain
* Domain.pretty x.domain *)Format.fprintffmt"%a (value %a)"Terms.prettyx(Domain.boolean_prettyctx.domain)xletinteger_prettyctxfmtx=Format.fprintffmt"%a (value %a)"Terms.prettyx(Domain.integer_prettyctx.domain)xletenum_prettyctxfmtx=Format.fprintffmt"%a (value %a)"Terms.prettyx(Domain.enum_prettyctx.domain)xletbinary_pretty~sizectxfmtx=Format.fprintffmt"%a (value %a)"Terms.prettyx(Domain.binary_pretty~sizectx.domain)xendmodulePretty_Both_with_UF:Pretty_Terms=structletpp_parent(typea)fmt(x:aTerms.t)=matchTerms.get_parentxwith|Root->()|Node(p,rel)->Format.fprintffmt" (verifies %a)"(Terms.Relation.pretty_with_termsTerms.prettyxTerms.prettyp)relletboolean_prettyctxfmtx=(* Format.fprintf fmt "value %a domain %a"
* (Domain.boolean_pretty x.domain) x.constrain
* Domain.pretty x.domain *)Format.fprintffmt"%a%a (value %a)"Terms.prettyxpp_parentx(Domain.boolean_prettyctx.domain)xletinteger_prettyctxfmtx=Format.fprintffmt"%a%a (value %a)"Terms.prettyxpp_parentx(Domain.integer_prettyctx.domain)xletenum_prettyctxfmtx=Format.fprintffmt"%a%a (value %a)"Terms.prettyxpp_parentx(Domain.enum_prettyctx.domain)xletbinary_pretty~sizectxfmtx=Format.fprintffmt"%a%a (value %a)"Terms.prettyxpp_parentx(Domain.binary_pretty~sizectx.domain)xendmodulePretty_Value:Pretty_Terms=structletboolean_prettyctxfmtx=(* Format.fprintf fmt "value %a domain %a"
* (Domain.boolean_pretty x.domain) x.constrain
* Domain.pretty x.domain *)Domain.boolean_prettyctx.domainfmtx;;letinteger_prettyctxfmtx=Domain.integer_prettyctx.domainfmtx(* Format.fprintf fmt "value %a domain %a"
* (Domain.integer_pretty x.domain) x.constrain
* Domain.pretty x.domain *);;letenum_prettyctxfmtx=Domain.enum_prettyctx.domainfmtx(* Format.fprintf fmt "value %a domain %a"
* (Domain.integer_pretty x.domain) x.constrain
* Domain.pretty x.domain *);;letbinary_pretty~sizectxfmtx=Domain.binary_pretty~sizectx.domainfmtx(* Format.fprintf fmt "value %a domain %a"
* (Domain.integer_pretty x.domain) x.constrain
* Domain.pretty x.domain *);;endmodulePretty_Symbolic:Pretty_Terms=structletboolean_prettyctx=Identifier.prettyletinteger_prettyctx=Identifier.prettyletbinary_pretty~sizectx=Identifier.prettyletenum_prettyctx=Identifier.prettyendletboolean_pretty,integer_pretty,binary_pretty,enum_pretty=let(moduleX:Pretty_Terms)=(match!option_pretty_termswith|Value->(modulePretty_Value)|Both->(modulePretty_Both)|Symbolic->(modulePretty_Symbolic)|Relation->(modulePretty_Both_with_UF))inX.boolean_pretty,X.integer_pretty,X.binary_pretty,X.enum_pretty(**************** Tuple construction and fixpoint/nondet. ****************)(* Note that this operation is purely syntactic. Normally, the use
of semantic information to transform identifiers is already done. *)letbinary_is_empty~sizectx(b:binary)=Terms.equalb(Terms.Build.Binary.empty~size)letinteger_is_emptyctx(i:integer)=Terms.equaliTerms.Build.Integer.emptyletboolean_is_emptyctx(b:boolean)=Terms.equalbTerms.Build.Boolean.emptyletserialize:'hd'tail.t->'hdidentifier->t->'hdidentifier->'tailin_acc->('hdidentifier,'tail)Context.result=functxaactxbb(included,acc)->letmapping=ifTerms.equalabthenConsSame(a,acc.mapping)elseConsDifferent(a,b,acc.mapping)inletdeserializectx({phi}asout_tup)=(* Note: if I used different serializes, I would not have to re-test here. *)letconstrain=ifTerms.equalabthenaelsebeginmatchMapPair.findabphiwith|exceptionNot_found->assertfalse|x->xendinconstrain,out_tupinResult(included,{mapping},deserialize);;letserialize_binary~widens~size=serializeletserialize_integer~widens=serializeletserialize_boolean=serializeletserialize_enum=serialize(**************** Nondet and union. ****************)(* Traverse the phi arguments and make it a tuple. *)letbuild_phi_argumentsin_tup=letfiltereda,filteredb,map=letrecloop:typea._->_->_->amapping->_=funlalbmap->function|EmptyMapping->la,lb,map(* TODO: We could get rid of ConsSame, and maybe this filtering, by changing serialize. *)|ConsSame(_,mapping)->looplalbmapmapping(* Global value numbering: remove redundant pairs. *)|ConsDifferent(a,b,mapping)whenMapPair.memabmap->looplalbmapmapping|ConsDifferent(a,b,mapping)->letmap=MapPair.addabamapinloop((Terms.Anya)::la)((Terms.Anyb)::lb)mapmappinginloop[][]MapPair.emptyin_tup.mappinginfiltereda,filteredb,map;;(* Build the phi that will go in out_tup. *)letbuild_phitupatupbtupres=Immutable_array.fold_left3(funmapabc->matcha,b,cwith|Terms.(Any(Binary_asa),Any(Binary_asb),Any(Binary_asc))->MapPair.addabcmap|Terms.(Any(Integer_asa),Any(Integer_asb),Any(Integer_asc))->MapPair.addabcmap|Terms.(Any(Bool_asa),Any(Bool_asb),Any(Bool_asc))->MapPair.addabcmap|Terms.(Any(Enum_asa),Any(Enum_asb),Any(Enum_asc))->MapPair.addabcmap|_->Log.fatal(funp->p"Wrong type assertion"))MapPair.emptytupatupbtupres;;letnondet_same_context(ctx:context)(in_tup:_in_tuple)=letfiltereda,filteredb,map=build_phi_argumentsin_tupinlettupa=Immutable_array.of_listfilteredainlettupb=Immutable_array.of_listfilteredbinletdom=ctx.domaininletdoma=dominletdomb=dominletconda_bool=ctx.path_conditioninletcondb_bool=ctx.path_conditioninlettupres=Terms.Build.Tuple.nondet~level:ctx.level~conda_bool~a:tupa~condb_bool~b:tupbinletdomain=Domain.nondet~doma~tupa~domb~tupb~tupresinctx.domain<-domain;letphi=build_phitupatupbtupresin{phi};;letunioncond(ctx:context)(in_tup:_in_tuple)=letfiltereda,filteredb,map=build_phi_argumentsin_tupinlettupa=Immutable_array.of_listfilteredainlettupb=Immutable_array.of_listfilteredbinlettupres=Immutable_array.map2(fun(Terms.Anyx1)(Terms.Anyx2)->match(x1,x2)with|Terms.Binary{size=s1},Terms.Binary{size=s2}->assert(s1==s2);Terms.Any(Terms.Build.Binary.bunion~size:s1condx1x2)|Terms.Bool_,Terms.Bool_->Terms.Any(Terms.Build.Boolean.bunionx1x2)|Terms.Integer_,Terms.Integer_->assertfalse(* Terms.Any(Terms.Build.Integer.bunion x1 x2) *)(*
| Terms.Enum _, Terms.Enum _ ->
Terms.Any(Terms.Build.Enum.bunion x1 x2)
*)|_->assertfalse)tupatupbinletdomres=ctx.domainin(* We reuse nondet for now. *)letdomain=Domain.nondet~doma:domres~tupa~domb:domres~tupb~tupresinctx.domain<-domain;letphi=build_phitupatupbtupresin{phi};;lettyped_nondet2(ctxa:context)(ctxb:context)(in_tup:_in_tuple)=(* We filter etc. but we try to preserve the serialization order
(the code could be simplified otherwise, in particular in_tup
could consist in the mapping.
We reuse MapPair as a set here; we reuse the first element as a
value for typing purposes. Possibly we could implement SetPair
instead. *)letfiltereda,filteredb,map=build_phi_argumentsin_tupinlettupa=Immutable_array.of_listfilteredainlettupb=Immutable_array.of_listfilteredbinletdoma=ctxa.domaininletdomb=ctxb.domaininletconda_bool=ctxa.path_conditioninletcondb_bool=ctxb.path_conditioninassert(ctxa.level=ctxb.level);(* Note: we could build the tuple by a map operation of the MapPair instead. *)lettupres=Terms.Build.Tuple.nondet~level:ctxa.level~conda_bool~a:tupa~condb_bool~b:tupbinletdomain=Domain.nondet~doma~tupa~domb~tupb~tupresinletctx={domain=domain;level=ctxa.level;unique_id=get_unique_id();path_condition=Terms.Build.Boolean.(||)ctxa.path_conditionctxb.path_condition;}in(* Build the final map. *)letphi=build_phitupatupbtupresinctx,{phi};;(**************** New fixpoint computation. ****************)letwidened_fixpoint_step~widening_id~previous~next(includes,(in_tup:_in_tuple))=letwidening_id:Sig.Widening_Id.t=widening_idinletwidening_id:int=(widening_id:>int)inLog.debug(funp->p"widened_fixpoint_step: arg is %b %a %a"includescontext_prettypreviouscontext_prettynext);(* Fixpoint step: generates fresh variables until we reach a
fixpoint (we don't need to generate new variable
anymores). Furthermore this domain is used as a functor to
provide terms for the numerical domains.
A simple detection of fixpoint would consist in waiting for
next to be the same twice in a row. We can have fewer
iterations by detecting when we do no longer need to introduce
a variable, and when all previously introduced variables are
mapped to a different expression. We use a unique widening_id
to avoid confusing the variables that we created with others.
This could could possibly be simplified by having [in_tup]
being just a set of pairs. It seems better to preserve the
ordering of the tuple (e.g. for debugging reasons), so we
maintain the list of pairs variant instead.
MAYBE: Also check that the variables are in order (they should
be whent the fixpoint is reached; currently they are not,
probably because we dont assign numbers in the tuple in the
right order. *)(* Check if the parameter x is an inductive variable for the current widening point. *)letis_inductive_variable(typeu)(x:uTerms.t)i:bool=matchxwith|Terms.Bool{term=Terms.Inductive_var{widening_id=w}}->(w=widening_id)|Terms.Binary{term=Terms.Inductive_var{widening_id=w}}->(w=widening_id)|Terms.Integer{term=Terms.Inductive_var{widening_id=w}}->(w=widening_id)|Terms.Enum{term=Terms.Inductive_var{widening_id=w}}->(w=widening_id)|Terms.(Binary{term=Tuple_get(j,Inductive_vars{widening_id=w})})->(w=widening_id)(* && i = j *)|Terms.(Bool{term=Tuple_get(j,Inductive_vars{widening_id=w})})->(w=widening_id)(* && i = j *)|Terms.(Integer{term=Tuple_get(j,Inductive_vars{widening_id=w})})->(w=widening_id)(* && i = j *)|Terms.(Enum{term=Tuple_get(j,Inductive_vars{widening_id=w})})->(w=widening_id)(* && i = j *)|_->falsein(* In the [in_tup] pair of phi arguments, those coming from
[previous] may already be inductive variables, and those from
[next] are not (but may be the definition of these inductive
variables).
There are several cases:
- [ConsSame] is the case phi(e,e) = e, we don't need to introduce any variable.
TODO: Actually, we may even not register them in in_tup.
- [ConsDifferent] phi(e1,e2): we create a new variable.
- [ConsDifferent] phi(x,e) when every x is attached to the same
definition e: we can reuse the variable x.
If both phi(x,e1) and phi(x,e2) exist, then we need to create
different variables x1 and x2 in the next iteration.
To simplify, we create fresh variable on each iteration, unless
it is the last iteration (every [ConsDifferent] case is of the
form phi(x,e) where every x is attached to a single e.
*)(* TODO: If the inductive_vars are part of a tuple, we may want to
build a mapping (or an array) from indices in the tuple to
their definition, that we may use to build the definition. Or
we can just check that the induction variables are in the right
order. *)letfiltereda,filteredb,map,includes,i=letrecloop:typea._->_->_->bool->int->amapping->_=funlalbmapincludesi->function|EmptyMapping->la,lb,map,includes,i(* We skip it when we have both arguments. *)|ConsSame(_,mapping)->looplalbmapincludesimapping(* Global value numbering: if in the map, this cas was already handled. *)|ConsDifferent(a,b,mapping)whenMapPair.memabmap->looplalbmapincludesimapping|ConsDifferent(a,b,mapping)->letla'=((Terms.Anya)::la)andlb'=((Terms.Anyb)::lb)inletoldmap=mapinletmap=MapPair.addabamapin(* We have not reached a fixpoint, and need to introduce new
induction variables:
- if we still have some phi(a,b) where a is not an
induction variable
- if we have phi(a,b1) and phi(a,b2) where a is an
induction variable: in this case we need to split the
induction variable in two, i.e. the fixpoint is not
finished. *)letincludes=ifis_inductive_variableaithenbeginlettwo_definitions_for_a=MapPair.Map1.memaoldmapinnottwo_definitions_for_a&&includesendelsefalseinloopla'lb'mapincludes(i+1)mappinginloop[][]MapPair.emptyincludes0in_tup.mappinginletprevious_tup=Immutable_array.of_listfilteredainletnext_tup=Immutable_array.of_listfilteredbinletctxa=previousandctxb=nextinassert(ctxa.level=ctxb.level);letres_tup=ifincludesthenbegin(* Fixpoint reached on the symbolic expression domain reuse
the variables., and update the inductive definition (by
assignment). *)ifCodex_config.term_group_inductive_variable_by_tuplethenLog.error(funp->p"TODO: Change the definition of the Inductive_var here.")elseImmutable_array.iter2(funphidef->letopenTermsinletAnyphi=phiinletAnydef=definletf:typeab.at*bt->unit=fun(phi,def)->match(phi,def)with|Binary({term=Inductive_varx}),Binary_->x.definition<-def|Integer({term=Inductive_varx}),Integer_->x.definition<-def|Bool({term=Inductive_varx}),Bool_->x.definition<-def|Enum({term=Inductive_varx}),Enum_->x.definition<-def|_->Log.fatal(funp->p"Type mismatch in phi arguments");inf(phi,def))previous_tupnext_tup;previous_tupendelse(* Don't bother about the old variables, just create fresh ones. *)(* Note: we could have an induction step number in the name,
to ensure that we don't confuse variables coming from
different steps. *)Terms.Build.Tuple.inductive_vars~widening_id:(widening_id:>int)~level:ctxa.level~def:next_tupinletdomain,bool=Domain.widened_fixpoint_step~previous:previous.domain~previous_tup~next:next.domain~next_tupincludes~res_tupinLog.debug(funp->p"includes %b bool %b"includesbool);letctx={domain=domain;level=ctxa.level;unique_id=get_unique_id();path_condition=Terms.Build.Boolean.(||)ctxa.path_conditionctxb.path_condition;}in(* Build the final map. *)letphi=Immutable_array.fold_left3(funmapabc->matcha,b,cwith|Terms.(Any(Binary_asa),Any(Binary_asb),Any(Binary_asc))->MapPair.addabcmap|Terms.(Any(Integer_asa),Any(Integer_asb),Any(Integer_asc))->MapPair.addabcmap|Terms.(Any(Bool_asa),Any(Bool_asb),Any(Bool_asc))->MapPair.addabcmap|Terms.(Any(Enum_asa),Any(Enum_asb),Any(Enum_asc))->MapPair.addabcmap|_->Log.fatal(funp->p"Wrong type assertion"))MapPair.emptyprevious_tupnext_tupres_tupinctx,bool,{phi};;(**************** Fixpoint computation. ****************)lettyped_fixpoint_step~iteration~init~arg:(arg:Context.t)~body((included,in_tup):'ain_acc)=(* Codex_log.debug "typed_fixpoint_step init:%a arg:%a body:%a" context_pretty init context_pretty arg context_pretty body ; *)letinit:context=initinletbody:context=bodyinletcur_level=body.levelinletinit_dom=init.domaininletfinal_dom=body.domainin(* Codex_log.feedback "fixpoint step: arg_dom constraint = %a" Terms.pretty (Domain.to_constraint arg_dom); *)(* Codex_log.feedback "fixpoint step: final_dom constraint = %a" Terms.pretty (Domain.to_constraint final_dom); *)(* Codex_log.feedback "argument level = %d,@ context level = %d" (Terms.level @@ Domain.to_constraint arg_dom) cur_level; *)assert(Terms.level@@init.path_condition<cur_level);letfixpoint_reached=refincludedinletactuals,old_args,finals,map,init_dom=letrecloop:typea._->_->_->_->_->amapping->_=funactualsold_argsfinalsmapinit_dom->function|EmptyMapping->actuals,old_args,finals,map,init_dom|ConsSame(x,mapping)->(* Codex_log.debug "constraint x = %a" Terms.pretty x ; *)(* Codex_log.debug "constraint level of x = %d" (Terms.level x) ; *)(* Codex_log.debug "current level is %d" cur_level ; *)assert(Terms.levelx<cur_level);loopactualsold_argsfinalsmapinit_dommapping(* Global value numbering. *)|ConsDifferent(input,final,mapping)whenMapPair.meminputfinalmap->loopactualsold_argsfinalsmapinit_dommapping|ConsDifferent(input,final,mapping)->assert(Terms.levelinput<=cur_level);(* assert(Terms.level final <= cur_level); *)ifTerms.levelfinal>cur_levelthenraise(Failure"Trying to join two SSA constraints of the same level during widening");(* TODO : use the domain of array to try and solve this *)letinit_dom,old_arg,actual=input|>fun(typea)(input:aTerms.t)->matchinputwith(* If already a variable. *)|Terms.Bool{term=Terms.Mu_formal{level;actual=(actual_constrain,_)}}whenlevel==cur_level->init_dom,Terms.Anyinput,Terms.Anyactual_constrain|Terms.Integer{term=Terms.Mu_formal{level;actual=(actual_constrain,_)}}whenlevel==cur_level->init_dom,Terms.Anyinput,Terms.Anyactual_constrain|Terms.Binary{term=Terms.Mu_formal{level;actual=(actual_constrain,_)}}whenlevel==cur_level->init_dom,Terms.Anyinput,Terms.Anyactual_constrain|Terms.Enum{term=Terms.Mu_formal{level;actual=(actual_constrain,_)}}whenlevel==cur_level->init_dom,Terms.Anyinput,Terms.Anyactual_constrain(* Normal introduction of a variable. *)|_whenTerms.levelinput<cur_level->beginfixpoint_reached:=false;init_dom,Terms.Anyinput,Terms.Anyinputend(* In some rare cases (e.g. reads of different sizes may
happen at the same memory location), the memory
domain makes small modifications to an argument
already introduced. We make some simple substitutions
to support this. *)|_whenTerms.levelinput==cur_level->fixpoint_reached:=false;(* Note that this is not just substitution, but also
an evaluation of the function in the domain. *)letrecsubst:typea.aTerms.t->aidentifier=letopenTermsinfunction|Integer{term=T2{tag=TC.Imod;a;b=Integer{term=T0{tag=TC.Iconstk}}}}->Integer_Forward.imodinit(substa)(Integer_Forward.iconstkinit)|Integer{term=T2{tag=TC.Ishr;a;b=Integer{term=T0{tag=TC.Iconstk}}}}->Integer_Forward.ishrinit(substa)(Integer_Forward.iconstkinit)|Integer{term=Mu_formal{actual=(v,_)}}->v|Binary{term=T1{tag=TC.Bextract{size;index;oldsize};a}}->Binary_Forward.bextractinit~size~index~oldsize(substa)(* | Binary{term=T1{tag=TC.Bofbool(size);a}} -> Binary_Forward.bofbool ~size init (subst a) *)|Binary{term=Unknownlevel}asv->v|Binary{term=Mu_formal{actual=(v,_)}}->v|Binary{term=T2{tag=TC.Biadd{size;flags};a;b=Binary{term=T0{tag=TC.Biconst(size1,k)}}}}->Binary_Forward.biadd~size~flagsinit(substa)(Binary_Forward.biconst~size:size1kinit)|Binary{term=T2{tag=TC.Biadd{size;flags};a=Binary{term=T0{tag=TC.Biconst(size1,k)}};b}}->Binary_Forward.biadd~size~flagsinit(Binary_Forward.biconst~size:size1kinit)(substb)|Binary{term=T2{tag=TC.Bisub{size;flags};a;b=Binary{term=T0{tag=TC.Biconst(size1,k)}}}}->Binary_Forward.bisub~size~flagsinit(substa)(Binary_Forward.biconst~size:size1kinit)|Binary{term=T2{tag=TC.Bisub{size;flags};a=Binary{term=T0{tag=TC.Biconst(size1,k)}};b}}->Binary_Forward.bisub~size~flagsinit(Binary_Forward.biconst~size:size1kinit)(substb)|Binary{term=T2{tag=TC.Bimul{size;flags};a;b=Binary{term=T0{tag=TC.Biconst(size1,k)}}}}->Binary_Forward.bimul~size~flagsinit(substa)(Binary_Forward.biconst~size:size1kinit)|Binary{term=T2{tag=TC.Bimul{size;flags};a=Binary{term=T0{tag=TC.Biconst(size1,k)}};b}}->Binary_Forward.bimul~size~flagsinit(Binary_Forward.biconst~size:size1kinit)(substb)(* Rules used by the loop domain *)|Binary{term=T2{tag=TC.Biadd{size;flags};a;b}}whenTerms.levela<cur_level->Binary_Forward.biadd~size~flagsinita(substb)|Binary{term=T2{tag=TC.Biadd{size;flags};a;b}}whenTerms.levelb<cur_level->Binary_Forward.biadd~size~flagsinit(substa)b|constr->Codex_log.fatal"in typed_fixpoint_step, invalid constr = %a"Terms.prettyconstrinbeginmatchsubstinputwith|substed->assert(Terms.level@@init.path_condition<cur_level);letsubsted=Terms.Anysubstedin(* Save the result of evaluation in init_dom. *)(* For now we do nothing but we would have if functions like bextract
return a new domain instead of updating it imperatively. *)init_dom,(Terms.Anyinput),substedend|_->assert(Terms.levelinput>cur_level);assertfalse(* This case should not happen. *)inloop(actual::actuals)(old_arg::old_args)((Terms.Anyfinal)::finals)(MapPair.addinputfinalinputmap)init_dommappinginloop[][][]MapPair.emptyinit_domin_tup.mappingin(* This assertion is not always true, as we can call subst,
which imperatively changes the context. *)(* assert(init_dom == init.domain); *)letactuals=Immutable_array.of_listactualsinletold_args=Immutable_array.of_listold_argsinletfinals=Immutable_array.of_listfinalsinletcond_init=init.path_conditionin(* Codex_log.feedback "Cond arg is %a" Terms.pretty cond_arg; *)(* It is important that the domain corresponding to the argument
is suitable for use outside of the mu, when we leave the mu. *)assert(Terms.levelcond_init<cur_level);letbool,domainf=Domain.fixpoint_step~iteration~lvl:cur_levelinit_dom~actualsarg.domain~args:old_argsfinal_dom~finalsinletres=!fixpoint_reached&&boolinletcontinuef~close=letconstraints=ifclosethenbeginletbody_cond=body.path_conditioninTerms.Build.Tuple.mu~level:(cur_level-1)~init:actuals~var:old_args~body:finals~body_condendelse(* Restart *)letnew_args=actuals|>Immutable_array.map(funactual->letintroconstrainx=ifoption_fresh_variable_every_time||Terms.levelconstrain!=cur_level(* Is not already a variable. *)then(* TODO: Remove the actual_cond argument, that is not needed in this implementation. *)Terms.Build.Mu_Formal.intro~level:cur_level~actual:constrain~actual_cond:Terms.Build.Boolean.true_xelseconstraininmatchactualwith|Terms.(Any(Bool_asc))->Terms.Any(introcTC.Boolean)|Terms.(Any(Integer_asc))->Terms.Any(introcTC.Integer)|Terms.(Any(Binary{size}asc))->Terms.Any(introc@@TC.Binarysize)|Terms.(Any(Enum_asc))->Terms.Any(introcTC.Enum))innew_argsinletdomain=domainf~closeconstraintsin(* assert(Terms.level @@ Domain.to_constraint domain < cur_level); *)letphi=build_phiold_argsfinalsconstraintsinletrestup={phi}inletresctx:Context.t=ifclosetheninitelsearginrestup,{resctxwithdomain=domain}inres,continuefletmu_context_openparent_ctx=Domain.fixpoint_open();letctx={unique_id=get_unique_id();level=parent_ctx.level+1;domain=parent_ctx.domain;path_condition=parent_ctx.path_condition;}inctx(**************** Queries ****************)moduleInteger_Query=structincludeDomain.Integer_Queryletqueryctxx=queryctx.domainxendmoduleQuery=structincludeDomain.Queryletboolean(ctx:context)x=letdomain=ctx.domaininDomain.Query.booleandomainx;;(* This version is more precise as propagating constraints using assume may discover
additional contradictions. *)letbooleanctxx=matchbooleanctxxwith|Lattices.Quadrivalent.(False|True|Bottom)asx->x|Lattices.Quadrivalent.Top->matchassumectxx,assumectx(Boolean_Forward.notctxx)with|Some_,Some_->Lattices.Quadrivalent.Top|None,None->Lattices.Quadrivalent.Bottom|None,Some_->Lattices.Quadrivalent.False|Some_,None->Lattices.Quadrivalent.True;;letbinary~size(ctx:context)x=letdomain=ctx.domaininDomain.Query.binary~sizedomainxletinteger(ctx:context)x=letdomain=ctx.domaininDomain.Query.integerdomainxletenum(ctx:context)x=letdomain=ctx.domaininDomain.Query.enumdomainxendletassume_binary~size=assertfalseletbinary_is_empty=binary_is_emptyletinteger_is_empty=integer_is_emptyletboolean_is_empty=boolean_is_emptymoduleSatisfiable=structmoduleTo_SMT_FirstOrder=Terms_SMT.MakeFirstOrder(Terms)moduleTo_SMT_Horn=Terms_SMT.MakeHorn(Terms)letsatisfiable(ctx:context)bool=letcondition=ctx.path_conditionin(* Codex_log.feedback "Satisfiable? condition = %a" Terms.pretty condition; *)Smtbackend.Smtlib.with_z3(fun(moduleSMT:Smtbackend.Smtlib.UNTYPED_MUZ)->lettranslate=matchoption_translatewith|`Horn->letmoduleInst=To_SMT_Horn(SMT)inInst.translate|`First_order->letmoduleInst=To_SMT_FirstOrder(SMT)inInst.translateintranslate@@Terms.Build.Boolean.(&&)boolcondition);;letboolean_unknownctx=letres=boolean_unknownctxinLog.debug(funp->p"Term_domain.boolean_unknown %a"(boolean_prettyctx)res);resmoduleBinary_Forward=structincludeBinary_Forwardletbofbool~sizectxb=letres=bofbool~sizectxbinLog.debug(funp->p"Term_domain.bofbool %a"(binary_pretty~sizectx)res);resendendletsatisfiable=Satisfiable.satisfiable(* MAYBE: take pred into account. *)letbinary_unknown_typed~sizectxtyp=binary_unknown~sizectxletquery_boolean=Query.booleanend