Relocatable OCaml - --with-relative-libdir - #162
Merged
Conversation
dra27
force-pushed
the
installation-tests
branch
from
September 25, 2024 10:52
74a1509 to
5c0e92a
Compare
dra27
force-pushed
the
enable-relative
branch
2 times, most recently
from
September 25, 2024 12:28
6e500a1 to
13d88d6
Compare
dra27
force-pushed
the
installation-tests
branch
from
September 25, 2024 21:55
5c0e92a to
585dd30
Compare
dra27
force-pushed
the
enable-relative
branch
from
September 25, 2024 21:56
13d88d6 to
886f8ad
Compare
dra27
force-pushed
the
installation-tests
branch
3 times, most recently
from
September 29, 2024 10:28
ca4085f to
27bb7a4
Compare
dra27
force-pushed
the
enable-relative
branch
4 times, most recently
from
September 29, 2024 13:25
9491cb7 to
fd5eb89
Compare
dra27
force-pushed
the
installation-tests
branch
2 times, most recently
from
September 29, 2024 20:27
50b5af3 to
3f264c8
Compare
dra27
force-pushed
the
enable-relative
branch
2 times, most recently
from
September 29, 2024 21:07
8f9dc09 to
a157b8b
Compare
dra27
force-pushed
the
installation-tests
branch
from
September 30, 2024 14:17
3f264c8 to
9f8d4eb
Compare
dra27
force-pushed
the
enable-relative
branch
3 times, most recently
from
September 30, 2024 18:08
b1e4a3b to
10198ac
Compare
dra27
force-pushed
the
installation-tests
branch
2 times, most recently
from
October 3, 2024 20:13
9ae7dfd to
3680fcf
Compare
dra27
force-pushed
the
installation-tests
branch
from
October 3, 2024 20:45
3680fcf to
80e6074
Compare
dra27
force-pushed
the
installation-tests
branch
from
October 4, 2024 08:02
80e6074 to
095026f
Compare
dra27
force-pushed
the
installation-tests
branch
2 times, most recently
from
October 19, 2024 09:01
ea49c99 to
b236eec
Compare
dra27
force-pushed
the
enable-relative
branch
from
October 19, 2024 09:01
ccc4851 to
8964c08
Compare
dra27
force-pushed
the
installation-tests
branch
from
October 22, 2024 09:56
b236eec to
09aa33a
Compare
MisterDA
reviewed
Nov 25, 2024
| [AC_CHECK_HEADERS([unistd.h],[AC_DEFINE([HAS_UNISTD], [1])])]) | ||
|
|
||
| AC_CHECK_HEADER([math.h]) | ||
| AC_CHECK_HEADERS([unistd.h],[AC_DEFINE([HAS_UNISTD])]) |
There was a problem hiding this comment.
Suggested change
| AC_CHECK_HEADERS([unistd.h],[AC_DEFINE([HAS_UNISTD])]) |
This isn't needed.
Owner
Author
There was a problem hiding this comment.
This is part of a much worse merge/rebase artefact!
| May be a relative path if the compiler was configured with | ||
| [--enable-relative]. | ||
|
|
||
| @since 5.1 *) |
There was a problem hiding this comment.
Suggested change
| @since 5.1 *) | |
| @since 5.3 *) |
Owner
Author
There was a problem hiding this comment.
Low down the list to fix for now - but it certainly won’t be 5.3!! 🤣
| and {!standard_library_default} into account, but not taking CAMLLIB or | ||
| OCAMLLIB into account. | ||
|
|
||
| @since 5.1 *) |
There was a problem hiding this comment.
Suggested change
| @since 5.1 *) | |
| @since 5.3 *) |
| val standard_library_relative: bool | ||
| (** Whether {!standard_library_effective} is computed relative to the runtime. | ||
|
|
||
| @since 5.1 *) |
There was a problem hiding this comment.
Suggested change
| @since 5.1 *) | |
| @since 5.3 *) |
Owner
Author
|
Thanks for looking at this, of course, but these PRs are for my own purposes and aren’t ready for review |
* Add test * natdynlink: dlclose on error * Promote test * Only dlclose if unit has not initialized, fix test * Accept * Call ndl_register after CRC check * Accept * Simplify * Accept * Changes * Fix test script * Fix test script * Accept * Disable test on Windows
There are systems without /bin/bash (Free/Net/OpenBSD, some Linux distributions) We use /usr/bin/env to run bash. Another option is to revise these scripts to use sh (see shellcheck.net, and POSIX), but I'm not going down the rabbit hole right now. ;)
Co-authored-by: v-gb <valentin.gatienbaron@gmail.com>
…l#14397) * add immutable array literal example to apidoc * manual: add iarray to builtin type list * manual: add floatarray to builtin type list * manual: Iarray.t in type-based array literal disambiguation
Both Cygwin and MSYS2 are now consistently detected on MSYS2. In particular, this means that ./configure --prefix $PWD/install and similar will cause the prefix to be correctly translated to a Windows path, as already happens on Cygwin.
Previously, the --prefix argument was always normalised with cygpath -m which meant that regardless of the argument, the paths used in the compiler would always use slashes. This behaviour is preserved if a slash is detected in the argument, i.e. the caller explicitly uses mixed notation (e.g. `--prefix=C:/Prefix` or `--prefix $PWD/install`). In particular, it means that a Cygwin-style path will be correctly converted to a Windows-style path. If the path uses backslashes, then it is still converted to use forward slashes for the installation commands, but the backslashes are otherwise preserved and used within the build itself.
The runtime-launch-info file includes the location of the binary directory. The compiler is extended so that . refers to the directory of the compiler binary.
By default, ocamlrun first tries to resolve argv[0] to determine where the bytecode image is and then tries opening the executable image itself. This is obviously correct for ocamlrun, when being called using a shebang or executable header, but it's not correct for -custom executables where we _know_ that the bytecode image should be with the executable. To achieve this, a new mode is added to caml_byte_program_mode (and the existing ones renamed) such that caml_byte_program_mode is now STANDARD (for ocamlrun - the existing behaviour), APPENDED (for -custom executables - the new behaviour) and EMBEDDED (for -output-complete-exe/-output-obj - the original use of it). The mode is also set directly by the linker, rather than having a default in libcamlrun which is then overridden by the startup code for -output-complete-exe. In the new APPENDED mode, if caml_executable_name is implemented (i.e. it returns a string) then this file _must_ contain the bytecode image and no other mechanisms are used. On platforms where caml_executable_name is not implemented, APPENDED falls back to STANDARD for compatibility. Technically, this stops an argv[0] injection attack on setuid/setgid -custom bytecode executables, although setuid should be used with -output-complete-exe, if at all.
Previously, the bytecode runtime just used OCAML_STDLIB_DIR from build_config.h. This value is now stored once in dynlink.o as caml_runtime_standard_library_default.
%standard_library_default allows Config.standard_library_default to be converted to a compile-time derived value, as with existing compile-time constants such as %backend_type, etc. This paves the way for allowing Config.standard_library_default to be changed at link-time, rather than fixed when the Config module itself is compiled.
Allows the default location used by the bytecode runtime for the Standard Library to be overridden when creating bytecode executables.
Config.standard_library_default is now implemented using the %standard_library_default primitive. This allows a convenient test which can be added for `-set-runtime-default`. The change also makes the host-like nature of of Config.standard_library_default clearer, as the build of the cross-compiler must now (correctly) specify the location of its (target) Standard Library.
When configured with --with-relative-libdir, the runtime uses the directory of the executable to determine the location of the Standard Library. Thus, ocamlrun and the compilers look for ../lib/ocaml by default. This is implemented by changing caml_standard_library_default to be a relative path, and then computing the actual value at startup (for bytecode) and when queried (for native). Executables (and objects) produced by the compiler always have an absolute value of caml_standard_library_default. ocamlc.opt and ocamlopt.opt are built using -set-runtime-default to force caml_standard_library_default to be a relative value.
mingw-w64 is based on GCC, so supports -fdebug-prefix-map, but the test for it is skipped in configure. The test is no longer skipped (which means that Config.c_has_debug_prefix_map returns true) but the flag is still explicitly not used by the compilers (as before).
Indication as to whether ocamlopt assembles files via the C compiler or by calling the assembler directly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is the second of three PRs which implement Relocatable OCaml as proposed in
ocaml/RFCs#53. The series of changes in this PR combine to allow the absolute location of the Standard Library (e.g./usr/lib/ocaml) to be removed from both the C runtime (ocamlrunandlibcamlrun.a, etc.) and also from theConfigmodule in theocamlcommoncompiler-libs library. The patches address sections 3 & 4 of the RFC.The key changes are:
%standard_library_defaultallows an OCaml program to determine the default of the Standard Library as a compile-time constant. In particular, it means that rather thanConfig.standard_library_defaultbeing fixed once whenconfig.mlitself is compiled, it allows the compiler to determine its value each time a program is linked.-set-runtime-defaultallows the calculated value for the%standard_library_defaultprimitive to be overridden when linking an executable.configureoption,--with-relative-libdir, which allows the compiler to be configured to expect to find the Standard Library in a location specified relative to where the compiler is running from. When./configureis run with no arguments, the default location of the Standard Library is/usr/local/lib/ocamland binaries are installed to/usr/local/bin. The equivalent relative compiler would be configured with--with-relative-libdir=../lib/ocaml.BUILD_PATH_PREFIX_MAPsupport added in#1515and usage of-fdebug-prefix-map-style options to the C compiler to make the compiler's artefacts considerably more reproducible when--with-relative-libdirhas been specified.These changes necessitate considerable churn in the start-up routines for the bytecode runtime, and in the way
argv[0]is being processed. While this code is in motion, there are several additional changes which aren't a strict requirement of the main change, but are here because this is all being shaken up:configurenow correctly recognises that it is a Cygwin-like environment, and usescygpathet al as necessary.--prefix(e.g../configure --prefix='C:\OCaml') then these are preserved in the resulting compiler (this implements a slightly more sensible version of#658). Apart from grinding my own axe where this is concerned, it prevents "mixed" slash paths (which look particularly amateur) from being generated when the compiler is configured--with-relative-libdir.-custom, it is possible to direct the resulting executable to load a different bytecode image by manipulatingargv[0]. This issue is fixed here, and on normal systems (wherecaml_executable_nameis implemented), an executable compiled with-customonly loads the bytecode image it was compiled with and can no longer be directed to a load a different one.ocamloptnow callsasdirectly, just as Linux does, rather than going viagcc.Technical background
The crux of this PR is this pair of innocuous-looking lines:ocaml/runtime/dynlink.c
Line 91 in 0728f6a
and
ocaml/utils/config.generated.ml.in
Line 23 in 0728f6a
which, by default, are:
and
Here,
/usr/localis the installation prefix. A consequence of being relocatable, as defined in the RFC, is that this string cannot appear (in any encoding!) in the compiler binaries. At present, the string is present in theConfigmodule and in the bytecode runtime. Its presence in the bytecode runtime means that essentially all bytecode executables contain the location of the OCaml Standard Library, either directly, if compiled with-output-obj,-output-complete-exe, etc., or through theocamlrunexecutable, if compiled with default options or-custom. Conversely, native executables only contain the location of the OCaml Standard Library if they link theConfigmodule from theocamlcommon.cmxa1.For the compiler or runtime, installed to
/usr/local/bin/ocamloptor/usr/local/bin/ocamlrun, it is straightforward forocamlopt/ocamlrunto determine the directory containing the running executable (/usr/local/bin) and to instead contain the location../lib/ocamland thus combine the two to derive/usr/local/lib/ocaml. In the code,../lib/ocamlis the default value, which is static and may be explicit-relative or absolute, where the calculated/usr/local/lib/ocamlis the effective value, which is dynamic and must be absolute. Thus, at module initialisation in:https://github.com/ocaml/ocaml/blob/0728f6af2aae32a97c2a7a1214c25736a26a479b/utils/config.common.ml.in#L23-L30
we can instead calculate the effective value
"/usr/local/lib/ocaml"from the defaultstandard_library_default = {|../lib/ocaml|}.If only it were that straightforward! The calculation of the effective location requires the caller to agree to be invoked from a binary located in a specific place relative to the effective location. The compiler is not the only consumer of
Config.standard_library. Consider the trivial programdisplay.ml:compiled with:
Today, the two final commands display the same result. If
Config.standard_libraryalways uses the effective value as the default for the Standard Library location,ocamlopt -wherewill continue to display/usr/local/lib/ocamlbut./displaywill suddenly display/home/dra/work/../lib/ocaml(or something similar, but nonetheless not/usr/local/lib/ocaml).Finally, bytecode executables pose an additional problem. Let us extend the trivial program slightly:
compiled with:
$ /usr/local/bin/ocamlc -o display -I +unix -I +compiler-libs unix.cma ocamlcommon.cma display.mlSupposing we have two installations of the same version and configuration of OCaml, one in
/usr/localand another in~/.opam/default. In this case, we have:Now, executing
./displayinvolves loadingdllunixbyt.sofrom thestublibsdirectory. In these two invocations:there are two important things we reasonably expect to happen:
./displaywill display the same path in each case, which will be/usr/local/lib/ocaml(Config.standard_libraryfor the compiler it was built with)ocamlrunwill loaddllunixbyt.sofrom its own installation - i.e./usr/local/bin/ocamlrunwill load/usr/local/lib/ocaml/stublibs/dllunixbyt.soand~/.opam/default/bin/ocamlrunwill load~/.opam/default/lib/ocaml/stublibs/dllunixbyt.soThis leads to what I think is not an entirely obvious distinction. There are two locations to consider: the default location of the Standard Library for the runtime, and the default Standard Library location for the mutator.
Config.standard_library_defaultrefers to the mutator (i.e. program's view), but this may not necessarily be the same value as the runtime needs forOCAML_STDLIB_DIRinruntime/dynlink.c. In practice, this only affects standalone bytecode images - i.e. the situation where the runtime executable and the bytecode image are in separate locations. In all other compilations (including native code; though the native runtime doesn't ever care about the location of the Standard Library), there are still two values, but they are always the same.The changeset is best reviewed commit-by-commit (and, I'm afraid, armed with the "Technical background" explanation...):
enum caml_byte_program_modeis augmented with a newAPPENDEDoption which is used for-custom.caml_mainthen usescaml_byte_program_modeto differentiate between a#!-style "standalone" bytecode image running viaocamlrunand a-customexecutable. While moving the code around, I renamed theCOMPLETE_EXEenumeration constant toEMBEDDEDas the mode is also used with-output-obj.runtime-launch-infois one of two places where the binary directory, rather than library directory is embedded in a file. The format forruntime-launch-infois trivially extended to recognise.as referring to the directory containing the compiler (note thatruntime-launch-infois not a generally-configurable file - supporting arbitrary relative paths here would be a hypothetical installation where the runtime executables are installed to a different location from the compiler, which isn't supported or needed at present).caml_runtime_standard_library_defaultfor the runtime default value, principally so thatOCAML_STDLIB_DIRis only referred to in once place.%standard_library_defaultis the most involved change, introducing a compile-time constant to retrieve the mutator default value. In native code, the Standard Library location is only present if theConfigmodule is linked.ocamlopttherefore has to create the string as part of linking an executable, which is done using a similar mechanism to thecaml_apply,caml_curryandcaml_sendfunctions - a new field in the cmx header records that the compilation unit uses the%standard_library_defaultprimitive. At link-time, if any of the compilation units has set this flag, the linker createscaml_standard_library_natcontaining the location of the standard library and synthesises references to it. For bytecode, a similar technique is used, except that the linker already has the full list of%-primitives which are used by the program, so there is no need for cmo format to be changed. There are various strategies on offer for exactly where and how the value is stored. It is needed to link the bytecode runtime (i.e. the value has to be included in the same places where C tables of primitives and so forth are generated). It is also has to be included in bytecode images where no C is being produced. Given that-output-complete-exeand so forth share the same value for both runtime and mutator, for bytecode images I've opted to add a newOSLDbytecode section containing the mutator value, which is read tocaml_standard_library_default(not tocaml_runtime_standard_library_default). Then, as with the other compile-time constants, it's just a matter of adding anothercaml_sys_const_primitive inruntime/sys.swhich returns an OCaml copy of that string. I've proposed this as a%-primitive, rather than a "known" C primitive for two reasons. Firstly, the behaviour of this is a compile-time constant (i.e. something which the compiler must work out when linking the program), and the other compile-time constants are%primitives. Secondly, the standard library location only wants to be embedded when it's actually needed; if the native runtime used a C primitive, there would have to be a sentinel value or a default forcaml_standard_library_nat- it seems worse to have a C primitive sat in the runtime which could return an invalid value (i.e.caml_sys_const_standard_library_defaultalways returns a correct value in bytecode, but if exposed in native code, it would be possible to end up with a program which called it, but got a default empty string back). Given that these primitives are never intended to be called by user-code (because of the difference in linking), unlike in#13465, I think it's better to have the C primitive for bytecode only, and the completely synthesise the function in ocamlopt only when needed.%standard_library_defaultprovides a mechanism which means that the string constant carved intoutils/config.mlwhen the compiler was built can now be determined when the compiler is run. The default value determined by the compiler is simply that same absolute path. The-set-runtime-defaultprovides a mechanism to change that default value when linking a specific program.Config.standard_library_defaultto be changed to be the result of%standard_library_default. This mechanism is a necessary consequence of%standard_library_defaultfor cross-compilation. When linking a cross-compiling version ofocamlopt, that compiler is built with a compiler which uses a host standard library but the resulting cross-compiler it's linking should default to a different target standard library (see the change inMakefile.cross).%standard_library_defaultmeans that"/usr/local/lib/ocaml"has now moved theConfigmodule to the executables themselves. Where before,ocamloptandocamlc.byteboth had the string location linked in code viaConfig.standard_library, it's now instead embedded incaml_standard_library_natforocamlopt(andocamlc.opt) and in anOSLDsection forocamlc.byte. The next commit then allows explicit-relative values of this path to be interpreted by both the runtime and compiler. This is then activated using-set-runtime-default, so thatcaml_standard_library_natis changed to be"../lib/ocaml". Most of the complexity here arises from the fact that the bytecode C runtime needs to be able to do this. Especially in the light of the work onld.confin#PR1, rather than implementing the logic in both C and OCaml, the logic is implemented just in C and theConfigmodule uses a newcaml_sys_get_stdlib_dirsprimitive, which is passed the result of%standard_library_defaultand returns both the effective value and also the directory containing the executable (which is used to implementConfig.bindirforocamlmklib). Note, in passing, thatocamlmklibon Windows now searches for tools in the same way as on Unix, as there's no need for the PATH-search previously there. The C implementation itself is incaml_locate_standard_librarywhich is implemented separately for Unix and Windows inruntime/unix.candruntime/win32.c. The tools used to implement this (dirname,realpath, etc.) are not equivalently available on Windows, by which I mean that the functions don't have exactly equivalent semantics. In particular, an exactdirnameis not available on Windows, butGetFullPathNamehas the required semantics for this specific use and actually has an option to return the dirname (albeit indirectly). On this occasion, it therefore seemed easier to make the entire "work out the location of the Standard Library" operation platform-specific, than to go to more effort to construct platform-specific building blocks for a generic version of this function.ocamlc.byte -whereproduces the expected result regardless of whether the original or copiedocamlrunis used. If theCI: Full matrixlabel of#14013is added, then each of the jobs builds an additional compiler with the alternate configuration (i.e. the jobs which by default are--without-relative-libdirthen have an additional--with-relative-libdirbuild created, and vice versa). These two compilers are likewise used for the "cross-runtime"ocamlc.byte -wheretest.BUILD_PATH_PREFIX_MAPand directly adding-ffile-prefix-mapto our internal C flags. Ignoring#!lines andRNTMsections (which are addressed in the final PR), when building with--with-relative-libdir, none of the binaries in the build contain either the build path or the installation prefix on Windows (with mingw-w64) or Linux.Footnotes
Note that prior to
#119965.3.0, native code executables which linkeddynlink.cmxaalso contained the location of OCaml Standard Library through the copy of theConfigmodule in theDynlink_compilerlibslibrary. ↩