#2176·hermes

`shermes` emits GNU-style compile and link flags unconditionally, so its own default `SHERMES_CC` (= `CMAKE_C_COMPILER` = clang-cl) cannot link what it compiles on Windows

Author: altweenCreated Sep 7, 2026Updated Sep 11, 2026

shermes compiles JavaScript to C and then invokes a C compiler to build and link the result. That compiler defaults to CMake's C compiler — tools/shermes/CMakeLists.txt:18:

cmake
set(SHERMES_CC "${CMAKE_C_COMPILER}" CACHE STRING "C compiler to invoke")

so on a clang-cl configure, SHERMES_CC is clang-cl. But the command line shermes builds for it is GNU-driver-shaped unconditionally (tools/shermes/compile.cpp, buildCCArgs): -fPIC at :225, -fno-strict-overflow at :270, -L<dir> at :282 and :289, -l<name> at :284, -lshermes_console at :310, -Wl,-rpath at :313 and :317, -lm at :325, and -lhermesvm at :334.

clang-cl warns and ignores every one of those. The fatal one is -Wl,-rpath: clang-cl does not understand -Wl,, so the bare directory that follows it is left on the command line and the linker reads it as an input file:

clang-cl: warning: unknown argument ignored in clang-cl: '-fPIC' [-Wunknown-argument]
clang-cl: warning: unknown argument ignored in clang-cl: '-lhermesvm' [-Wunknown-argument]
warning: unknown warning option '-Wl,-rpath'; did you mean '-Wformat'? [-Wunknown-warning-option]
LINK : fatal error LNK1181: cannot open input file '...\static-hermes\lib.obj'

Pointing SHERMES_CC at the GNU-driver clang.exe that sits beside clang-cl does not fix it, which is worth saying because it is the obvious first suggestion. That driver translates -L and -l correctly for an MSVC target, but it still leaves the -Wl,-rpath operand as an input file (LNK1181, the same message), and for -exec it rejects -fPIC outright before it gets that far:

clang: error: unsupported option '-fPIC' for target 'x86_64-pc-windows-msvc'

The escape hatches that do exist are environment variables, read at compile.cpp:136-147 (CC, CFLAGS, LDFLAGS, LDLIBS), and they are all-or-nothing: a non-empty CFLAGS replaces the include paths and -Ds as well as the flags, and a non-empty LDFLAGS is what skips the -Wl,-rpath and -lm block (compile.cpp:286-329) — but the trailing -lhermesvm (:334) still needs -nohermeslibs to suppress. With all three set, and every library named by full path, a Windows AOT executable does build and run under plain clang-cl. That is a user hand-writing the link line the tool exists to write.

Two further Windows gaps behind this one, both measured:

  • -exec cannot work on Windows even once the link line is right. It compiles to a shared object and LoadLibrarys it, but the generated C's main carries no __declspec(dllexport), so nothing exports it from a DLL:

    Running library with args: ...\qa-five-lines.js-e5029a
    GetProcAddress(main) error: 127

    (127 is ERROR_PROC_NOT_FOUND.) The -fPIC / default-visibility assumption in the SharedObj path is an ELF one.

  • -static-link is refused outright off __APPLE__ (compile.cpp:305): Static linking unsupported on this platform — even though linking the static hermesvm_a.lib by hand through LDFLAGS works perfectly on Windows and produces a self-contained executable.

This is separate from, and downstream of, /WHOLEARCHIVE:hermesvm_a not resolving under lld-link (#2175): even with a linked hermesvm.dll, the flags above would still not work. Together the two make the AOT path unreachable on a Windows/clang-cl toolchain.

Repro

bat
git clone https://github.com/facebook/hermes.git hermes-static
cd hermes-static
git checkout 5cee10abc93667ea5538caecaf0a457c66fa5bdc

call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
set CLANGCL=C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/Llvm/x64/bin/clang-cl.exe

cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release ^
  -DCMAKE_C_COMPILER="%CLANGCL%" -DCMAKE_CXX_COMPILER="%CLANGCL%" ^
  -DHERMES_ENABLE_TEST_SUITE=OFF -DHERMES_ENABLE_NAPI=OFF

(HERMES_ENABLE_TEST_SUITE=OFF and HERMES_ENABLE_NAPI=OFF only shorten the build; neither is load-bearing for the failure below.) Then:

bat
cmake --build build --target shermes-dep
echo print("hello"); > hello.js
build\bin\shermes.exe -v -exec hello.js
build\bin\shermes.exe -v hello.js -o hello.exe

shermes.exe itself builds and compiles the JavaScript fine; the failure is everything after the C it emits, and -v prints the SHERMES_CC command line it constructed. Both invocations end in LNK1181: cannot open input file '...\lib.obj'.

(shermes-dep will itself stop earlier at lld-link: error: could not open 'hermesvm_a' — that is #2175, filed on its own. Building the shermes target alone is enough to reach the failure described here.)

Environment

Windows 11 Pro 10.0.26200, x86-64
CMake 3.31.6-msvc6 (the one Visual Studio 2022 ships), Ninja 1.13.2
Visual Studio 2022 Community

> clang-cl --version
clang version 19.1.5
Target: x86_64-pc-windows-msvc
Thread model: posix
InstalledDir: C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\Llvm\x64\bin

Proposed fix (one line, plus a follow-on)

The one-line version: make SHERMES_CC not default to an MSVC-driver compiler — at tools/shermes/CMakeLists.txt:18, fall back to a GNU-driver clang when CMAKE_C_COMPILER is clang-cl (CMake computes CMAKE_C_SIMULATE_ID and the tree already computes CLANG_CL), or fail the configure with a message naming SHERMES_CC rather than deferring the failure to the first -exec.

The complete fix is larger and is really the ask: the command-line builder in tools/shermes/compile.cpp should select MSVC spellings when the configured SHERMES_CC is an MSVC-driver compiler — no -fPIC, no -Wl,-rpath (Windows has no rpath; the DLL search path is the caller's problem), no -lm, and /LIBPATH: plus a plain name.lib for the library search. The -fPIC at :225 in particular is emitted from the output-level switch before any of the CFLAGS overrides are consulted, so no environment variable can get out of its way.

Two smaller ones, if they are wanted separately: give the generated C's main a __declspec(dllexport) (or export it from the link) so -exec can find it on Windows, and allow -static-link off __APPLE__ — linking hermesvm_a.lib, shermes_console_a.lib, jsi.lib and boost_context.lib by hand works and produces a self-contained 4.85 MB executable, which is exactly what -static-link claims to do.

Related issues

  • #1994 / #2089 fixed the :-vs-; path-list splitting in tools/shermes/compile.cpp at de1526b (2026-09-02). Everything above was measured at 5cee10a, which includes that fix — the -I / -L directories now arrive whole, and what remains is that the flags around them are GNU-driver spellings handed to an MSVC-driver compiler. In #1994 the reporter was driving a GNU-driver clang.exe; this report is about the DEFAULT SHERMES_CC on a clang-cl configure, and about the GNU driver not being a complete escape either (-fPIC, -Wl,-rpath).
  • #1627 (open) reached dlfcn.h in compile.cpp in early 2025; f13b0d9 has since ported the -exec loading to LoadLibrary, and the GetProcAddress(main) failure above is the next step on that same path.
  • The /WHOLEARCHIVE:hermesvm_a bare-name issue, #2175.

Where this came from

Measured while embedding static_h as the second runtime of a native game host on Windows (the repository is private at the time of filing, so no links). The AOT reading was attempted, time-boxed and recorded as NOT MEASURED — there is no AOT column in that project's tables and no number invented for one. No file under the Hermes checkout was edited.