Re-organise export control macros in headers#1633
Re-organise export control macros in headers#1633dra27 wants to merge 32 commits intoocaml:trunkfrom
Conversation
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.
See also MPR#7744.
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...
|
@dra27 the description is quite long, could you sum up in a few words what this patch is about? |
|
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 |
|
Thanks. @alainfrisch, I saw on "flexlink" so I assigned the PR to you. |
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). |
|
I agree that it's not likely to be having an impact, but my reasoning was that:
|
I think you're right for all the |
Yes if you mean including |
|
How does it influence bindings authors? Should they use any CAML* macros for binding C functions? |
|
@ygrek - it doesn't affect bindings authors. The manual would be updated to use It would always be possible to continue using |
|
I see that manual now mentions:
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. |
|
@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 |
|
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. |
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,CAMLprimandCAMLexternmacros.CAMLprimandCAMLexportcarried the same purpose and were to be used in definitions, except thatCAMLprimwas also intended to identify C functions implementing primitives compiled into the runtime systems (previously identified with magic/* ML */comments).CAMLexternwas introduced in the headers in place of theexternkeyword. Together,CAMLexportandCAMLexternallowed 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
CAMLexportandCAMLextern, 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 of0x10000caused 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 least0x2:00000000and therebaseutility 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
CAMLexternto 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 withCAMLextern, 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
visibilityattribute, as this is related to__declspec. The commit history started out on a similar path to #1207, hacking the GCC attribute onto theCAMLprimandCAMLexportmechanism. 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:
-fvisibility=hidden, thus requiring all symbols to be explicitly exported. This is inCFLAGSonly, and so does not affect files compiled withocamlc -cor libraries built withocamlmklib.CAMLprimhad been co-opted to do things beyond its original intention. I've introducedCAMLstubfor use outsidebyterunwhich 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.CAMLexporthas been completely removed, because we don't need to use__declspec(dllexport)(the macro remains, because it was a public part of the header).CAMLexternhas been dropped, as has the use of theexternkeyword for function declartions and a new macroCAMLdatahas been introduced which is used to mark the data symbols which are to be exported. Again,CAMLexternremains only because it was public before.This GPR also contains a few minor fixes:
caml_code_fragments_tablewas defined only through C tentative declarations in the headers. This is now replaced with a tentative declaration inmisc.c-- the headers all useCAMLdata(i.e.extern).caml_alloc_float_arraywas incorrectly declaredCAMLprimin #534 and this has been fixed (with an associated bootstrap).caml_stat_strdup_to_utf16was also declaredexternand definedinline.There are a few questions arising from going through the runtime's C headers:
caml_alloc_with_profinfoandcaml_alloc_small_with_profinfoare declared inalloc.h, but they don't seem to exist (@mshinwell?)caml_init_alloc_for_heap,caml_alloc_for_heap,caml_free_for_heap,caml_disown_for_heap,caml_add_to_heapandcaml_allocation_colorare mentioned inmemory.hbut not markedCAMLexportinmemory.c. Shouldn't they be enclosed in aCAML_INTERNALSblock in the header? (@damiendoligez?)caml_set_minor_heap_sizeinminor_gc.hexcept forcaml_gc_dispatch(@damiendoligez?)caml_fatal_uncaught_exceptionalso be hidden inside aCAML_INTERNALSblock inprintexc.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 withgit config --global diff.external $(which patdiff-git-wrapper)(if you want to do a subsequentgit diffwithout usingpatdiff, then usegit diff --no-ext-diff. Should the steps be useful, from your ocaml git clone: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.