Skip to content

Re-organise export control macros in headers#1633

Closed
dra27 wants to merge 32 commits intoocaml:trunkfrom
dra27:fvisibility
Closed

Re-organise export control macros in headers#1633
dra27 wants to merge 32 commits intoocaml:trunkfrom
dra27:fvisibility

Conversation

@dra27
Copy link
Copy Markdown
Member

@dra27 dra27 commented Feb 25, 2018

A long time ago, in a commit far, far away (ddd99c7, to be precise), @xavierleroy blessed the OCaml Windows port1 with dynamic loading of C code in the bytecode runtime. This introduced the CAMLexport, CAMLprim and CAMLextern macros. CAMLprim and CAMLexport carried the same purpose and were to be used in definitions, except that CAMLprim was also intended to identify C functions implementing primitives compiled into the runtime systems (previously identified with magic /* ML */ comments). CAMLextern was introduced in the headers in place of the extern keyword. Together, CAMLexport and CAMLextern allowed the C files on Windows to be decorated with __declspec(dllimport) and __declspec(dllexport) as appropriate.

A slightly less long time ago, in a somewhat closer commit (3958a92, to maintain precision), @alainfrisch blessed the OCaml Windows ports2 with dynamic loading of C code in both the bytecode and native runtimes with the introduction of FlexDLL. FlexDLL eliminated the need for CAMLexport and CAMLextern, but the macros remained.

There was one subtle problem, however. The simplest explanation of how FlexDLL works is that it defers the job of the linker from link-time to run-time, leaving easy work to the Windows loader, but performing all the rest of the required code patching itself. For x86, this pretty much just works. x86_64 has one problem: the compiler correctly assumes that the final size of a compiled image will be < 2GiB (part of the Portable Executable spec for Windows) and so merrily generates rip-relative loads, jumps and calls for symbols which it reasonably believes will be statically linked (because none of them is marked __declspec(dllimport)). This causes problems if a DLL is loaded dynamically by the runtime at a base address which is more than 2GiB away from the runtime's own code, since rip-relative relocations are limited to ±2GiB. In this case, FlexDLL displays an error message and dynamic linking fails. At the time, it appeared that simply compiling everything with a preferred base address of 0x10000 caused the Windows loader to keep everything in the low 32-bit section of the address space and FlexDLL 0.20 included this workaround.

In the intervening time, the mingw-w64 compiler replaced the older 32-bit only mingw compiler and Cygwin64 also appeared. Cygwin requires DLLs in any one installation to have unique base addresses at which they can always be loaded, since it requires the Windows loader to behave repeatably to allow Cygwin's emulation of fork(2). We've known for a while that this caused problems for any OCaml program which loaded more than one DLL dynamically (for example, programs using both systhreads and unix) and the CI contains a temporary workaround. However, this workaround has recently stopped working, because Cygwin64 now requires DLLs to load into at least 0x2:00000000 and the rebase utility doesn't permit forcing a lower address. This means that on Cygwin64, rip-relative addressing will always fail.

Fortunately, this can be solved using thunks, and ocaml/flexdll#52 contains a proposed patch which alters FlexDLL to generate branch islands. This solves the problem for Cygwin64 and the same solution works for the mingw64 port. As long as GCC's code model (-mcmodel) is left at its default, changes made in mingw-w64 for Cygwin64 mean that gcc always accesses external variables via the Global Offset Table which means that data symbols are never accessed directly (this can be seen by trying to compile with -mcmodel=small).

However, the Microsoft C compiler has no notion of x86_64 code models. The most reliable way to fix this seems to be to ensure that data symbols are all marked __declspec(dllimport).

Congratulations for getting this far, that sets the scene for revising these export control macros. One solution would be to just to re-use CAMLextern to provide the __declspec(dllimport) as it used to be used. This isn't wholly satisfactory, as it would mean that the symbols would always be accessed indirectly, even when it's not strictly necessary. What is really wanted is to have only the data symbols marked with CAMLextern, at which point the Microsoft C compiler will generate code on the same assumptions as the GCC medium code model.

So far, so good. The only problem is that these annotations need to be maintained in the headers, but provide benefit for just one variant of OCaml. The suggestion in this GPR is to switch the headers to start taking advantage of GCC 4's visibility attribute, as this is related to __declspec. The commit history started out on a similar path to #1207, hacking the GCC attribute onto the CAMLprim and CAMLexport mechanism. The problem with this is two-fold: technically, the GCC attribute should be specified in consumers of the library as well as in the library itself (i.e. it is the declarations which should have the attribute, not the definitions) and the __declspec(dllimport) attribute obviously should also be specified on the declaration, not the definition.

For these reasons, I've chosen to overhaul the export macros completely. The changes now mean that:

  • For GCC 4 or later, the distribution is compiled with -fvisibility=hidden, thus requiring all symbols to be explicitly exported. This is in CFLAGS only, and so does not affect files compiled with ocamlc -c or libraries built with ocamlmklib.
  • CAMLprim had been co-opted to do things beyond its original intention. I've introduced CAMLstub for use outside byterun which should be used in the same way - i.e. to mark a C function whose purpose is to be used as a primitive (this includes unboxed native code primitives), especially as these are not normally mentioned in C header files.
  • CAMLexport has been completely removed, because we don't need to use __declspec(dllexport) (the macro remains, because it was a public part of the header).
  • CAMLextern has been dropped, as has the use of the extern keyword for function declartions and a new macro CAMLdata has been introduced which is used to mark the data symbols which are to be exported. Again, CAMLextern remains only because it was public before.

This GPR also contains a few minor fixes: caml_code_fragments_table was defined only through C tentative declarations in the headers. This is now replaced with a tentative declaration in misc.c -- the headers all use CAMLdata (i.e. extern). caml_alloc_float_array was incorrectly declared CAMLprim in #534 and this has been fixed (with an associated bootstrap). caml_stat_strdup_to_utf16 was also declared extern and defined inline.

There are a few questions arising from going through the runtime's C headers:

  • Symbols caml_alloc_with_profinfo and caml_alloc_small_with_profinfo are declared in alloc.h, but they don't seem to exist (@mshinwell?)
  • Symbols caml_init_alloc_for_heap, caml_alloc_for_heap, caml_free_for_heap, caml_disown_for_heap, caml_add_to_heap and caml_allocation_color are mentioned in memory.h but not marked CAMLexport in memory.c. Shouldn't they be enclosed in a CAML_INTERNALS block in the header? (@damiendoligez?)
  • The same seems to apply to everything below caml_set_minor_heap_size in minor_gc.h except for caml_gc_dispatch (@damiendoligez?)
  • Shouldn't caml_fatal_uncaught_exception also be hidden inside a CAML_INTERNALS block in printexc.h (@xavierleroy?)

I'd highly recommend reviewing this using patdiff, as it makes the changing of keywords and re-alignment of whitespace much clearer! If installed via opam, you can enable patdiff as a git driver with git config --global diff.external $(which patdiff-git-wrapper) (if you want to do a subsequent git diff without using patdiff, then use git diff --no-ext-diff. Should the steps be useful, from your ocaml git clone:

$ git remote add dra27 https://github.com/dra27/ocaml.git -f
$ opam install patdiff --yes
$ git config --global diff.external $(which patdiff-git-wrapper)
$ git diff $(git merge-base dra27/fvisibility trunk) dra27/fvisibility

1 This was back in the wonderful days of there only being one Windows port to worry about, but it sure needed a lot of worrying.
2 Windows life had become more complicated by then.

dra27 added 28 commits March 2, 2018 14:19
CAMLextern was introduced in ddd99c7 in 2001 to allow dynamic loading of
DLLs for Windows bytecode. It was subsequently made obsolete by 3958a92
in 2007 when the the distribution switched to using FlexDLL as part of the
introduction of natdynlink.

Since then, it was haphazardly used or not used - this commit eliminates
it for function declarations.
Following on from the previous commit, symbols not used outside
{asm,byte}{comp,run} are no longer marked CAMLextern but just extern.
CAMLdata allows the msvc64 port to mark data symbols which are accessed by
dynamically loaded C code to be marked __declspec(dllimport) which
prevents relative relocations for the symbols and so allows the symbols to
be accessed even if the DLL containing the code is loaded more than 2GiB
away from the runtime in the address space.
CAMLprim should only be used for primitives (innocuous, since this
occurs outside byterun)
Allows compilation with -fvisibility=hidden
These functions don't need to be marked CAMLexport.
Minor tweak, since it doesn't (and shouldn't ever) contain any C
primitives.
All symbols not inside a CAML_INTERNALS block are now declared with
CAMLdata.
This function would never been inlined before, because the function used
to be declared extern in the header. GCC can cope with this, but it's a
C11 violation so at some point it'll matter.

GCC appears to interpret a tentative declaration followed by an inline
definition as not inline (which is, of course, legal). MSVC does not and
ends up with a linker error. The MSVC behaviour is apparently C11, though
possibly more by luck than judgment...
@ghost
Copy link
Copy Markdown

ghost commented Mar 12, 2018

@dra27 the description is quite long, could you sum up in a few words what this patch is about?

@dra27
Copy link
Copy Markdown
Member Author

dra27 commented Mar 12, 2018

Blimey, that's nearly harder than the patch...

It's ultimately about removing a hack in flexlink which fixes the base address of 64bit processes and libraries to 0x10000 as that's no longer possible on Cygwin64 (i.e. OCaml presently doesn't work on Cygwin64, or at least is hugely degraded). The fix for that works for gcc, but requires a teensy bit of extra work for MSVC which is what this GPR facilitiates.

@ghost ghost assigned alainfrisch Mar 12, 2018
@ghost
Copy link
Copy Markdown

ghost commented Mar 12, 2018

Thanks. @alainfrisch, I saw on "flexlink" so I assigned the PR to you.

@alainfrisch
Copy link
Copy Markdown
Contributor

This isn't wholly satisfactory, as it would mean that the symbols would always be accessed indirectly, even when it's not strictly necessary.

If I understand correctly, that would be typically for function calls within the main image, right? I wouldn't be overly concerned by that (OCaml-generated code itself is by default -fPIC, and there are probably way more OCaml than C function calls in a typical OCaml program).

@dra27
Copy link
Copy Markdown
Member Author

dra27 commented Mar 13, 2018

I agree that it's not likely to be having an impact, but my reasoning was that:

  • the export control macros should be revised, because they're a legacy from something else which simply wasn't removed at the time (justifiably not removed at the time, I hasten to add - a decade ago, one couldn't have predicted that we'd stick with flexdll, of course). The revision also allows for -fvisibility changes elsewhere, which has benefits.
  • while revising them, it's easier to differentiate between data and code now than change it again later.
  • the GCC medium code model makes this distinction, which makes me suspect that someone either benchmarked it or that someone with more intuition than me was worried that this could be a performance problem in general, given that they went to the trouble of producing three x86_64 code models.

@damiendoligez
Copy link
Copy Markdown
Member

Symbols caml_init_alloc_for_heap, caml_alloc_for_heap, caml_free_for_heap, caml_disown_for_heap, caml_add_to_heap and caml_allocation_color are mentioned in memory.h but not marked CAMLexport in memory.c. Shouldn't they be enclosed in a CAML_INTERNALS block in the header? (@damiendoligez?)

I think you're right for all the *_for_heap stuff, but I won't be surprised if some code out there uses caml_allocation_color. We'll have to check the OPAM repo.

@damiendoligez
Copy link
Copy Markdown
Member

The same seems to apply to everything below caml_set_minor_heap_size in minor_gc.h except for caml_gc_dispatch (@damiendoligez?)

Yes if you mean including caml_set_minor_heap_size itself. And I don't think even caml_gc_dispatch needs to be exported.

@alainfrisch alainfrisch removed their assignment Jun 28, 2018
@damiendoligez damiendoligez self-assigned this Nov 29, 2018
@ygrek
Copy link
Copy Markdown
Contributor

ygrek commented Jan 3, 2020

How does it influence bindings authors? Should they use any CAML* macros for binding C functions?

@dra27
Copy link
Copy Markdown
Member Author

dra27 commented Jan 3, 2020

@ygrek - it doesn't affect bindings authors. The manual would be updated to use CAMLstub instead of CAMLprim and to add an "advanced topic" on the use of CAMLdata (which would be necessary only for a C binding which defines global data symbols and expects those to be available to subsequently dynamically loaded code and have that work on Windows...!).

It would always be possible to continue using CAMLprim instead of CAMLstub (indeed, when I finally rebase this monster, we may decide that the CAMLstub/CAMLprim distinction is for the compiler only and change nothing for the outside world).

@ygrek
Copy link
Copy Markdown
Contributor

ygrek commented Jan 3, 2020

I see that manual now mentions:

The CAMLprim macro expands to the required compiler directives to ensure that the function is exported and accessible from OCaml.

My understanding up until today was to remove CAMLprim wherever I saw it in bindings, looks like I was wrong (and have spread this advice to other people).

So CAMLprim should be used currently in bindings and will be still used after/if this PR is merged.

@dra27
Copy link
Copy Markdown
Member Author

dra27 commented Jan 3, 2020

@ygrek - yes, that's correct. IIRC (I'm slowly paging this back in) the fvisibility part would only be used by the compiler - so bindings which have stopped using CAMLprim would still work, but just can't take advantage of it (i.e. we'd publicise that it's a good idea to, but not enforce it)

@dra27
Copy link
Copy Markdown
Member Author

dra27 commented Nov 25, 2020

4.12 restores Cygwin64 support - hopefully we can keep it that way! The fvisibility is not a priority for me, so closing this for now.

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.

4 participants