123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501(************************************************************************)(* * The Rocq Prover / The Rocq Development Team *)(* v * Copyright INRIA, CNRS and contributors *)(* <O___,, * (see version control and CREDITS file for authors & dates) *)(* \VV/ **************************************************************)(* // * This file is distributed under the terms of the *)(* * GNU Lesser General Public License Version 2.1 *)(* * (see LICENSE file for the text of the license) *)(************************************************************************)openUtilopenPp(* Code to interact with ML "toplevel", in particular, handling ML
plugin loading.
We use Fl_dynload to load plugins, which can correctly track
dependencies, and manage path for us.
A bit of infrastructure is still in place to support a "legacy"
mode where Rocq used to manage the OCaml include paths and directly
load .cmxs/.cma files itself.
We also place here the required code for interacting with the
Summary and Libobject, and provide an API so plugins can interact
with this loading/unloading for Rocq-specific purposes adding their
own init functions, given that OCaml cannot unlink a dynamic module.
*)moduleFl_internals=struct(* Fl_split.in_words is not exported *)letfl_split_in_wordss=(* splits s in words separated by commas and/or whitespace *)letl=String.lengthsinletrecsplitij=ifj<lthenmatchs.[j]with|(' '|'\t'|'\n'|'\r'|',')->ifi<jthen(String.subsi(j-i))::(split(j+1)(j+1))elsesplit(j+1)(j+1)|_->spliti(j+1)elseifi<jthen[String.subsi(j-i)]else[]insplit00(* simulate what fl_dynload does *)letfl_find_pluginslib=letbase=Findlib.package_directorylibinletpreds=Findlib.recorded_predicates()inletarchive=tryFindlib.package_propertypredslib"plugin"withNot_found->tryfst(Findlib.package_property_2("plugin"::preds)lib"archive")withNot_found->""infl_split_in_wordsarchive|>List.map(Findlib.resolve_path~base)(* We register errors at for Dynlink and Findlib, it is possible to
do so Symtable too, as we used to do in the bytecode init code.
*)let()=CErrors.register_handler(function|Dynlink.Errormsg->Some(hov0(str"Dynlink error: "++str(Dynlink.error_messagemsg)))|Fl_package_base.No_such_package(p,msg)->letpaths=Findlib.search_path()inSome(hov0(str"Findlib error: "++strp++str" not found in:"++cut()++v0(prlist_with_sepcutstrpaths)++fnl()++strmsg))|_->None)endletdbg_dynlink=CDebug.create~name:"dynlink"()modulePluginSpec:sigtypet(* Main constructor, takes the format used in Declare ML Module.
With [usercode:true], warn instead of error on legacy syntax. *)valof_package:?usercode:bool->string->tvalto_package:t->string(* Load a plugin, low-level, that is to say, will directly call the
loading mechanism in OCaml/findlib *)valload:t->unit(* Compute a digest, a findlib library name have more than one
plugin .cmxs, however this is not the case in Rocq. Maybe we
should strengthen this invariant. *)valdigest:t->Digest.tlist(** bool = true for implicit dependencies *)valadd_deps:tlist->(bool*t)listvalpp:t->stringmoduleSet:CSet.ExtSwithtypeelt=tmoduleMap:CMap.ExtSwithtypekey=tandmoduleSet:=Setend=structtypet={lib:string}letcompare{lib=l1}{lib=l2}=String.comparel1l2moduleSelf=structtypenonrect=tletcompare=compareendmoduleSet=CSet.Make(Self)moduleMap=CMap.Make(Self)moduleErrors=structletwarn_legacy_loading=CWarnings.create~name:"legacy-loading-removed"~category:Deprecation.Version.v9_0Pp.(funname->str"Legacy loading plugin method has been removed from Rocq, \
and the `:` syntax is deprecated, and its first \
argument ignored; please remove \""++strname++str":\" from your Declare ML")letplugin_name_invalid_formatm=CErrors.user_errPp.(strFormat.(asprintf"%s is not a valid plugin name."m)++spc()++str"It should be a public findlib name, e.g. package-name.foo."++spc()++str"Legacy names followed by a findlib public name, e.g. "++spc()++str"legacy_plugin:package-name.plugin,"++spc()++str"are not supported anymore.")letwarn_coq_core=CWarnings.create~name:"coq-core-plugin"~category:Deprecation.Version.v9_0Pp.(fun()->str"\"coq-core\" has been renamed to \"rocq-runtime\".")end(* We would properly load the rocq-runtime cmxs because of the
virtual coq-core findlib package, but we would not initialize the plugin.
eg [Declare ML Module "coq-core.plugins.ltac". Ltac foo := idtac.] would fail
as the grammar for Ltac is not activated. *)letcompat_coq_corelib=letold_prefix="coq-core.plugins."inifCString.is_prefixold_prefixlibthenbeginErrors.warn_coq_core();letold_len=String.lengthold_prefixin"rocq-runtime.plugins."^(CString.sublibold_len(String.lengthlib-old_len))endelselibletof_package?(usercode=false)m=letlib=matchString.split_on_char':'mwith|[lib]->lib|[cmxs;lib]whenusercode->Errors.warn_legacy_loadingcmxs;lib|_->Errors.plugin_name_invalid_formatminletlib=ifusercodethencompat_coq_corelibelselibin{lib}letto_package{lib}=libletload=function|{lib}->ifFindlib.is_recorded_packagelibthendbg_dynlinkPp.(fun()->strlib++str" already loaded")elsebegin(* no point in using Fl_dynload since we [add_deps] to get digests *)letplugins=Fl_internals.fl_find_pluginslibindbg_dynlinkPp.(fun()->strlib++str": linking"++spc()++prlist_with_sepspcstrplugins);List.iterDynlink.loadfileplugins;Findlib.record_packageRecord_loadlibendletadd_depsplugins=letexplicit=Set.of_listpluginsinletpreds=Findlib.recorded_predicates()inletallplugins=Findlib.package_deep_ancestorspreds(List.mapto_packageplugins)inletdeps=List.filter_map(funlib->(* comes from findlib so guaranteed valid *)letplugin={lib}inletexplicit=Set.mempluginexplicitin(* only add not-yet-loaded implicit deps *)ifnotexplicit&&Findlib.is_recorded_packagelibthenNoneelseSome(notexplicit,plugin))allpluginsindbg_dynlinkPp.(fun()->str"for "++prlist_with_sepspc(fun{lib}->strlib)plugins++str":"++fnl()++str"all deps "++prlist_with_sepspcstrallplugins++fnl()++str"filtered "++prlist_with_sepspc(fun(_,{lib})->strlib)deps);depsletdigests=matchswith|{lib}->letplugins=Fl_internals.fl_find_pluginslibinList.mapDigest.filepluginsletpp=function|{lib}->libend(* If there is a toplevel under Rocq *)typetoplevel={load_plugin:PluginSpec.t->unit(** Load a findlib library, given by public name *);load_module:string->unit(** Load a cmxs / cmo module, used by the native compiler to load objects *);add_dir:string->unit(** Adds a dir to the module search path *);ml_loop:?init_file:string->unit->unit(** Run the OCaml toplevel with given initialisation file *)}(* Determines the behaviour of Rocq with respect to ML files (compiled
or not) *)typekind_load=|WithTopoftoplevel|WithoutTop(* Must be always initialized *)letload=refWithoutTop(* Sets and initializes a toplevel (if any) *)letset_toptoplevel=load:=WithToptoplevel;Nativelib.load_obj:=toplevel.load_module(* Removes the toplevel (if any) *)letremove()=load:=WithoutTop;Nativelib.load_obj:=(funx->():string->unit)(* Tests if an Ocaml toplevel runs under Rocq *)letis_ocaml_top()=match!loadwith|WithTop_->true|_->false(* Tests if we can load ML files *)lethas_dynlink=Coq_config.has_natdynlink||notSys.(backend_type=Native)(* Runs the toplevel loop of Ocaml *)letocaml_toploop?init_file()=match!loadwith|WithTopt->t.ml_loop?init_file()|_->()letml_loadp=match!loadwith|WithTopt->t.load_pluginp|WithoutTop->PluginSpec.loadpletload_modulex=match!loadwith|WithTopt->t.load_modulex|WithoutTop->()(* Adds a path to the ML paths *)letadd_ml_dirs=match!loadwith|WithTopt->t.add_dirs|WithoutTopwhenhas_dynlink->()|_->()(** Is the ML code of the standard library placed into loadable plugins
or statically compiled into rocq repl ? For the moment this choice is
made according to the presence of native dynlink : even if bytecode
rocq repl could always load plugins, we prefer to have uniformity between
bytecode and native versions. *)(* [known_loaded_module] contains the names of the loaded ML modules
* (linked or loaded with load_object). It is used not to load a
* module twice. It is NOT the list of ML modules Rocq knows. *)(* TODO: Merge known_loaded_module and known_loaded_plugins *)letknown_loaded_modules:PluginSpec.Set.tref=refPluginSpec.Set.emptyletadd_known_modulemname=ifnot(PluginSpec.Set.memmname!known_loaded_modules)thenknown_loaded_modules:=PluginSpec.Set.addmname!known_loaded_modulesletmodule_is_knownmname=PluginSpec.Set.memmname!known_loaded_modulesletplugin_is_knownmname=PluginSpec.Set.memmname!known_loaded_modules(** Init time functions *)letinitialized_plugins=Summary.ref~stage:Synterp~name:"inited-plugins"PluginSpec.Set.emptyletplugin_init_functions:(unit->unit)listPluginSpec.Map.tref=refPluginSpec.Map.emptyletadd_init_functionnamef=letname=PluginSpec.of_packagenameinifPluginSpec.Set.memname!initialized_pluginsthenCErrors.anomalyPp.(str"Not allowed to add init function for already initialized plugin "++str(PluginSpec.ppname));plugin_init_functions:=PluginSpec.Map.updatename(function|None->Some[f]|Someg->Some(f::g))!plugin_init_functions(** Registering functions to be used at caching time, that is when the Declare
ML module command is issued. *)letcache_objs=refPluginSpec.Map.emptyletdeclare_cache_objfname=letname=PluginSpec.of_packagenameinletobjs=tryPluginSpec.Map.findname!cache_objswithNot_found->[]inletobjs=f::objsincache_objs:=PluginSpec.Map.addnameobjs!cache_objsletperform_cache_objname=letobjs=tryPluginSpec.Map.findname!cache_objswithNot_found->[]inletobjs=List.revobjsinList.iter(funf->f())objs(** ml object = ml module or plugin *)letdinit=CDebug.create~name:"mltop-init"()letinit_ml_objectmname=ifPluginSpec.Set.memmname!initialized_pluginsthendinitPp.(fun()->str"already initialized "++str(PluginSpec.ppmname))elsebegindinitPp.(fun()->str"initing "++str(PluginSpec.ppmname));letn=matchPluginSpec.Map.findmname!plugin_init_functionswith|l->List.iter(funf->f())(List.revl);List.lengthl|exceptionNot_found->0ininitialized_plugins:=PluginSpec.Set.addmname!initialized_plugins;dinitPp.(fun()->str"finished initing "++str(PluginSpec.ppmname)++str" ("++intn++str" init functions)")endletload_ml_objectmname=ml_loadmname;add_known_modulemnameletadd_known_modulename=letname=PluginSpec.of_packagenameinadd_known_modulenameletmodule_is_knownmname=letmname=PluginSpec.of_packagemnameinmodule_is_knownmname(* Summary of declared ML Modules *)(* List and not String.Set because order is important: most recent first. *)letloaded_modules=ref[]letget_loaded_modules()=List.rev!loaded_modules(* XXX: It seems this should be part of trigger_ml_object, and
moreover we should check the guard there *)letadd_loaded_modulemd=ifnot(List.memmd!loaded_modules)thenloaded_modules:=md::!loaded_modulesletreset_loaded_modules()=loaded_modules:=[]typeload_request=|Summary|Regularof{implicit:bool}letif_verbose_loadreqfname=matchreqwith|Regular{implicit}whennot!Flags.quiet->beginletinfo=str"[Loading ML file "++str(PluginSpec.ppname)++(ifimplicitthenstr" (implicit dependency)"elsemt())++str" ..."intryletpath=fnameinFeedback.msg_info(info++str" done]");pathwithreraise->Feedback.msg_info(info++str" failed]");raisereraiseend|Summary|Regular_->fname(** Load a module for the first time (i.e. dynlink it) *)lettrigger_ml_objectreqplugin=let()=ifnot@@plugin_is_knownpluginthenbeginifnothas_dynlinkthenCErrors.user_err(str"Dynamic link not supported (module "++str(PluginSpec.ppplugin)++str").")elseif_verbose_loadreqload_ml_objectpluginendinadd_loaded_modulepluginletunfreeze_ml_modulesx=reset_loaded_modules();List.iter(funname->letname=PluginSpec.of_packagenameintrigger_ml_objectSummaryname)xlet()=Summary.declare_ml_modules_summary{stage=Summary.Stage.Synterp;Summary.freeze_function=(fun()->get_loaded_modules()|>List.mapPluginSpec.to_package);Summary.unfreeze_function=unfreeze_ml_modules;Summary.init_function=reset_loaded_modules}(* Liboject entries of declared ML Modules *)typeml_module_object={mlocal:Vernacexpr.locality_flag;mnames:(bool*PluginSpec.t)list(* bool: if true then implicit dep
XXX should we init_ml_object even for implicit deps? *);mdigests:Digest.tlist}letcache_ml_objectsmnames=letiter(implicit,obj)=trigger_ml_object(Regular{implicit})obj;ifnotimplicitthenbegininit_ml_objectobj;perform_cache_objobjendinList.iteritermnamesletload_ml_objects_{mnames;_}=letiter(implicit,obj)=trigger_ml_object(Regular{implicit})obj;ifnotimplicittheninit_ml_objectobjinList.iteritermnamesletclassify_ml_objects{mlocal=mlocal}=ifmlocalthenLibobject.DisposeelseLibobject.SubstituteletinMLModule:ml_module_object->Libobject.obj=letopenLibobjectindeclare_object{(default_object"ML-MODULE")withobject_stage=Summary.Stage.Synterp;cache_function=(fun_->());load_function=load_ml_objects;subst_function=(fun(_,o)->o);classify_function=classify_ml_objects}letdeclare_ml_moduleslocalmnames=letmnames=List.map(PluginSpec.of_package~usercode:true)mnamesinifLib.sections_are_opened()thenCErrors.user_errPp.(str"Cannot Declare ML Module while sections are opened.");letmnames=PluginSpec.add_depsmnamesinletmdigests=CList.concat_map(fun(_,plugin)->PluginSpec.digestplugin)mnamesinLib.add_leaf(inMLModule{mlocal=local;mnames;mdigests});(* We can't put this in cache_function: it may declare other
objects, and when the current module is required we want to run
the ML-MODULE object before them. *)cache_ml_objectsmnames(* Printing of loaded ML modules *)letprint_ml_modules()=letl=get_loaded_modules()instr"Loaded ML Modules: "++pr_vertical_liststr(List.mapPluginSpec.ppl)letprint_gc()=letstat=Gc.stat()inletmsg=str"minor words: "++realstat.Gc.minor_words++fnl()++str"promoted words: "++realstat.Gc.promoted_words++fnl()++str"major words: "++realstat.Gc.major_words++fnl()++str"minor_collections: "++intstat.Gc.minor_collections++fnl()++str"major_collections: "++intstat.Gc.major_collections++fnl()++str"heap_words: "++intstat.Gc.heap_words++fnl()++str"heap_chunks: "++intstat.Gc.heap_chunks++fnl()++str"live_words: "++intstat.Gc.live_words++fnl()++str"live_blocks: "++intstat.Gc.live_blocks++fnl()++str"free_words: "++intstat.Gc.free_words++fnl()++str"free_blocks: "++intstat.Gc.free_blocks++fnl()++str"largest_free: "++intstat.Gc.largest_free++fnl()++str"fragments: "++intstat.Gc.fragments++fnl()++str"compactions: "++intstat.Gc.compactions++fnl()++str"top_heap_words: "++intstat.Gc.top_heap_words++fnl()++str"stack_size: "++intstat.Gc.stack_sizeinhv0msg