Clang-cl.exe support for C++ modules

MSVC’s cl.exe supports several new command-line flags for C++ module interface and implementation files, detailed here. There are several straightforward equivalents between the two compiler front-ends:

cl.exe clang.exe
/interface --precompile
/ifcSearchDir -fprebuilt-module-path
/reference -fmodule-file
/ifcOutput -fmodule-output
/exportHeader -fmodule-header

Some options in cl.exe don’t appear to have clang.exe equivalents (to my knowledge), such as /internalPartition and /ifcOnly, or they may be combinations of the above options.

I understand that Windows users can use the clang.exe compiler (i.e. with the GNU-style front-end) and use the instructions in Standard C++ Modules — Clang 17.0.0git documentation more or less as-is, but it would be easier to switch compilers (for CI, performance, conformance, etc) without having to change a full set of compiler command-line options. Hence, it would be a good idea to add support for these options to clang-cl.exe.

Furthermore, cl.exe supports P1689R5 with the /scanDependencies switch, whereas the Clang/LLVM ecosystem uses a different binary altogether, with clang-scan-deps.exe. How is this intended to be handled with clang-cl.exe?

Thanks!

1 Like

On the one hand, we don’t have contributors developing in windows for C++20 modules. So it will be harder to test. On the other hand, the semantics of these options are similar instead of being equal to each other. e.g., the term ifc refers to a BMI format used by Microsoft. And it looks not good to use the term ifc when we don’t produce or consume ifc format actually.

1 Like

On the one hand, we don’t have contributors developing in windows for C++20 modules

I am willing and able to contribute; I have vested interest in trying to achieve clang-cl.exe parity with cl.exe. :slight_smile:

And it looks not good to use the term ifc when we don’t produce or consume ifc format actually.

Fair point, but I feel this could be alleviated, given several points:

  1. Modules are very new to the entire C++ ecosystem as a whole, and the feature is still a moving target. This generally requires users to read a fair bit of documentation, and therefore:
  2. We could document that these /*ifc* options are aliases to the clang.exe -fmodule-* options, note that Clang’s .pcm format is different to Microsoft’s .ifc format, and they cannot be mixed freely.

Of course, the best long-term solution would be to standardise the built module interface format as a C++ paper, and tweak either compiler’s implementation to fit this standard. But this is rather orthogonal to the matter.

That is not going to happen. Clang writes various binary files, e.g. pcm, to the disk. The version of the file is tied to the git hash. If you have two Clang’s built from different git hashes, the file format may differ. They could have added a new AST node, refactored the AST, or improved compactnesss of the output format.

I forgot for a moment that BMIs are binaries (ergo, built).

However, I still think the command-line flag aliases are a decent idea, as long as they are documented clearly enough (perhaps at the above-mentioned link), and users are aware of the semantic differences. It’s not like clang.exe and cl.exe generated BMIs have interop now, anyway.

I don’t feel the direction is good. While we don’t have any intention to implement IFC format in clang now, we can’t assume we won’t do that some other day. Then it will become a severe breaking change.

In my opinion, we’d better to mitigate this in tools like build systems. For example, the output of P1689 format is completely a by-product of scanning. So it is the job of cmake (or other build systems) to handle the difference. And I think this is the same for other similar options.

1 Like

If C++ modules with CMake on Windows does not work out of the box, I would open a ticket with them. CMake should handle the differences between Windows and Unix-like.

I’m not sure how CMake is relevant.

Unless I’m missing something, my point is that C++ module compilation is straight-up missing in the clang-cl driver while it is present in the clang driver, and can be demonstrated by experimenting with a very simple hello-world example from the command-line:

> clang-cl.exe /std:c++20 Hello.cppm --precompile -o Hello.pcm
clang-cl: warning: unknown argument ignored in clang-cl: '--precompile' [-Wunknown-argument]
clang-cl: warning: argument unused during compilation: '/std:c++20' [-Wunused-command-line-argument]
LINK : fatal error LNK1561: entry point must be defined
clang-cl: error: linker command failed with exit code 1561 (use -v to see invocation)

However, this works:

> clang-cl.exe /std:c++20 Hello.cppm /clang:--precompile /Fo.\Hello.pcm

Therefore, I suppose users are meant to use /clang: as a work-around. If this is the expectation, then yes, I can let the CMake authors know this (because there, too, clang-cl isn’t working for C++ modules).

My comment about P1689R5 was to discuss unifying the user-facing behaviour (i.e. the compiler flags) for the two compiler front-ends, cl.exe and clang-cl.exe.

Currently, can it works if you try /clang:--precompile? If yes, I think this is the expectation and maybe we can add this to the document.

1 Like

I just updated my comment above, and yes, /clang:--precompile works. However, I’m not sure how I can compile a working executable:

> clang-cl.exe /std:c++20 use.cpp /clang:"-fprebuilt-module-path=. .\Hello.pcm" /Fo.\Hello.out
use.cpp(1,8): fatal error: module 'Hello' not found
import Hello;
~~~~~~~^~~~~
1 error generated.

-fprebuilt-module-path takes a path to the directory instead of the path to the BMI. That’s the job of -fmodule-file

Thanks. I was following the instructions in the Clang documentation as-is.

This finally works:

> clang-cl /std:c++20 use.cpp /clang:"-fmodule-file=.\Hello.pcm" /c /Fo.\Hello.out

Looks like the documentation needs a bit of a clean-up.

Oh, no. It ls incorrect. The form -fmodule-file=<BMI> is deprecated. We prefer -fmodule-file=<Module-name>=<BMI>. And I don’t know why it works since we should need to compile Hello.pcm and link it into the binary. I guess something surprising happens in clang-cl or in windows.

Okay, I’m so sorry; it’s actually not working at all. I was getting very confused.

  1. If I use -fmodule-file=, I cannot link, and the linker complains that it cannot find hello(). If I use fprebuilt-module-path and supply the precompiled module file, the compiler then complains that module Hello cannot be found:

    > clang-cl /std:c++20 use.cpp /clang:"-fmodule-file=Hello=.\Hello.pcm" /Fo.\Hello.exe
    Hello.exe : error LNK2019: unresolved external symbol "void __cdecl hello(void)" (?hello@@YAXXZ) referenced in function main
    use.exe : fatal error LNK1120: 1 unresolved externals
    clang-cl: error: linker command failed with exit code 1120 (use -v to see invocation)
    
    clang-cl /std:c++20 use.cpp /clang:"-fmodule-file=.\Hello.pcm" /Fo.\Hello.exe
    Hello.exe : error LNK2019: unresolved external symbol "void __cdecl hello(void)" (?hello@@YAXXZ) referenced in function main
    use.exe : fatal error LNK1120: 1 unresolved externals
    clang-cl: error: linker command failed with exit code 1120 (use -v to see invocation)
    
    > clang-cl /std:c++20 use.cpp /clang:"-fprebuilt-module-path=. .\Hello.pcm" /Fo.\Hello.exe
    use.cpp(1,8): fatal error: module 'Hello' not found
    import Hello;
    ~~~~~~~^~~~~
    1 error generated.
    
  2. The module file itself can be compiled fine with clang-cl.exe and /clang:--precompile:

    > clang-cl.exe /std:c++20 Hello.cppm /clang:--precompile /Fo.\Hello.pcm
    > clang++.exe -module-file-info .\Hello.pcm
    Information for module file '.\Hello.pcm':
    Module format: raw
    ====== C++20 Module structure ======
    Interface Unit 'Hello' is the Primary Module at index #2
    Sub Modules:
        Global Module Fragment '<global>' is at index #1
    ====== ======
    Generated by this Clang:
    Module name: Hello
    Language options:
        C99: No
        C11: No
        C17: No
        C2x: No
        Microsoft Visual C++ full compatibility mode: Yes
        Kernel mode: No
        Microsoft C++ extensions: Yes
        Microsoft inline asm blocks: Yes
        Borland extensions: No
        C++: Yes
        C++11: Yes
        C++14: Yes
        C++17: Yes
        C++20: Yes
    ...
    
  3. The documentation needs to be clearer on clang-cl C++ module compatibility. All of this works perfectly fine with the GNU-like clang.exe driver:

    > clang.exe --std=c++20 Hello.cppm --precompile -o .\Hello.pcm
    > clang.exe --std=c++20 .\use.cpp -fprebuilt-module-path="." .\Hello.pcm -o .\Hello.exe
    > .\Hello.exe
    Hello World!
    

I’m not sure that these flags correspond with each other. -interface is the flag that says “makes a BMI and exports the module/partition”; there is also -internalPartition for non-exported partitions of modules. I think -ifcOnly maps to --precompile much better.

Yes. I didn’t think about clang-cl at all. And at least it should be good to mention this in the document. I’ll do it later.

+1, I think the user expectation is going to be that ifc options will emit IFC files that are compatible with Microsoft’s.

4 Likes

Fair enough; we can’t make an assumption today and then break that down the line.

That being said, as mentioned above, even with /clang:, clang-cl.exe’s behaviour is not consistent with clang.exe’s, and fails to compile, so that behaviour definitely needs to be looked at.

1 Like

Reading through this, I see there is no consensus on how to expose the module flags via clang-cl as there are differences in behavior. Currently the only way is use /clang: (and -Xclang?), which are not nice flags to work with. Given that clang-cl has already several -f flags (See Clang Compiler User’s Manual — Clang 18.0.0git documentation), wouldn’t it be an idea to expose the clang.exe module flags with the same name in clang-cl.exe?

Sounds like a good idea.

1 Like