Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

compilerlibs: avoid unnecessary read to +compiler-libs/topdirs.cmi #11382

Merged
merged 3 commits into from Jul 6, 2022

Conversation

Octachron
Copy link
Member

This PR proposes to modifiy the pre-initialization of the toplevel in order to not force clients of compiler-libs.toplevel to read +compiler-libs/topdirs.cmi. (This is the non-hackish alternative to #11318.)

Currently, the ocaml toplevel is initialized in two phases:

  • when the Toploop module is linked (by a call to Topeval.init)
  • before the main loop is started by Topmain.main

The first pre-initialization is done to ensure that the toplevel is at least partially initialized when building custom toplevel with ocamlmktop. A good illustration of such use case would to build a custom toplevel with a custom int printer

(* printer.ml *)
let pi ppf = Format.fprintf  ppf "%03d" ;;

which is automatically loaded in another module

(* loader.ml *)
Topdirs.dir_install_printer Format.err_formatter (Longident.Ldot(Lident "Printer_bis","pi"))

and the custom toplevel is then build with

ocamlmktop printer.cmo loader.cmo -o mytop

When #11199 moved topdirs.cmi to +compiler-libs, it kept compatibility with this use case by loading +compiler-libs/topdirs.cmi in the pre-initialization phase. However, this means that nearly all programs linked with ocamltoplevel.cma will try to read this file as soon as they load Toploop without any control on this behavior. This unavoidable cmi read created some stability in our testsuite (see #11318) but at a higher-level is seem strange to enforce such read to all users for the sake of an edge case.

This PR proposes thus to:

  • remove the read to +compiler-libs/topdirs.cmi during the pre-initialization of the toplevel
  • restore compatibility with ocamlmktop by reading +compiler-libs/topdirs.cmi if we are trying to install a printer and if Topdirs is not in the toplevel environment.

A possible alternative would be to go further in the direction of the second commit and makes the directive for installing printer fully responsible of loading topdirs.cmi (with some caching of the type of printers to avoid reading the cmi at every printer installation). The major difference with this PR approach is that the Topdirs module would no longer be always accessible from the toplevel.

The Toploop module perform some pre-initialisation as soon as the module is
linked. This pre-initialisation only affects modules linked by ocamlmktop
when building custom toplevels. Indeed user-provided module are linked by
ocamlmktop before the toplevel is initialised and started.
This commit removes the read of topdirs.cmi from this preinitialisation
stage. This may broke ocamlmktop users that install printers, but this
is required to be able to write client of the toplevel compiler library
that don't read +compiler-libs/topdirs.cmi. In particular, the
expect_test tool from the compiler testsuite belongs to this category.
toplevel/topdirs.ml Outdated Show resolved Hide resolved
@gasche
Copy link
Member

gasche commented Jul 1, 2022

Doing something magic on printer installation also sounds like a hack to me.

A naive idea would be as follows:

  • have a compiler-libs module toplevel_init.cmo that does nothing else than let () = Topeval.init
  • have ocamlmktop automatically link this module before user-provided modules
  • don't link this module in compiler-libs, but call Topeval.init from Toploop; this probably requires ensuring that calling Topeval.init a second time is a no-op

I think that we do something similar with std_exit which is implicitly linked after user code.

@Octachron
Copy link
Member Author

The binary ocamlmktop is a fine shell wrapper around ocamlc which is already linking a topstart module at the end of the user modules to start the toplevel. It is perfectly possible to link another module at the start of this list (Ocamlktop_topenv_init?).

Copy link
Member

@gasche gasche left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new approach looks nice, thanks! See inline comment.

extra_quote ^ ocamlc ^
" -I +compiler-libs -I +ocamlmktop " ^
"-linkall ocamlcommon.cma ocamlbytecomp.cma ocamltoplevel.cma " ^
"ocamlmktop_init.cmo " ^ args ^ " topstart.cmo" ^
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity: why are topstart and ocamlmktop_init handled so differently in the build system and in the installation layout? I would assume that we could use the same approach for both modules, making the overall system simpler.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Topstart module is used by the toplevel itself, it makes sense to distribute as part of the compiler library. Since ocamlmktop is the only user of the new Ocamlmktop_init module, I feel that it is better to distribute it as a part of a small "ocamlmktop runtime library".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could have a topinit module to complement topstart, and then build ocaml exactly as the result of ocamlmktop called with no user-provided module at all.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @gasche - given that topdirs.cmi has to be special-cased by OS packagers now (i.e. +compiler-libs/topdirs.cmi must always be installed, even in layouts which install the compiler-libs separately) it doesn't seem too bad to put this module there too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather not increase the use of purely effectful module triggered by linking in the toplevel. It seem better to let Topmain responsible for the initialization of the paths and the directives.

The ocamlmktop_init module is a backward-compatibility pre-initialization step that is only required by ocamlmktop and I would rather contain it to ocamlmktop.

@dra27
Copy link
Member

dra27 commented Jul 5, 2022

I don't think this will affect utop, but have you compiled utop with it?

@Octachron
Copy link
Member Author

I have not compiled utop with this patch, but since utop has already been patched to call Topcommon.load_topdirs_path, it should not be affected in anyway?

@gasche
Copy link
Member

gasche commented Jul 5, 2022

My approval still stands. My gut feeling is that the approach could be further simplified by pulling down the thread of the initialization logic (see our discussion inline), but @Octachron has looked more deeply than I have and seems unconvinced -- or maybe he had enough exciting realizations about how the toplevel work and is eager to move to something else for a bit.

In any case, the current PR is an improvement over the ugly hack that is currently in the codebase, so it deserves to be merged in any case.

@gasche
Copy link
Member

gasche commented Jul 5, 2022

@Octachron may want to cleanup the commit history.

I think this PR could deserve a Changes entry, it makes an observable change to the installation layout etc.

This initialization module is linked before any of the user-provided
modules and reads "+compiler-libs/topdirs.cmi". This step is required
before installing any custom printers.
Custom repl build before OCaml 5 are unaware of this requirement and thus
ocamlmktop needs to initialize this part of the toplevel environment itself
to preserve backward compatibility.
@gasche gasche merged commit 3140fb4 into ocaml:trunk Jul 6, 2022
@Octachron
Copy link
Member Author

Speaking of observable change in the installation layout, the Makefile change is not quite right for bytecode only installation. I will fix that shortly.

Octachron pushed a commit that referenced this pull request Jul 11, 2022
compilerlibs: avoid unnecessary read to +compiler-libs/topdirs.cmi
(cherry picked from commit 3140fb4)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants