Skip to content

Instantly share code, notes, and snippets.

@DanielaE
Last active July 5, 2023 20:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DanielaE/39e270adb6ab27d12c61e1491d355d30 to your computer and use it in GitHub Desktop.
Save DanielaE/39e270adb6ab27d12c61e1491d355d30 to your computer and use it in GitHub Desktop.
Internal module partitions cause grief
// https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Modules.html
// gcc 12.2
# do NOT compile m-a.cpp, it will fail immediately!
g++ -std=c++2b -fmodules-ts -x c++ m-pif.cppm m.cppm m-b.cppm m-c.cppm main.cpp
// https://clang.llvm.org/docs/StandardCPlusPlusModules.html
// clang 15.0
clang++ -std=c++2b -fprebuilt-module-path=. m-pif.cppm m.cppm m-b.cppm m-c.cppm --precompile
clang++ -std=c++2b -fprebuilt-module-path=. -fmodule-file=m.pcm m-a.cpp m-b.pcm m-c.pcm m.pcm m-pif.pcm main.cpp
// msvc 17.5
cl /nologo /EHsc /std:c++latest /TP /c /interface m-pif.cppm m.cppm
cl /nologo /EHsc /std:c++latest /TP /c /internalPartition m-b.cppm m-c.cppm
cl /nologo /EHsc /std:c++latest main.cpp m-a.cpp m-b.obj m-c.obj m-pif.obj m.obj
// All compilers compile, link, and produce the "expected" result despite the program being ill-formed.
// For well-formedness add -DWANT-WELLFORMED
module m; // a module implementation unit, makes exported a@m(), b@m(), c@m() visible
// - is a module implementation unit [module.unit]/2
// - IMPLICITLY imports transitively partition :pif, [module.unit]/8 + [module.import]/7
// a@m()
// - is attached to module 'm' [module.unit]/7.3 + [basic.link]/10
// - is owned by module 'm'
// - 'corresponds' to a@m in partition :pif, [basic.scope]/4
// - IS the same entity as a@m with external linkage in partition :pif #1, [basic.link]/8
// -> has external linkage, [basic.link]/2.1 + [basic.link]/4.9
int a() {
return 42;
}
// TU not accepted by gcc,
// complains about contents of BMI (m.gcm), see below
// but accepts the same BMI for import into main.cpp
/*
$ g++ -std=c++2b -fmodules-ts -c m-a.cpp
In module imported at m-a.cpp:1:1:
m: error: failed to read compiled module: Bad file data
m: note: compiled module file is 'gcm.cache/m.gcm'
m: fatal error: returning to the gate for a mechanical issue
compilation terminated.
*/
module m:b; // an internal module partition that imports the module interface partition
// - is a module implementation unit [module.unit]/2
// - is a module partition [module.unit]/3
// - EXPLICITLY imports partition :pif, [module.import]/4
import :pif; // #1 // make exported a@m(), b@m(), c@m() visible
// b@m()
// - is attached to module 'm' [module.unit]/7.3 + [basic.link]/10
// - is owned by module 'm'
// - 'corresponds' to b@m in partition :pif, [basic.scope]/4
// - IS the same entity as b@m with external linkage in partition :pif #2, [basic.link]/8
// -> has external linkage, [basic.link]/2.1 + [basic.link]/4.9
#ifndef _MSC_VER // not accepted by msvc if import declaration #1 is present 😮
int b() {
return 42;
}
#endif
/* msvc chokes on the attempt to compile function b@m() like this
m-b.cppm(7,9): error C7619: cannot export 'b' as module partition 'b' does not
contribute to the exported interface of module unit 'm'
m-b.cppm(7,9): error C7619: int b() {
m-b.cppm(7,9): error C7619:
*/
module m:c; // an internal module partition
// - is a module implementation unit [module.unit]/2
// - is a module partition [module.unit]/3
// none of a@m(), b@m(), c@m() are visible
#ifndef WANT_WELLFORMED // sheer presence of c() in translation phase 9 makes program IF-NDR
// c@m()
// - is attached to module 'm' [module.unit]/7.3 + [basic.link]/10
// - is owned by module 'm'
// - is NOT exported
// - has module linkage, [basic.link]/2.2 + [basic.link]/4.8
// - 'corresponds' to c@m in partition :pif, [basic.scope]/4
// - is NOT the same entity as c@m with external linkage in partition :pif #3, [basic.link]/8
// - is NOT an ODR violation
int c() {
return 42;
}
#endif
export module m:pif; // a module interface partition unit
// - is a module interface unit [module.unit]/2
// - is a module partition [module.unit]/3
// all function declarations
// - are attached to module 'm' [basic.link]/10
// - are owned by module 'm'
// - have external linkage, [basic.link]/2.1 + [basic.link]/4.9
export {
int a(); // a@m() entity #1
int b(); // b@m() entity #2
int c(); // c@m() entity #3
}
export module m;
// - is the primary module interface unit [module.unit]/2
export import :pif; // exports a@m(), b@m(), c@m()
// interface dependency on partition :pif
// no interface dependency on partitions :b and :c
import m; // make exported a@m(), b@m(), c@m() visible
int main() {
int rc = 0;
#if !defined(__GNUC__) || defined(__clang__)
rc += a();
#endif
#ifndef _MSC_VER
rc += b();
#endif
#ifndef WANT_WELLFORMED
rc += c();
#endif
return rc;
}
@iains
Copy link

iains commented Jul 5, 2023

hmmm... m-c is an interesting problem .. I do not know if we have a mechanism at present for rejecting it (but it's NDR anyway) ..
.. if we did, AFAICS, we'd probably have to modify all the linkers out there, and I guess that is one reason that the design we have at present is how it is. Will have to think more about it - it does not seem (at present) that the compilers are doing anything 'wrong'.

@DanielaE
Copy link
Author

DanielaE commented Jul 5, 2023

Right, it's material to make users aware of and teach thoroughly so that they don't fall unwittingly into this pit of failure. This isn't obvious in any way and probably very difficult to convey to everyday users.

@iains
Copy link

iains commented Jul 5, 2023

well, philosophically, in m and m:pif the module author has promised that the three functions exist (so the consumers of the interfaces have to believe it). That promise turns out to be a "white lie" (or a typo even missing the :pif in m-c.cpp).

m:b and m:c both produce functions with the correct signature (the exportedness of b is only significant within the compiler, by the time the code is emitted the mangling of b@m is the same as c@m).

we have no mechanism to check that the module is "complete" (in an analogous manner to checking that a class impl. is complete) .. and IDK if it's even possible.

The linker (as things stand) cannot tell the difference between the b@m and c@m cases and therefore links happily.

Thinking of ways to (a) prove that an implementation is complete (b) to differentiate exported-ness in the mangling (which would allow the linker to complain) might be a way forward ..

.. I have a sneaking suspicion that [b] here was tried in the modules-ts and then withdrawn in favour of the scheme we have now (but ICBW).

perhaps this is a strange corner-case (where the user made the correct thing by accident) - have we evidence that people have made this mistake in the wild?

edit: I actually checked that the AST clang produces has the correct linkage for m-b and m-c.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment