`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
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:
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:
-execcannot work on Windows even once the link line is right. It compiles to a shared object andLoadLibrarys it, but the generated C'smaincarries 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 theSharedObjpath is an ELF one.-static-linkis refused outright off__APPLE__(compile.cpp:305):Static linking unsupported on this platform— even though linking the statichermesvm_a.libby hand throughLDFLAGSworks 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
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:
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.exeshermes.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\binProposed 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 intools/shermes/compile.cppatde1526b(2026-09-02). Everything above was measured at5cee10a, which includes that fix — the-I/-Ldirectories 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-driverclang.exe; this report is about the DEFAULTSHERMES_CCon a clang-cl configure, and about the GNU driver not being a complete escape either (-fPIC,-Wl,-rpath). - #1627 (open) reached
dlfcn.hincompile.cppin early 2025;f13b0d9has since ported the-execloading toLoadLibrary, and theGetProcAddress(main)failure above is the next step on that same path. - The
/WHOLEARCHIVE:hermesvm_abare-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.
Source: facebook/hermes