123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401# 1 "src/lib/eliom_wrap.server.ml"(* Ocsigen
* Copyright (C) 2011 Pierre Chambart
*
* This program is free software; 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, with linking exception;
* either version 2.1 of the License, or (at your option) any later version.
*
* This program 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*)letsection=Lwt_log.Section.make"eliom:wrap"typepolyexternalto_poly:'a->poly="%identity"type'awrapped_value=poly*'aletwith_no_heap_compactionfv=letgc_control=Gc.get()in(* disable heap compaction *)Gc.set{gc_controlwithGc.max_overhead=max_int};matchfvwith|v->(* reset gc settings *)Gc.setgc_control;v|exceptione->(* reset gc settings *)Gc.setgc_control;raiseemoduleMark:sigtypetvalwrap_mark:tvaldo_nothing_mark:tvalunwrap_mark:tend=structtypet=stringletwrap_mark="wrap_mark"letdo_nothing_mark="do_nothing_mark"letunwrap_mark="unwrap_mark"endtypemarked_value={mark:Mark.t;f:(Obj.t->Obj.t)option}[@@warning"-69"]letmake_markfmark={mark;f}letis_markedo=letis_marko=ifObj.tago=0&&Obj.sizeo=2&&Obj.fieldo0==Obj.reprMark.wrap_markthen(letf=Obj.fieldo1inassert(Obj.tagf=0);(* The case None should not happen here *)assert(Obj.sizef=1);assert(lettag=Obj.tag(Obj.fieldf0)intag=Obj.infix_tag||tag=Obj.closure_tag);true)elsefalseinifObj.tago=0&&Obj.sizeo>=2(* WARNING: we only allow block values with tag = 0 to be wrapped.
It is easier: we do not have to do another test to know if the
value is a function *)thenletpotential_mark=Obj.fieldo(Obj.sizeo-1)inis_markpotential_markelsefalseletwrap_locallyo=letmark:marked_value=Obj.obj(Obj.fieldo(Obj.sizeo-1))inmatchmark.fwithSomef->fo|None->assertfalseletbits=8(* We use a hash-table with open addressing (which minimize
allocations) and resizable arrays. The initial size of the hash
table is 2 ** bits; the initial size of arrays is half this *)letnone=Obj.repr0(* Unallocated entry in an array or in a hash-table *)moduleDynArray=structletreccheck_sizeai=letlen=Array.length!ainifi>lenthen(letold_a=!aina:=Array.make(2*len)none;Array.blitold_a0!a0len;check_sizeai)letmake()=ref(Array.make(1lsl(bits-1))none)letgetai=!a.(i)letsetaiv=!a.(i)<-vendletresize_count=ref0letrehash_count=ref0(* Hash-table associating an integer to a block.
As the block may be moved (once) during a minor garbage collection,
we may allocate more than once index for a block. But thereafter a
look-up will always return the second index. *)moduleTbl=structtypet={mutablesize:int;(* Size of the hash table *)mutableshift:int;(* For hashing *)mutableoccupancy:int;(* How many elements have been inserted *)mutableobj:Obj.tarray;(* Inserted blocks *)mutableidx:intarray;(* Corresponding indices *)mutablegc:int;(* Last minor GC cycle where the
table was accurate *)on_resize:(int->unit)list}(* Functions called on resize *)letcst=(* Fibonacci hash: 2 ^ Sys.int_size / phi *)Int64.to_int(Int64.shift_right0x4F1BBCDCBFA53E09L(63-Sys.int_size))lethashtblx=(Obj.magicx*cst)lsrtbl.shiftletgc_count()=Gc.((quick_stat()).minor_collections)(* Rehash the hash-table, possibly after resizing it *)letreallocateresizetbl=letold_size=tbl.sizeinletold_obj=tbl.objinletold_idx=tbl.idxinifresizethen(tbl.size<-2*old_size;tbl.shift<-tbl.shift-1;List.iter(funf->f(tbl.sizelsr1))tbl.on_resize);tbl.obj<-Array.maketbl.sizenone;tbl.idx<-Array.maketbl.size(-1);tbl.gc<-gc_count();letrecinserttblhxidx=lety=tbl.obj.(h)inify==nonethen(tbl.obj.(h)<-x;tbl.idx.(h)<-idx)elseify==xthentbl.idx.(h)<-maxidxtbl.idx.(h)(* Keep largest index *)elseinserttbl((h+1)land(tbl.size-1))xidxinfori=0toold_size-1doletx=old_obj.(i)inifx!=nonetheninserttbl(hashtblx)xold_idx.(i)doneletresizetbl=incrresize_count;reallocatetruetblletrehashtbl=incrrehash_count;reallocatefalsetblletmaketbls=letsize=1lslbitsinletobj=Array.makesizenoneinletidx=Array.makesize(-1)inleton_resize=List.mapDynArray.check_sizetblsinletgc=gc_count()in{size;shift=Sys.int_size-bits;occupancy=0;obj;idx;gc;on_resize}letrecallocate_rectblxi=iftbl.obj.(i)==xthentbl.idx.(i)elseiftbl.obj.(i)==nonethen(tbl.obj.(i)<-x;letidx=tbl.occupancyintbl.idx.(i)<-idx;tbl.occupancy<-idx+1;iftbl.occupancy*2>=tbl.sizethenresizetbl;idx)elseallocate_rectblx((i+1)land(tbl.size-1))(* Insert a block into the hash-table. This may return a new index
if the block was moved. *)letallocate_indextblx=allocate_rectblx(hashtblx)letrecget_rectblxi=lety=tbl.obj.(i)inify==xthentbl.idx.(i)elseify==nonethen-1(* Not found *)elseget_rectblx((i+1)land(tbl.size-1))(* This may fail if a GC occurred *)letget_index_no_retrytblx=get_rectblx(hashtblx)(* Get the index associated to a block already in the hash-table.
If allocate_index is not invoked in-between, this always returns
the same index for a given block. Indeed, a look-up always return
the largest index of a block; this property is preserved both
when invoking allocate_index (though this may allocate a larger
index) and by rehashing. *)letget_indextblx=letidx=get_index_no_retrytblxinifidx<>-1thenidxelse(rehashtbl;letidx=get_index_no_retrytblxinifidx=-1then(fori=0toArray.lengthtbl.obj-1doassert(tbl.obj.(i)!=x)done;Format.eprintf"%b@."(is_markedx));assert(idx<>-1);idx)(* We can check whether the table is up to date, but this has a very
slight chance to perform an allocation; in which case, the table
will no longer be up to date... *)letwas_up_to_datetbl=tbl.gc=gc_count()endletobj_kindv=ifnot(Obj.is_blockv)then`Opaqueelselettag=Obj.tagviniftag>=Obj.no_scan_tagthen`Opaqueelseiftag<=Obj.last_non_constant_constructor_tagthen`Scannableelseiftag=Obj.forward_tagthenlettag'=Obj.tag(Obj.fieldv0)iniftag'=Obj.forward_tag||tag'=Obj.double_tagthen`Scannableelse(* Forward pointer that may be optimized away by the GC *)`Forwardelse(iftag=Obj.lazy_tagthenfailwith"lazy values must be forced before wrapping";iftag=Obj.object_tagthenfailwith"cannot wrap object values";iftag=Obj.closure_tagthenfailwith"cannot wrap functional values";iftag=Obj.infix_tagthenfailwith"cannot wrap functional values: infix tag";(* Should not happen (in case a new kind of value is added) *)failwith(Printf.sprintf"cannot wrap value (unexpected tag %d)"tag))letunchanged=(* This block and its descendants can be left unchanged *)Obj.repr1letmodified=(* This block or its descendants may need to be modified *)Obj.repr2letiteration_count=ref0letwrap_count=ref0(* First step: we traverse the value and find which parts need to be
replaced. We also compute which parts can be clearly left
unchanged. We may traverse some values twice if a minor GC occurs,
but this is harmless. *)(* TODO: shall we use an explicit stack to avoid stack overflows? *)letrecfind_subststblsubst_tblv=incriteration_count;matchobj_kindvwith|`Opaque->(* Opaque values don't need to be copied *)unchanged|`Forward->(* Follow the forward pointers that may disappear due to GC
(our code might get confused if we stored them in the
hash-table) *)find_subststblsubst_tbl(Obj.fieldv0)|`Scannable->letidx=Tbl.allocate_indextblvinletv'=DynArray.getsubst_tblidxinifv'==none(* Not visited yet *)thenifis_markedvthenifnot(Tbl.was_up_to_datetbl)then((* v may have been visited already, so we rehash and try
again. Indeed, we don't want to call the wrapping
function twice on the same value. *)Tbl.rehashtbl;find_subststblsubst_tblv)else(incrwrap_count;letv'=wrap_locallyvinDynArray.setsubst_tblidxv';ignore(find_subststblsubst_tblv');modified)else((* We don't know yet whether v needs to be copied.
We conservatively assume so for now. *)DynArray.setsubst_tblidxmodified;letsize=Obj.sizevinletis_unchanged=reftrueinfori=0tosize-1doletstatus=find_subststblsubst_tbl(Obj.fieldvi)inis_unchanged:=!is_unchanged&&status==unchangeddone;letres=if!is_unchangedthenunchangedelsemodifiedinDynArray.setsubst_tblidxres;res)elsev'letcopy_count=ref0letrecduplicatetblsubst_tblcopy_tblorig=matchobj_kindorigwith|`Opaque->(* Opaque values are not copied *)orig|`Forward->(* Follow forward pointers that may disappear due to GC *)duplicatetblsubst_tblcopy_tbl(Obj.fieldorig0)|`Scannable->letidx=Tbl.get_indextbloriginletsubst=DynArray.getsubst_tblidxinifsubst==unchangedthen(* This block does not need to be copied *)origelseifsubst!=modifiedthen(* This block is replaced by another value *)duplicatetblsubst_tblcopy_tblsubstelseletcopy=DynArray.getcopy_tblidxinifcopy!=nonethen(* Since we have already copied the block; return the copy *)copyelse(incrcopy_count;letcopy=Obj.duporiginDynArray.setcopy_tblidxcopy;letsize=Obj.sizeoriginfori=0tosize-1doletchild=Obj.fieldorigiinletchild_copy=duplicatetblsubst_tblcopy_tblchildinifchild_copy!=childthenObj.set_fieldcopyichild_copydone;copy)letperform_wrap=with_no_heap_compaction@@funv->iteration_count:=0;copy_count:=0;wrap_count:=0;resize_count:=0;rehash_count:=0;(* TODO: maybe we should use globally allocated tables by default,
with temporary allocations only for really large values? *)letsubst_tbl=DynArray.make()inletcopy_tbl=DynArray.make()inlettbl=Tbl.make[subst_tbl;copy_tbl]inignore(find_subststblsubst_tblv);letw=duplicatetblsubst_tblcopy_tblvinLwt_log.ign_debug_f~section"Wrap stats: %d visited (%d blocks), %d wrapped, %d copied, %d resizes, %d rehashes"!iteration_counttbl.occupancy!wrap_count!copy_count!resize_count!rehash_count;wtype+'awrapper=marked_valueletcreate_wrapper(f:'a->'b):'awrapper=make_mark(Some(funx->Obj.repr(f(Obj.objx))))Mark.wrap_markletempty_wrapper:'awrapper=make_markNoneMark.do_nothing_marktypeunwrap_id=intletid_of_intx=xtypeunwrapper={(* WARNING Must be the same as Eliom_unwrap.unwrapper *)id:unwrap_id;umark:Mark.t}[@@warning"-69"]letcreate_unwrapperid={id;umark=Mark.unwrap_mark}letempty_unwrapper={id=-1;umark=Mark.do_nothing_mark}letwrapv=to_polyMark.unwrap_mark,Obj.obj(perform_wrap(Obj.reprv))