Why is Windows builds of Clang built with some exception flags?

LLVM doesn’t use exceptions by our coding standard, and on Unix systems, we build LLVM and clang with -fno-exceptions. But on windows, we pass the flags /EHs-c-, which enables some exceptions.

I did some local tests and removed the EH flags from the windows build when building with clang-cl. We save around 11% of the binary size (clang-cl) and around 2% in performance compiling LLVMSupport.

Is there a reason that the default setting would be /EHs-c- that I am missing?

There are also no ways to disable these flags without editing the CMake source, at minimum I think we should include a flag to disable this: llvm-project/AddLLVM.cmake at 2a16e1ff7d2735001dbe40e607823857f4bedd0e · llvm/llvm-project · GitHub

Any ideas @mstorsjo @hansw2000 @zero9178 ?

I believe the intention when passing /EHs-c- was to disable exceptions. Looks like it was added in a679f43f4e330ac313cb1d1b5dee4c98f94da822.

I do think we use SEH in the CrashRecoveryContext on Windows (and maybe elsewhere too).

I’m surprised removing that flag reduces code size so much though. Maybe clang-cl is doing something strange? Do you see the same size reduction when building with MSVC.

Huh, that’s funny - I tried the flags with MSVC 2022, and the results were a bit surprising: the binary ended up being the same size with and without the flag.

But they are much bigger than the clang-produced binary (almost 40% bigger), so maybe whatever clang-cl is doing is already baked in?

When you say we use SEH in the CrashRecoverContext, do you mean that we will not get correct crash reporting if SEH is disabled?

Btw - I get the same size-reduction if I pass -fnew-infallible this is how my investigation started, I wanted to see if it made any difference, and it reduced the binary size by 11%, when my coworker said that /EHs-c- is still generating exception data, our production game-engine omit the /EH flags entirely.

IIRC there’s two different concepts involved here - whether SEH unwind info is generated for all code (which is more or less mandatory), and whether code is generated for catching/handling C++ exceptions. AFAIK these options disable the C++ bits, but SEH unwind info still is generated, and I would expect that to be enough for the crash reporting.

With plain clang, it’s possible to disable the generation of the unwind info with -fno-unwind-tables -fno-asynchronous-unwind-tables (and that saves a fair bit of file size too), but that breaks backtraces in lots of places, and probably breaks the crash reports.

Reading Microsoft’s documentation: The compiler always generates code that supports asynchronous structured exception handling (SEH). By default (that is, if no /EHsc, /EHs, or /EHa option is specified.

So it seems the correct behavior is not to pass any of the /EH flags when building LLVM with clang-cl.

I looked at Chromium, which also doesn’t use exceptions, and it seems we also don’t pass any ‘/EH’ flag. So maybe that’s the right thing.

But I’m still confused about what’s going on here. It seems not passing /EHs-c-'to clang-cl made a difference in your builds, whereas parseClangCLEHFlags() says “The default is /EHs-c-”.

And it sounds like MSVC does something slightly different?

I just looked at parseClangCLEHFlags() and it will set /Ehs-c- if you also pass /GX otherwise both are set to false.

  if (EHArgs.empty() &&
      Args.hasFlag(options::OPT__SLASH_GX, options::OPT__SLASH_GX_,
                   /*Default=*/false)) {

The comment should probably reflect this.

It looks to me like it will set /EHsc if you pass /GX.

Ah, you are right. My bad.

Then I agree - it’s a little of a mystery here.

I checked the driver invocation with /clang:-v and can’t see any difference in the invocation between no /EH and /EHs-c-.

I’ll dig further.

1 Like