123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590openSigstype'uidentry={uid:'uid;kind:kind;length:int;preferred:bool;delta:'uiddelta;}and'uiddelta=Fromof'uid|Zeroletmake_entry~kind~length?(preferred=false)?(delta=Zero)uid={uid;kind;length;preferred;delta}letlength{length;_}=lengthmoduleUtils=structletlength_of_variable_lengthn=letrecgor=function0->r|n->go(succr)(nlsr7)ingo1(nlsr7)letcmdofflen=letcmd=ref0inifoffland0x000000ff<>0thencmd:=!cmdlor0x01;ifoffland0x0000ff00<>0thencmd:=!cmdlor0x02;ifoffland0x00ff0000<>0thencmd:=!cmdlor0x04;ifoffland0x7f000000<>0thencmd:=!cmdlor0x08;iflenland0x0000ff<>0thencmd:=!cmdlor0x10;iflenland0x00ff00<>0thencmd:=!cmdlor0x20;iflenland0xff0000<>0thencmd:=!cmdlor0x40;!cmd[@@inline]letlength_of_copy_code~off~len=letrequired=leta=[|0;1;1;2;1;2;2;3;1;2;2;3;2;3;3;4|]infunx->a.(xland0xf)+a.(xlsr4)inletcmd=cmdoffleninrequiredcmdletlength~source~targethunks=length_of_variable_lengthsource+length_of_variable_lengthtarget+List.fold_left(funacc->function|Duff.Insert(_,len)->1+len+acc|Duff.Copy(off,len)->1+length_of_copy_code~off~len+acc)0hunksendmoduleW=structtype'at='aWeak.tletcreate()=Weak.create1letcreate_withv=lett=Weak.create1inWeak.sett0(Somev);tletsettv=Weak.sett0(Somev)letgett=Weak.gett0endtype'uidp={index:Duff.indexW.t;entry:'uidentry;depth:int;v:Dec.vW.t;}type'uidpatch={hunks:Duff.hunklist;depth:int;source:'uid;source_length:int;}type'uidq={mutablepatch:'uidpatchoption;entry:'uidentry;v:Dec.vW.t;}lettarget_uid{entry;_}=entry.uidlettarget_length{entry;_}=entry.lengthletpp_patchtarget_lengthpp_uidppfpatch=Fmt.pfppf"{ @[<hov>hunks= %d;@ depth= %d;@ source= %a;@ source_length= %d;@] }"(Utils.length~source:patch.source_length~target:target_lengthpatch.hunks)patch.depthpp_uidpatch.sourcepatch.source_length[@@@warning"-32"](* XXX(dinosaure): pretty-printers. *)letpp_kindppf=function|`A->Fmt.stringppf"a"|`B->Fmt.stringppf"b"|`C->Fmt.stringppf"c"|`D->Fmt.stringppf"d"letpp_deltapp_uidppf=function|Zero->Fmt.stringppf"<none>"|Fromuid->Fmt.pfppf"@[<1>(From %a)@]"pp_uiduidletpp_entrypp_uidppfentry=Fmt.pfppf"{ @[<hov>uid= %a;@ kind= %a;@ length= %d;@ preferred= %b;@ delta= \
@[<hov>%a@];@] }"pp_uidentry.uidpp_kindentry.kindentry.lengthentry.preferred(pp_deltapp_uid)entry.deltaletpp_qpp_uidppfq=Fmt.pfppf"{ @[<hov>patch= @[<hov>%a@]; entry= @[<hov>%a@]; v= %s@] }"Fmt.(Dump.option(pp_patchq.entry.lengthpp_uid))q.patch(pp_entrypp_uid)q.entry(ifWeak.checkq.v0then"#raw"else"NULL")[@@@warning"+32"]type('uid,'s)load='uid->(Dec.v,'s)ioletdepth_of_source:'uidp->int=fun{depth;_}->depthletdepth_of_target:'uidq->int=fun{patch;_}->matchpatchwithNone->1|Some{depth;_}->depthlettarget_to_source:'uidq->'uidp=funtarget->{index=W.create();entry=target.entry;depth=depth_of_targettarget;v=target.v(* XXX(dinosaure): dragoon here! *);}letentry_to_target:types.sscheduler->load:('uid,s)load->'uidentry->('uidq,s)io=fun{bind;return}~loadentry->let(>>=)=bindinloadentry.uid>>=funv->(matchentry.deltawith|Fromuid->loaduid>>=funs->letsource=Bigstringaf.sub~off:0~len:(Dec.lens)(Dec.raws)inlettarget=Bigstringaf.sub~off:0~len:(Dec.lenv)(Dec.rawv)inletindex=Duff.make(Bigstringaf.sub~off:0~len:(Dec.lens)(Dec.raws))inlethunks=Duff.deltaindex~source~targetinreturn(Some{hunks;depth=Dec.depthv;source=uid;source_length=Dec.lens;})|Zero->returnNone)>>=funpatch->return{patch;entry;v=W.create_withv}letlength_of_delta~source~targethunks=Utils.length~source~targethunksexceptionBreakexceptionNext(* XXX(dinosaure): [apply] tries to generate a patch between [source] and [target].
If the resulted patch is good enough, we set [target.patch] to it. [apply] can raise
two exceptions:
- [Break] where it is not able to generate a patch (different kinds)
- [Next] when it reaches the depth limit or resulted patch is not good enough
NOTE: [load] must create a new [Bigstringaf.t]! No cache are expected at this
layer where we already handle it with [W.t] (weak reference). *)letapply:typesuid.sscheduler->load:(uid,s)load->uid_ln:int->source:uidp->target:uidq->(unit,s)io=fun{bind;return}~load~uid_ln~source~target->let(>>=)=bindin(* Don't bother doing diffs between different types. *)ifsource.entry.kind<>target.entry.kindthenraise_notraceBreak;(* Let's not bust the allowed depth. *)ifdepth_of_sourcesource>=_max_depththenraise_notraceNext;(* Now some size filtering heuristics. *)letmax_length,ref_depth=matchtarget.patchwith|Some{hunks;source_length;depth;_}->(length_of_delta~source:source_length~target:target.entry.lengthhunks,depth)|None->(target.entry.length/2)-uid_ln,1inletmax_length=max_length*(_max_depth-depth_of_sourcesource)/(_max_depth-ref_depth+1)inifmax_length==0thenraise_notraceNext;letdiff=ifsource.entry.length<target.entry.lengththentarget.entry.length-source.entry.lengthelse0inifdiff>=max_lengththenraise_notraceNext;iftarget.entry.length<source.entry.length/32thenraise_notraceNext;(* Load data if not already done. *)letload_ifweakuid=matchW.getweakwith|Somev->returnv|None->loaduid>>=funv->W.setweakv;returnvin(* Load index if not already done (TODO: check it!). *)letindex_ifweakv=matchW.getweakwith|Someindex->index|None->letindex=Duff.make(Bigstringaf.sub~off:0~len:(Dec.lenv)(Dec.rawv))inW.setweakindex;indexinload_ifsource.vsource.entry.uid>>=funsource_v->load_iftarget.vtarget.entry.uid>>=funtarget_v->index_ifsource.indexsource_v|>funsource_index->lettarget_r=Bigstringaf.sub~off:0~len:(Dec.lentarget_v)(Dec.rawtarget_v)inletsource_r=Bigstringaf.sub~off:0~len:(Dec.lensource_v)(Dec.rawsource_v)inlethunks=Duff.deltasource_index~source:source_r~target:target_rintarget.patch<-Some{hunks;source=source.entry.uid;source_length=source.entry.length;depth=source.depth+1;};return()moduletypeVERBOSE=sigtype'afibervalsucc:unit->unitfibervalprint:unit->unitfiberendmoduletypeUID=sigtypetvalhash:t->intvalequal:t->t->boolendmoduleDelta(Scheduler:SCHEDULER)(IO:IOwithtype'at='aScheduler.s)(Uid:UID)(Verbose:VERBOSEwithtype'afiber='aIO.t)=structlet(>>=)=IO.bindletreturn=IO.returnlets=letopenSchedulerin{bind=(funxf->inj(IO.bind(prjx)(funx->prj(fx))));return=(funx->inj(IO.returnx));}letdelta:load:(Uid.t,Scheduler.t)load->weight:int->uid_ln:int->Uid.tqarray->unitIO.t=fun~load~weight~uid_lntargets->letwindow=Array.makeweightNoneinletfind_deltaidxtarget=letbest:intref=ref(-1)inlettry_deltajsource=letother_idx=idx+jinletother_idx=ifother_idx>=weightthenother_idx-weightelseother_idxintryapplys~load~uid_ln~source~target|>Scheduler.prj>>=fun()->best:=other_idx;return()with|Next->return()|Breakasexn->raise_notraceexninletrecgoj=ifj<0thenreturn()elsematchwindow.(j)with|Somem->(trytry_deltajm>>=fun()->(go[@tailcall])(predj)withBreak->return())|None->return()(* TODO: check it! *)ingo(Array.lengthwindow-1)>>=fun()->(if!best>=0thenVerbose.succ()elsereturn())>>=fun()->return!bestin(* XXX(dinosaure): [git] does something a bit complex between the iteration
over [targets] and the [window]. [n] is the current [target] where we will try
to apply a patch and [idx] seems a lower-bound of the LRU-cache [window]. *)letreciternidx=ifn<Array.lengthtargetsthen(find_deltaidxtargets.(n)>>=funbest->(* [git] does this update __before__ to try to find a patch. However, it seems fine
to do that after when an object can not be patched with itself. *)window.(idx)<-Some(target_to_sourcetargets.(n));Verbose.print()>>=fun()->(* [git] wants to deflate and cache the delta data. Should we do the same? TODO *)ifdepth_of_targettargets.(n)>1&&depth_of_targettargets.(n)<_max_depththen((* XXX(dinosaure): a slightly assumption, if [target] has a patch,
[!best] (into [go]) was properly set to a valid source. Of course, that
means that given [targets] contains non-delta-ified objects. *)letswap=window.(best)in(* Move the best delta base up in the window, after the currently deltified object, to
keep it longer. It will be the first base object to be attempted next. *)letv=refbestinfor_=(weight+idx-best)modweightto0dowindow.(!v)<-window.((!v+1)modweight);v:=(!v+1)modweightdone;window.(!v)<-swap);ifdepth_of_targettargets.(n)<_max_depththen(iter[@tailcall])(succn)(ifidx+1>=weightthen0elseidx+1)else(iter[@tailcall])(succn)idx)elsereturn()initer00typem={mutablev:int;m:IO.Mutex.t}letdispatcher:load:(Uid.t,Scheduler.t)load->mutex:m->entries:Uid.tentryarray->targets:Uid.tqoptionarray->unitIO.t=fun~load~mutex~entries~targets->letrecgo()=IO.Mutex.lockmutex.m>>=fun()->letv=mutex.vinmutex.v<-mutex.v+1;ifv>=Array.lengthentriesthen(IO.Mutex.unlockmutex.m;IO.return())else(IO.Mutex.unlockmutex.m;entry_to_targets~loadentries.(v)|>Scheduler.prj>>=funtarget->targets.(v)<-Sometarget;go())ingo()letget=functionSomex->x|None->assertfalseletdelta~threads~weight~uid_lnentries=letmutex={v=0;m=IO.Mutex.create()}inlettargets=Array.make(Array.lengthentries)NoneinIO.parallel_iter~f:(funload->dispatcher~load~mutex~entries~targets)threads>>=fun()->lettargets=Array.mapgettargetsindelta~load:(List.hdthreads)~weight~uid_lntargets>>=fun()->returntargetsendmoduleN:sigtypeencodertypeb={i:Bigstringaf.t;q:De.Queue.t;w:De.window}valencoder:'sscheduler->b:b->load:('uid,'s)load->'uidq->(encoder,'s)iovalencode:o:Bigstringaf.t->encoder->[`Flushofencoder*int|`End]valdst:encoder->Bigstringaf.t->int->int->encoderend=structtypeb={i:Bigstringaf.t;q:De.Queue.t;w:De.window}typeencoder=HofZh.N.encoder|ZofZl.Def.encoderletrecencode_zlib~oencoder=matchZl.Def.encodeencoderwith|`Awaitencoder->encode_zlib~o(Zl.Def.srcencoderBigstringaf.empty00)|`Flushencoder->letlen=Bigstringaf.lengtho-Zl.Def.dst_remencoderin`Flush(encoder,len)|`Endencoder->letlen=Bigstringaf.lengtho-Zl.Def.dst_remencoderiniflen>0then`Flush(encoder,len)else`Endletencode_hunk~oencoder=matchZh.N.encodeencoderwith|`Flushencoder->letlen=Bigstringaf.lengtho-Zh.N.dst_remencoderin`Flush(encoder,len)|`End->`Endletencode~o=function|Zencoder->(matchencode_zlib~oencoderwith|`Flush(encoder,len)->`Flush(Zencoder,len)|`End->`End)|Hencoder->(matchencode_hunk~oencoderwith|`Flush(encoder,len)->`Flush(Hencoder,len)|`End->`End)letdstencodersjl=matchencoderwith|Zencoder->letencoder=Zl.Def.dstencodersjlinZencoder|Hencoder->letencoder=Zh.N.dstencodersjlinHencoderletencoder:types.sscheduler->b:b->load:('uid,s)load->'uidq->(encoder,s)io=fun{bind;return}~b~loadtarget->let(>>=)=bindinletload_ifweakuid=matchW.getweakwith|Somev->returnv|None->loaduid>>=funv->W.setweakv;returnvinmatchtarget.patchwith|Some{hunks;source_length;_}->load_iftarget.vtarget.entry.uid>>=funv->letraw=Bigstringaf.sub~off:0~len:(Dec.lenv)(Dec.rawv)inletencoder=Zh.N.encoder~i:b.i~q:b.q~w:b.w~source:source_lengthraw`Manualhunksinreturn(Hencoder)|None->load_iftarget.vtarget.entry.uid>>=funv->letencoder=Zl.Def.encoder`Manual`Manual~q:b.q~w:b.w~level:0inletencoder=Zl.Def.srcencoder(Dec.rawv)0(Dec.lenv)inreturn(Zencoder)endtype('uid,'s)find='uid->(intoption,'s)iotypeb={i:Bigstringaf.t;q:De.Queue.t;w:De.window;o:Bigstringaf.t}letencode_header~okindlength=iflength<0theninvalid_arg"encode_header: length must be positive";letc=ref((kindlsl4)lor(lengthland15))inletl=ref(lengthasr4)inletp=ref0inletn=ref1inwhile!l!=0doBigstringaf.seto!p(Char.chr(!clor0x80land0xff));incrp;c:=!lland0x7f;l:=!lasr7;incrndone;Bigstringaf.seto!p(Char.unsafe_chr!c);!ntype'uiduid={uid_ln:int;uid_rw:'uid->string}letkind_to_int=function|`A->0b001|`B->0b010|`C->0b011|`D->0b100letheader_of_pack~lengthbufofflen=ifoff<0||len<0||off+len>Bigstringaf.lengthbuf||len<4+4+4thenFmt.invalid_arg"header_of_pack";Bigstringaf.set_int32_bebuf(off+0)0x5041434bl;Bigstringaf.set_int32_bebuf(off+4)0x2l;Bigstringaf.set_int32_bebuf(off+8)(Int32.of_intlength)letencode_target:types.sscheduler->b:b->find:('uid,s)find->load:('uid,s)load->uid:'uiduid->'uidq->cursor:int->(int*N.encoder,s)io=fun({bind;return}ass)~b~find~load~uidtarget~cursor->let(>>=)=bindinmatchtarget.patchwith|None->letoff=encode_header~o:b.o(kind_to_inttarget.entry.kind)target.entry.lengthinN.encoders~b:{i=b.i;q=b.q;w=b.w}~loadtarget>>=funencoder->return(off,N.dstencoderb.ooff(Bigstringaf.lengthb.o-off))|Some{source;source_length;hunks;_}->(findsource>>=function|Someoffset->letoff=encode_header~o:b.o0b110(Utils.length~source:source_length~target:target.entry.lengthhunks)inletbuf=Bytes.create10inletp=ref(10-1)inletn=ref(cursor-offset)inBytes.setbuf!p(Char.unsafe_chr(!nland127));while!nasr7<>0don:=!nasr7;decrp;Bytes.setbuf!p(Char.unsafe_chr(128lor((!n-1)land127)));decrndone;Bigstringaf.blit_from_bytesbuf~src_off:!pb.o~dst_off:off~len:(10-!p);N.encoders~b:{i=b.i;q=b.q;w=b.w}~loadtarget>>=funencoder->letoff=off+(10-!p)inletlen=Bigstringaf.lengthb.o-offinreturn(off,N.dstencoderb.oofflen)|None->letoff=encode_header~o:b.o0b111(Utils.length~source:source_length~target:target.entry.lengthhunks)inletraw=uid.uid_rwsourceinBigstringaf.blit_from_stringraw~src_off:0b.o~dst_off:off~len:uid.uid_ln;N.encoders~b:{i=b.i;q=b.q;w=b.w}~loadtarget>>=funencoder->letoff=off+uid.uid_lninletlen=Bigstringaf.lengthb.o-offinreturn(off,N.dstencoderb.oofflen))