wasi-sdk icon indicating copy to clipboard operation
wasi-sdk copied to clipboard

Option to suppress errors related to unsupported features

Open kamrann opened this issue 6 months ago • 11 comments

From what I've gathered so far, it seems like unsupported functionality is guarded by emission of preprocessor errors at the point of #including a header that represents such functionality. Maybe there's something I've missed, but this seems somewhat heavy handed and difficult to work around. Presumably in most cases, including a header alone would not cause issues so long as certain symbols are not referenced?

My immediate issue relates to trying to build code for WASI which uses C++20 modules (a niche combo admittedly). Since the standard library module will just include a bunch of system headers, the build fails right away:

In file included from C:\WASM\wasi-sdk-25.0-x86_64-windows\share\wasi-sysroot\share\libc++\v1\std.cppm:52:
In file included from C:\WASM\wasi-sdk-25.0-x86_64-windows\bin\../share/wasi-sysroot/include/wasm32-wasi/c++/v1\csetjmp:37:
C:\WASM\wasi-sdk-25.0-x86_64-windows\bin\../share/wasi-sysroot/include/wasm32-wasi\setjmp.h:13:2: error: Setjmp/longjmp support requires Exception handling support, which is [not yet standardized](https://github.com/WebAssembly/proposals?tab=readme-ov-file#phase-3---implementation-phase-cg--wg). To enable it, compile with `-mllvm -wasm-enable-sjlj` and use an engine that implements the Exception handling proposal.
   13 | #error Setjmp/longjmp support requires Exception handling support, which is [not yet standardized](https://github.com/WebAssembly/proposals?tab=readme-ov-file#phase-3---implementation-phase-cg--wg). To enable it, compile with `-mllvm -wasm-enable-sjlj` and use an engine that implements the Exception handling proposal.
      |  ^
In file included from C:\WASM\wasi-sdk-25.0-x86_64-windows\share\wasi-sysroot\share\libc++\v1\std.cppm:53:
In file included from C:\WASM\wasi-sdk-25.0-x86_64-windows\bin\../share/wasi-sysroot/include/wasm32-wasi/c++/v1\csignal:46:
C:\WASM\wasi-sdk-25.0-x86_64-windows\bin\../share/wasi-sysroot/include/wasm32-wasi\signal.h:2:2: error: "wasm lacks signal support; to enable minimal signal emulation, compile with -D_WASI_EMULATED_SIGNAL and link with -lwasi-emulated-signal"
    2 | #error "wasm lacks signal support; to enable minimal signal emulation, \
      |  ^
2 errors generated.

Is it feasible to allow for these errors to be conditionally disabled via macros (actually it seems for the signals there is a macro, but not for the exceptions), leaving it up to the user to navigate potential linker/runtime issues that could result?

kamrann avatar Jun 15 '25 06:06 kamrann

This seems like the kind of thing @sbc100 has been for: it would certainly make it easier to compile more things. I'm pretty neutral and your "extra macro to just compile the thing" idea makes sense to me. @alexcrichton, @sunfishcode?

abrown avatar Jun 17 '25 22:06 abrown

Yes, perhaps simply including the header should probably not be and error? But using / referencing missing symbols probably should continue to be a compiler/link failure.

Is the C++ module system going to want to unconditionally reference symbols that wasi-libc doesn't provide? Or does it have some way to make such things optional?

sbc100 avatar Jun 17 '25 22:06 sbc100

I find in situations like this there's not really a best answer and "someone always loses" in the sense that someone is going to get a bad error message they don't understand somewhere along the line. The current system punishes #include-ing files that you don't actually use the symbols from. Not having these guards at all (which I realize isn't being proposed but wanted to list our here for completeness) would punish users who are using the symbols with obscure compiler warnings ("but I included the header?!"). Adding an opt-in punishes those who need the opt-in with extra syntax to pass.

Personally I sort of feel that everything is a not-so-great option here so I'd defer to the side of something that's easiest to maintain which to me would be to remove #error guards but also remove the symbols that wasi-libc doesn't have. That would mean we punish users actually using these symbols with compiler errors about referring to symbols that don't exist, but I feel that it's the most flexible in terms of integrating upstream in projects since the headers exist and the contents are somewhat there and it's only if things are actually used does something break, but that's inevitable and it's just a question about how nice the error message is which we can in theory mitigate with documentation for wasi-{libc,sdk}

alexcrichton avatar Jun 17 '25 23:06 alexcrichton

Is the C++ module system going to want to unconditionally reference symbols that wasi-libc doesn't provide? Or does it have some way to make such things optional?

I believe it should simply be re-exporting names and wouldn't cause any emission of symbol references itself, but I'll try to make some local modifications to the headers in question to see if I can verify this.

kamrann avatar Jun 18 '25 07:06 kamrann

Doesn't re-exporting a symbol foo require that the symbol foo is actually defined? What about platforms that don't provide foo?

sbc100 avatar Jun 18 '25 16:06 sbc100

Doesn't re-exporting a symbol foo require that the symbol foo is actually defined? What about platforms that don't provide foo?

I don't believe so, no. Though I'm a little unsure of your precise meaning of symbol and defined. Certainly the named entity being exported needs to have been declared and accessible at the point it's exported. Other than that though, it really is just a case of making the name available to lookup for consumers of the module, so has no effect on symbols as I understand the term to mean as far as the linker is concerned.

FWIW, I commented out the #error line in WASI setjmp.h, and was able to build the std module, and then link and run my program without any apparent issue.

I'm out of my depth here so not qualified to give suggestions as to the best way to solve the issue, but I did a bit of digging in case it's useful. Here's an excerpt from the libc++ std module source:

#if !defined(_LIBCPP_HAS_NO_LOCALIZATION)
#  include <codecvt>
#endif
#include <compare>
#include <complex>
#include <concepts>
#include <condition_variable>
#include <coroutine>
#include <csetjmp>
#include <csignal>

So you can see some things are guarded there; perhaps a change at this level is also a possibility?

Then later on we have:

export namespace std {
  using std::jmp_buf _LIBCPP_USING_IF_EXISTS;
  using std::longjmp _LIBCPP_USING_IF_EXISTS;
} // namespace std

So it's exporting a type and a function. So long as user code doesn't try to use the function, then the compiler won't emit a symbol reference for it just from this. The macro conditionally expands to a __using_if_exists__ attribute; that also looks somewhat relevant, but I don't know anything about it.

Anyway, to sum up, my feeling is that there's nothing really special about the std library module case. I think it's basically the exact same issue that could occur in any other context where someone targetting WASI pulls in some code which they don't have full control over, which in turn #includes certain system headers even though it (or the user's program) may not end up actually using the problematic features.

kamrann avatar Jun 19 '25 01:06 kamrann

Anyway, to sum up, my feeling is that there's nothing really special about the std library module case. I think it's basically the exact same issue that could occur in any other context where someone targetting WASI pulls in some code which they don't have full control over, which in turn #includes certain system headers even though it (or the user's program) may not end up actually using the problematic features.

Normally/historically, in portable software, references to libc functions (for example, setjmp) would be guard by some kind of HAVE_SETJMP macro. This does often also include headers too. HAVE_SETJMP_H, for example. However it sounds like the __using_if_exists__ macro takes care of that in this case, although the existence of the header appears to be mandatory.

I'd be OK with providing an empty header in this case, but it does seems a little odd.

sbc100 avatar Jun 19 '25 14:06 sbc100

Ah, the libcxx/include/__cxx03/csetjmp file does seem to be doing the right things and checking for __has_include(<setjmp.h>) first:

https://github.com/llvm/llvm-project/blob/a4e4527c4b44be9a88168c0a4758de58fd1a770d/libcxx/include/__cxx03/csetjmp#L35-L41

So perhaps we just need to remove the unconditional #error when the header is included.

sbc100 avatar Jun 19 '25 14:06 sbc100

Or we could simple remove the header completely.. that seems like it would also work for libc++

sbc100 avatar Jun 19 '25 14:06 sbc100

What if we moved the error message from a #error to a deprecated attribute, so that users still see the "use -mllvm -wasm-enable-sjlj`" message? It's not deprecation per se, but it would be a way to help users see "here's how to do this" rather than "this just doesn't work".

sunfishcode avatar Jun 19 '25 16:06 sunfishcode

This error message isn't even accurate anymore:

error: Setjmp/longjmp support requires Exception handling support, which is not yet standardized. To enable it, compile with -mllvm -wasm-enable-sjlj and use an engine that implements the Exception handling proposal.

Exception Handling is Phase 5: https://github.com/WebAssembly/proposals/blob/main/finished-proposals.md

Why is clang still emitting this?

anonhostpi avatar Oct 24 '25 08:10 anonhostpi