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

Cross compiler #13

Closed
wants to merge 20 commits into from
Closed

Cross compiler #13

wants to merge 20 commits into from

Conversation

shym
Copy link
Owner

@shym shym commented Oct 2, 2024

This PR makes it possible to build OCaml cross compilers without the various patches that all cross-compiler projects apply at the moment. It builds upon many previous PRs that have made this a lot simpler now than a couple of years ago.

Context

Recall that building a compiler involves 3 system types described by triplets:

  • build: the machine on which the compiler is built, usually automatically detected by autoconf's configure
  • host: the machine on which the compiler will run,
  • target: the machine on which the code generated by the compiler will run.

So when build != host, we are cross-compiling. When host != target, we are building a cross compiler. This PR tackles issues in that latter case, cross compilers.

Currently, the only supported configurations are when build ~ host = target, where by ~ I mean that the code generated for host runs on build even when they differ (such as when build is Cygwin and host is Windows (MSVC or MinGW)).

Even though this is not officially supported yet, many projects use OCaml cross compilers. All those projects generate a cross compiler by assuming a non-cross OCaml compiler is available in PATH (where “non-cross” means generating code that will run on build = host).

Summary

This PR proposes the following main improvements for building cross compilers.

  • It disambiguates host and target in configure, where currently most tests about host in fact mean to test target.
  • It fixes the implementation of the %ostype_* primitives that were reporting the OS type of the host instead of the target.
  • It teaches sak about cross compilation between Unix and Windows and autodetect the C toolchain to build it if needed.
  • When building a cross compiler, it adds new make targets: crossopt to drive the build of the cross compiler and installcross to drive its installation.

Note that all the changes of this PR are transparent in the non-cross-compiler cases.

This PR comes with a CI workflow to help review the PR but not intended to be merged (at least, not as is). This CI workflow runs on x86 Linux and:

  • builds a non-cross compiler
  • builds a cross compiler to Windows MinGW and:
    • compiles a tiny program that displays Sys.os_type, Sys.unix, etc.
    • runs that program on Windows
  • builds a cross compiler to arm Linux and:
    • compiles a tiny program that displays Sys.os_type, Sys.unix, etc.
    • runs that program using qemu-user
  • tests the sak cross compiling behavior (Unix to Windows and vice versa).

How to test it

To build a cross compiler you first need to have a non-cross compiler of the same version installed in your $PATH. Note that they need to be of the same version since the cross compilers will contain the runtime of the non-cross compiler because the C toolchain that will be detected during the configure for the OCaml cross compiler will be the C cross compiler to target.

Build the non-cross compiler

It could be by creating an opam trunk switch or by building manually as usual. For instance,

$ ./configure --prefix=$PWD/non-cross # and any other option you want...
$ make -j
$ make install
$ export PATH="$PWD/non-cross:$PATH"

It may be useful to keep a note of the flags used to link with zstd, if any:

$ grep ZSTD_LIBS Makefile.config

Build the cross compiler

After cleaning the working directory, call configure with the target triplet, possibly setting where the library will be installed on the target. So for instance, with the GCC MinGW cross compiler installed, it could be:

$ ./configure --prefix=$PWD/cross --target=x86_64-w64-mingw32 --with-target-libdir='C:\somedir' ...
$ make crossopt -j
$ make installcross

Notes:

  • The cross compiler to Windows needs flexdll to link the binaries. A simple way to get it is to use the flexdll submodule (git submodule update --init if needed) and let the crossopt target bootstrap flexdll.

  • If pkg-config --libs libzstd doesn’t return the flags used to link the non-cross runtime, you can specify them with:

    $ make crossopt -j HOST_ZSTD_FLAGS="..."
    

Use the cross compiler

If you have built a cross compiler to a Unix target, you can simply run as usual:

cross/bin/ocamlopt.opt -o test test.ml

If you have built a cross compiler to Windows on a Unix host, you must first make sure that ocamlopt can find the flexlink executable in $PATH when it needs to link. Boostrapping flexdll builds a flexlink.exe (note the .exe), so you can:

ln -s flexlink.exe cross/bin/flexlink
(export PATH="$PWD/cross/bin:$PATH"; ocamlopt.opt.exe -o test.exe test.ml)

or any other possibility to make sure flexlink can be invoked.

Overview of the commits

This PR is meant to be reviewed commit per commit.

  • Fix some small issues
    • 378e958 Add the missing $(EXE) for stripdebug invocations
    • 665c3e0 Include the runtime directory only for ocamltest
  • Fix configure to handle properly the cross-compiler cases
    • 58584d9 Use target instead of host to detect the C toolchain
    • 15d5e8a Use target instead of host when relevant in configuration
    • c84d2a8 Detect and use {CC,...}_FOR_BUILD to build sak
    • 561a67a Check that the OCaml versions are compatible for a cross compiler
    • e6fb79b Add a configurable library directory on target
    • 4876024 Detect flexlink only on relevant targets
  • Add a Makefile.cross with the cross-compiler recipes
    • 2495303 Add a Makefile.cross for recipes to build a cross compiler
    • ce91988 Enable bootstrapping flexdll in the cross-compiler setting
  • Fix sak in cross-compiler cases
    • 58baaa2 Add cross-compilation cases to sak
  • Fix the ostype_* primitives
  • Misc
    • 8049dd1 Document cross compilers in INSTALL.adoc
    • 1f40f9e Add a changelog entry for cross compilers
    • 613e2c2 DROPME Add a CI workflow to test cross compilers

@shym shym force-pushed the native-cross branch 2 times, most recently from b230c19 to feac416 Compare October 2, 2024 17:10
@shym shym changed the base branch from no-push-ci to trunk October 3, 2024 08:42
@shym shym force-pushed the native-cross branch 2 times, most recently from d7df83a to d64fde9 Compare October 3, 2024 13:32
@shym shym force-pushed the native-cross branch 3 times, most recently from fe87b2e to 594f018 Compare October 4, 2024 13:00
goldfirere and others added 17 commits October 4, 2024 09:01
The `runtime` directory must be `-I`ncluded only since PR#12896 and only
in ocamltest.
When building an OCaml cross compiler, two OCaml compilers are really
involved, where the non-cross compiler is used to build the cross one.
Most cross-compiler projects do that by overriding variables such as
`CAMLOPT` to point to the non-cross compiler during the build of the
cross compilers. In these use cases, adding the explicit `-I runtime`
makes them generate the cross compilers linking in the cross runtime
(which naturally fails) instead of the build/host runtime that the
non-cross compiler would use without `-I runtime`. To re-enable those
use cases, this patch moves the addition only on `ocamltest/%` targets.
Recall that the only currently officially supported configurations are
when `build` ~ `host` = `target`, where '~' means that the code
generated for `host` runs on `build` even when they differ (such as when
`build` is `x86_64-pc-cygwin` and `host` is `x86_64-pc-windows` (MSVC)
or `x86_64-w64-mingw32`).

Still, many projects use OCaml cross compilers. All those projects
generate a cross compiler by assuming a non-cross OCaml compiler is
available in `PATH` (where non-cross means generating code that will run
on `host`). For that they need a C compiler and binutils for `target` in
order to build the runtime. (Note that the non-cross compiler will link
its own (`build`/`host`) runtime into the generated `.opt` cross
compilers rather than the just-compiled target runtime.)

In that setup the runtime will run only on the `target` so this commit:

- sets `cross_compiling` by comparing `build` to `target` (rather than
  to `host`), as this variable will be used later
- uses `target` to set up the tool prefix,
- temporarily assigns `host*` values to `target*` values during the
  libtool configuration, as this detects a `build` to `host` toolchain.

Note that all these changes are transparent when `host` = `target`.
As the C toolchain used being configured is generating code for
`target`, use `target` in every test that is done according to the
toolchain.

Note that all these changes are transparent when `host` = `target`.
Import `ax_prog_cc_for_build` from the Autoconf Macro Archive to detect
the C toolchain for the build machine when (and only when) we are
generating a cross compiler, namely when code generated for the target
doesn't run on the build machine
Use this build C toolchain by default to compile and link `sak`
Link `sak` by calling directly `SAK_CC` instead of `MKEXE_VIA_CC` to use
by default the same compiler to link than to compile `sak`, instead of
always requiring both to be overridden; this assumes that the full set
of flags that end up in `MKEXE_VIA_CC` are not really necessary to link
`sak` since it is a really simple program
When building a cross compiler using an already built non-cross
compiler, check that they are of the same version as a sanity check, as
the cross compiler will be linked using the OCaml code in the source
tree and the C runtime from the non-cross compiler
Add a `--with-target-libdir` option to `configure` and assign a Makefile
`TARGET_LIBDIR` variable with it
Use the value of `LIBDIR` by default for this new variable
Use `TARGET_LIBDIR` to define the `OCAML_STDLIB_DIR` macro used by the
runtime

When building a cross compiler, the OCaml standard library has no reason
to be found at the same paths on the host and on the target. This allows
users to provide a path that is meaningful to look for libraries to link
dynamically on the target.
On Unix platforms, make sure it is possible to have a `flexlink`
executable in `PATH` (which is useful for instance when using a cross
compiler to Windows), and still be able to configure and build a
non-cross compiler
Add a new `Makefile.cross` that gets enabled when building a cross
compiler, aka when host is different from target
Define two new (phony) targets:
- crossopt, to build the cross compilers to the native target (and what
  is required for such cross compilers to work)
- installcross, to install the cross compilers
Add a rule to build flexdll in the cross-compiler setting, namely
building flexdll on Unix, by driving its `Makefile` so that Windows
resources are not built and only the .opt version is really built (and
copied to the byte binary directory nevertheless)
Use the `-o` flag to tell `make` to never try to rebuild `flexlink` (as
it would otherwise, according to the dependencies in the main
`Makefile`)
When building an OCaml cross-compiler Windows to Unix, `sak` gets a
Windows string (of UTF-16 `wchar_t`s) and must produce a Unix string (of
UTF-8 `char`s). And vice versa when building a cross-compiler Unix to
Windows. To make this possible, this commit splits the
`encode-C-literal` command into two commands with specific encodings of
the result: `encode-C-utf8-literal` and `encode-C-utf16-literal`.

Instead of pulling in a library (and the problems of linking with it)
for the task, this commit adds the specific and simple UTF-* encoders
and decoders that are needed and uses them only when building a
cross-compiler (the result is unchanged in non-cross-compiler settings).

In the cross-compiler UTF-8 case, the non-printable characters are
encoded to get a safer generated code.
Define `Config.target_os_type` so that:

- the `%ostype_*` primitives correspond to the target OS type rather
  than the host OS one,
- the default executable name is the expected default of the target
  platform.
This CI workflow is not intended to be merged on the main branch as is.
Its purpose is to help review the cross compiler.

If it is deemed interesting to keep it for special cases (to run the
test from time to time, rather less regularly), it could be triggered on
explicit `workflow_dispatch` or on a specific label. In that case, it
might make sense to amend it, in particular the tests for the new cases
in `sak` could be dropped.
@shym shym closed this Oct 4, 2024
@shym shym deleted the native-cross branch October 4, 2024 14:49
@shym
Copy link
Owner Author

shym commented Oct 4, 2024

Opened upstream as 13526.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants