123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285(* object location DB *)(*
Copyright 2009, 2010, 2011, 2012, 2013, 2015, 2017 Anton Lavrik
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*)moduleU=Piqi_utilopenU.Stdtypeloc=string*int*int(* file, line, column *)(* whether to verify the correctness of location information *)letcheck=reffalse(* whether to print debug information *)lettrace=reffalse(* whether to crash with Piqloc_not_found if can't find the location; this is
* useful for obtaining the exact stacktrace where the location DB consistency
* is violated *)letcrash_on_error=reffalseexceptionPiqloc_not_found(* pause & resume storing location references; NOTE: it is paused from the
* beginning because there's some boot actions in piqi_piqi.ml would generate
* skewed location counters if we didn't pause it *)letis_paused=ref1(* > 0 means paused; pause() calls can be nested *)(* TODO: provide a more reliable way to resume in case of exceptions rather than
* calling resume () manually; we could use the same apprach as in
* Piqi_util.with_bool *)letpause()=incris_paused;if!tracethenPrintf.eprintf"Piqloc.pause: count = %d\n"!is_pausedletresume()=decris_paused;if!tracethenPrintf.eprintf"Piqloc.resume: count = %d\n"!is_paused(* input and output wire location counters *)leticount=ref0letocount=ref0(* internal locator structure: location can be represented by either location
* itself or by reference to an object which is registered in the location DB *)typet=Locofloc|RefofObj.tletdb:(Obj.t*t)listref=ref[](* append-only part of the DB that is filled by preserve() can't be discarded by
* reset() *)letpreserved_db:(Obj.t*t)listref=ref[]letpreserved_count=ref0(* similar to List.assq but also return the tail of the list after the matched
* element *)letlist_assq_return_tailkl=letrecaux=function|[]->raiseNot_found|(k',v)::twhenk'==k->v,t|_::t->auxtinauxl(* recursively dereference to find the location *)letfind_in_dbxdb~trace=letrecauxkl=letentry,t=list_assq_return_tailklinmatchentrywith|Locloc->loc|Refnew_key->iftracethenPrintf.eprintf"Piqloc.find: %d\n"(Obj.magicx);auxnew_keytinletkey=Obj.reprxinauxkeydbletfind?(trace=false)x=tryfind_in_dbx!db~tracewithNot_found->find_in_dbx!preserved_db~traceletlastloc=ref("undefined",0,0)(* whatever -- initial value *)letsetlocloc=lastloc:=locletaddx=if!tracethen(letf,i,j=!lastlocinPrintf.eprintf"Piqloc.add: %d at (%s, %d, %d)\n"(Obj.magicx)fij;);db:=(Obj.reprx,Loc!lastloc)::!dbletaddloclocx=setlocloc;addx(* add object within the current location and return the object *)letaddretx=addx;x(* add object within the specified location and return the object *)letaddlocretlocx=addloclocx;x(* Discard location information. This allows GC to reclaim memory used by data
* objects that are not referenced from anywhere else other than from location
* db *)letreset()=db:=[];icount:=!preserved_count;ocount:=!preserved_count;()(* Preserve location information by copying the contents of db to preserved_db
* so that existing location info won't be discarded by subsequent reset() calls.
*)letpreserve()=preserved_db:=!db@!preserved_db;preserved_count:=max!icount!ocount;if!tracethenPrintf.eprintf"Piqloc.preserve: preserved_count = %d\n"!preserved_count;reset()(* check if location for the object exists in the loc database *)letdo_check_loc?(trace=false)x=ignore(findx~trace)letdo_add_fake_loc?(label="")x=letfake_loc=("fake"^label,0,0)indb:=(Obj.reprx,Locfake_loc)::!db;letf,i,j=fake_locinif!tracethenPrintf.eprintf"Piqloc.do_add_fake_loc: %d at %d (%s, %d, %d)\n"(Obj.magicx)(Obj.magicfake_loc)fijletadd_fake_loc?(label="")x=if!trace||!checkthen(trydo_check_locx~trace:false;Printf.eprintf"Warning: internal error:\n";Printf.eprintf"Piqloc.add_fake_loc REAL LOC IS ALREADY PRESENT: %d:\n"(Obj.magicx);(* now, printing the actual search sequence *)Printf.eprintf"--\n";do_check_locx~trace:true;Printf.eprintf"--\n";if!crash_on_errorthenraisePiqloc_not_found;withNot_found->do_add_fake_locx~label)letcheck_locx=if!trace||!checkthen(trydo_check_locxwithNot_found->(Printf.eprintf"Warning: internal error:\n";Printf.eprintf"Piqloc.check_loc NOT FOUND: %d\n"(Obj.magicx);(* now, printing the actual search sequence *)Printf.eprintf"--\n";trydo_check_locx~trace:truewithNot_found->();Printf.eprintf"--\n";if!crash_on_errorthenraisePiqloc_not_found))letis_paused_once=reffalseletpause_once()=if!is_paused=0thenis_paused_once:=trueletaddrefdstsrc=ifObj.reprsrc==Obj.reprdst||!is_paused>0||!is_paused_oncethen((* nothing to do except for unpausing when pause_once () was requested *)if!is_paused_oncethenis_paused_once:=false)else(if!trace||!checkthen(tryletloc=finddst~trace:falseinif!tracethen(letf,i,j=locinPrintf.eprintf"Piqloc.addref: %d at %d (%s, %d, %d)\n"(Obj.magicsrc)(Obj.magicdst)fij);(* move the actual location record of the dst to the head of the list --
* this is an optimization that helps subsequent find () calls to work
* faster at a cost of using more memory *)db:=(Obj.reprdst,Locloc)::!dbwith|Not_foundwhenObj.is_int(Obj.reprdst)->(* is integer reference? *)(* this can be a legitimate case; for example, during
* mlobj_to_piqobj conversion, field mode (e.g. `required) is an
* unboxed value that doesn't generate a reference
*
* need to a add a fake reference here so that it doesn't cause more
* errors during subsequent conversions *)do_add_fake_locdst~label:"_addref_not_found_int"|Not_found->(Printf.eprintf"Warning: internal error:\n";Printf.eprintf"Piqloc.addref: %d at %d -- NOT FOUND\n"(Obj.magicsrc)(Obj.magicdst);(* now, printing the actual search sequence *)Printf.eprintf"--\n";trydo_check_locdst~trace:truewithNot_found->();Printf.eprintf"--\n";if!crash_on_errorthenraisePiqloc_not_found;(* adding a fake reference here to stop this error from reoccurring
* later *)do_add_fake_locdst~label:"_addref_not_found"));(* now, adding the actual record to the location database *)db:=(Obj.reprsrc,Ref(Obj.reprdst))::!db;)letaddrefretdstsrc=(* add reference and return *)addrefdstsrc;src(* store resulting object -> source object correspondent in the location DB *)letreferencefx=letres=fxinaddrefretxresletnext_icount()=letres=!icountinif!is_paused=0thenincricount;resletnext_ocount()=letres=!ocountinif!is_paused=0thenincrocount;res