[experiment] opt-in clang-cl support for Windows#128264
Conversation
Adds an experimental, opt-in build path for CoreCLR's JIT (clrjit.dll)
on Windows using LLVM clang-cl instead of MSVC cl.exe.
Usage:
# Direct (forces Ninja generator):
src\coreclr\build-runtime.cmd -clangcl -release -component jit
# Via top-level build (set env var, then build normally):
$env:__UseClangCl="1"
build.cmd Clr.Jit -c Release /p:NoPgoOptimize=true
Requires clang-cl.exe on PATH or under %ProgramFiles%\LLVM\bin
(winget install LLVM.LLVM).
Build infrastructure:
* src/coreclr/build-runtime.cmd: new -clangcl flag, also honors a
pre-set __UseClangCl=1 env var (so MSBuild can opt in without
modifying the call into build-runtime.cmd).
* eng/native/gen-buildsys.cmd: when __UseClangCl=1, locates
clang-cl.exe and injects -DCMAKE_C_COMPILER /
-DCMAKE_CXX_COMPILER. Forces the Ninja generator.
* eng/native/configurecompiler.cmake: a 10-line clang-cl branch
inside the existing if (MSVC) block. Each suppression was
empirically verified to actually fire by replacing -Wno-foo with
-Wno-error=foo and rebuilding (see comment block).
Source fixes (all MSVC-safe; verified MSVC build still passes):
* src/coreclr/utilcode/pedecoder.cpp: remove a stray line-continuation
'\' that silently merged two STATIC_CONTRACT_* statements (this
was actually masking a clang frontend crash). Also add explicit
cast for __unaligned char[] -> const char*.
* src/coreclr/jit/gentree.cpp: add missing 'id =' on a compare-
greater-than scalar branch where the ?: result was being discarded
(a real JIT bug found by -Wunused-value).
* src/coreclr/jit/importercalls.cpp: replace two 'impPopStack().val;'
pop-and-discard statements with one 'impPopStack(2);'.
* src/coreclr/jit/utils.cpp: explicit (int) cast on size_t template
argument compared against int (sign-compare).
* src/coreclr/inc/sigparser.h: 'operator= = default' to satisfy
-Wdeprecated-copy-with-user-provided-copy.
* src/coreclr/utilcode/debug.cpp: __assume(0) after
RaiseFailFastException so clang sees FailFastOnAssert as truly
noreturn (the WinAPI declaration isn't __declspec(noreturn)).
* src/coreclr/utilcode/fstring.cpp: gate '#pragma optimize("t", on)'
on _MSC_VER && !__clang__ (clang-cl only accepts an empty arg).
* src/coreclr/utilcode/stacktrace.cpp: reinterpret_cast<LPVOID>(...)
for FARPROC -> LPVOID conversion plus (int) cast on ARRAY_SIZE
for the loop counter.
* src/native/minipal/guid.c: (unsigned int) cast on guid.Data1 for
%08x format spec.
* src/native/minipal/xoshiro128pp.c: 'size_t i' instead of 'int i'
for the sizeof-based loop bound.
Verified:
* MSVC build.cmd Clr.Jit -c Release /p:NoPgoOptimize=true passes
* clang-cl __UseClangCl=1 build.cmd Clr.Jit -c Release
/p:NoPgoOptimize=true passes (clang 22.1.5)
* JIT smoke test (loop sum, Vector<int> SIMD, throw/catch) JITs and
runs correctly with the clang-cl-built clrjit.dll, exit code 100,
identical output to the MSVC-built JIT.
Out of scope for this PR (intended as follow-ups):
* Other CoreCLR components (coreclr.dll, DAC, mscordbi, host).
* Libs native and host clang-cl support.
* VS-integrated -T ClangCL MSBuild path.
* CI pipeline coverage.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
| else if (isScalar) | ||
| { | ||
| reverseCond ? NI_X86Base_CompareScalarNotLessThanOrEqual : NI_X86Base_CompareScalarGreaterThan; | ||
| id = reverseCond ? NI_X86Base_CompareScalarNotLessThanOrEqual : NI_X86Base_CompareScalarGreaterThan; |
There was a problem hiding this comment.
seems like a bug that clang's warning highlighted
There was a problem hiding this comment.
Pull request overview
Experimental, opt-in path to build CoreCLR's JIT on Windows using LLVM clang-cl instead of MSVC cl.exe. Adds a -clangcl flag (and __UseClangCl env-var bridge) to the native build, wires CMake to locate clang-cl.exe and force the Ninja generator, adds a clang-cl-specific warning-suppression block in configurecompiler.cmake, and applies a handful of small source fixes (one of which is a real JIT bug — a missing assignment in GetHWIntrinsicIdForCmpOp's scalar greater-than branch).
Changes:
- New
-clangclopt-in build flag plumbed throughbuild-runtime.cmdandgen-buildsys.cmd, with a clang-cl branch inconfigurecompiler.cmakethat suppresses MSVC-only command-line warnings and clang-only diagnostics that fire widely in current sources. - Source fixes to compile cleanly under clang-cl: stray line continuation in
pedecoder.cpp(also a real bug), missingid =assignment ingentree.cpp(real JIT bug),impPopStack(2)instead of two discardedimpPopStack().val, sign/cast tweaks (utils.cpp,stacktrace.cpp,pedecoder.cpp,guid.c,xoshiro128pp.c), defaultedoperator=forSigParser,__assume(0)afterRaiseFailFastException, and gating an MSVC#pragma optimize("t", on)on!__clang__.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/build-runtime.cmd | Adds -clangcl flag, env-var default, validation against -msbuild, and help text. |
| eng/native/gen-buildsys.cmd | When __UseClangCl=1, locates clang-cl.exe and injects -DCMAKE_C_COMPILER/-DCMAKE_CXX_COMPILER; requires Ninja. |
| eng/native/configurecompiler.cmake | Adds a clang-cl branch inside the existing if (MSVC) block to suppress unused-cli-arg and several clang-only diagnostics. |
| src/coreclr/jit/gentree.cpp | Adds missing id = assignment in scalar greater-than branch (real JIT bug). |
| src/coreclr/jit/importercalls.cpp | Replaces two discarded impPopStack().val statements with a single impPopStack(2). |
| src/coreclr/jit/utils.cpp | Casts bufferSize to int for sign-compare cleanliness. |
| src/coreclr/inc/sigparser.h | Adds defaulted operator= to satisfy -Wdeprecated-copy-with-user-provided-copy. |
| src/coreclr/utilcode/debug.cpp | __assume(0) after RaiseFailFastException so clang treats it as truly noreturn. |
| src/coreclr/utilcode/fstring.cpp | Gates #pragma optimize("t", on) on _MSC_VER && !__clang__. |
| src/coreclr/utilcode/pedecoder.cpp | Removes stray line-continuation that merged two STATIC_CONTRACT_* statements; explicit (const char *) cast for __unaligned. |
| src/coreclr/utilcode/stacktrace.cpp | Cast ARRAY_SIZE to int and reinterpret_cast<LPVOID> around GetProcAddress. |
| src/native/minipal/guid.c | (unsigned int) cast on guid.Data1 for %08x. |
| src/native/minipal/xoshiro128pp.c | Loop index changed from int to size_t to match sizeof-based bound. |
…ource fixes
Promotes the previous -clangcl flag to a top-level build.cmd switch and
extends source fixes to many more native components beyond just the JIT.
Top-level switch:
* eng/build.ps1: new -clangcl switch. Sets $env:__UseClangCl=1 so it
propagates through Arcade -> MSBuild Exec -> all three native build
scripts (src/coreclr/build-runtime.cmd, src/native/libs/build-native.cmd,
src/native/corehost/build.cmd). All three call eng/native/gen-buildsys.cmd
which already reads __UseClangCl, so no further plumbing was needed.
Usage:
build.cmd clr+libs+host -c Release -clangcl /p:NoPgoOptimize=true
Compiler-flag suppressions added to the clang-cl block:
* -Wno-sign-compare, -Wno-deprecated-copy-with-user-provided-copy,
-Wno-nontrivial-memcall, -Wno-unnecessary-virtual-specifier,
-Wno-duplicate-decl-specifier, -Wno-enum-compare,
-Wno-tautological-undefined-compare, -Wno-unused-but-set-parameter
-ferror-limit=4096 (match Unix Clang block; emit all errors per file)
* IPO/LTO disabled for clang-cl: ThinLTO triggers additional LLVM
compiler crashes in CoreCLR codepaths, so set
CMAKE_INTERPROCEDURAL_OPTIMIZATION OFF for clang-cl until the upstream
fix lands.
Source fixes (all MSVC-safe, verified MSVC build still passes):
* gc/vxsort/{vxsort.h,vxsort_targets_*.h,smallsort/*.h,smallsort/codegen/*.py}:
pragma guards used #ifdef __GNUC__ which excludes clang-cl
(clang-cl defines __clang__ but not __GNUC__). Widen to
#if defined(__GNUC__) || defined(__clang__) so the
__attribute__((target("avx2/avx512f"))) push gets emitted under
clang-cl too. The .py codegen is updated so future regeneration
produces the right output.
* gc/gcpriv.h: gate #pragma optimize("t", on) on
defined(_MSC_VER) && !defined(__clang__) (clang-cl only accepts "").
* vm/threads.h + vm/threads.cpp: split AreShadowStacksEnabled into an
out-of-line definition decorated with [[gnu::target("shstk")]] for
clang-cl, since cl.exe always exposes the _rdsspq intrinsic but
clang-cl gates it behind the "shstk" target feature.
* vm/dispatchinfo.h: forward declaration "enum BinderMethodID" must
match the actual definition's fixed underlying type
(enum BinderMethodID : int) under clang.
* native/eventpipe/ds-ipc-pal-namedpipe.c: restructure
case-fallthrough-with-goto pattern in ds_ipc_poll into
switch+poll_should_raise_error flag. Behaviorally identical and
cleaner; helps but does NOT fully sidestep the upstream LLVM crash
on this file (see "Known limitation" below).
* native/libs/System.Globalization.Native/pal_icushim.c: explicit
(unsigned int) cast on GetLastError() for %u format.
Known limitation: upstream LLVM bug
* clang-cl 22.1.5 crashes (clang frontend SIGSEGV) when compiling
src/native/eventpipe/ds-ipc-pal-namedpipe.c. Tried: __attribute__((optnone)),
__attribute__((noinline)), #pragma clang attribute push optnone+noinline,
per-file /Od and -mllvm -opt-bisect-limit=0. None sidestep the
crash, which appears to be in clang's IR emission rather than a
pass we can disable. cl.exe builds the same source cleanly. The
one file gates the rest of the CoreCLR build; this is the only
thing currently between us and an end-to-end build.cmd clr+libs+host
pass on clang-cl. Filing upstream is the next step.
Verified:
* MSVC build.cmd Clr.Jit -c Release /p:NoPgoOptimize=true still passes.
* clang-cl build.cmd Clr.Jit -c Release /p:NoPgoOptimize=true -clangcl
still passes (~30 s) and the JIT smoke test succeeds.
* clang-cl build.cmd clr+libs+host -c Debug -clangcl runs through CMake
configure cleanly and gets ~109/2551 native objects compiled before
the ds-ipc-pal-namedpipe.c crash blocks further progress on CoreCLR
(libs and host targets are unaffected by that one file).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Ran into a bunch of LLVM crashes, seems like it needs more efforts I was willing to put in. Will upstream the bug-fix and some other source changes. |
Experiment: building CoreCLR's JIT (and ~most of native) on Windows with clang-cl
Opt-in path to build CoreCLR's native components on Windows using LLVM
clang-clinstead of MSVCcl.exe. Not intended to merge as-is — opening as draft so we can discuss whether the approach is interesting and where it could go.Two commits:
Clr.Jitbuilds + JIT smoke test passes with the clang-cl-builtclrjit.dll(Vector SIMD, exception throw/catch, loop arithmetic — exit code 100, output identical to MSVC).-clangclpromoted to a top-levelbuild.cmdswitch and many additional source/cmake fixes for CoreCLR runtime, libs native, and host. Hits one upstream LLVM crash that we couldn't sidestep (see "Known limitation").Usage
Requires
clang-cl.exeon%PATH%or under%ProgramFiles%\LLVM\bin(winget install LLVM.LLVM). Tested with clang 22.1.5.Build infrastructure
eng/build.ps1— new-clangclswitch. Sets=1so it propagates through Arcade → MSBuild Exec → all three native build scripts (src/coreclr/build-runtime.cmd,src/native/libs/build-native.cmd,src/native/corehost/build.cmd). All three calleng/native/gen-buildsys.cmdwhich already reads__UseClangCl, so no further plumbing was needed.src/coreclr/build-runtime.cmd—-clangclflag (also honors a pre-set__UseClangCl=1env var).eng/native/gen-buildsys.cmd— discoversclang-cl.exe, injects-DCMAKE_C_COMPILER/-DCMAKE_CXX_COMPILER, forces Ninja generator.eng/native/configurecompiler.cmake— clang-cl branch insideif (MSVC). Each suppression empirically verified to actually fire (audit method: replace-Wno-foowith-Wno-error=fooand rebuild). IPO/LTO is also disabled for clang-cl since ThinLTO triggers additional LLVM crashes.Source fixes (all MSVC-safe; verified MSVC builds still pass)
utilcode/pedecoder.cpp\silently merged twoSTATIC_CONTRACT_*statements (real bug; was masking a clang frontend crash).jit/gentree.cpp(line 31645)id =, computing a?:value and discarding it. Real JIT bug found by-Wunused-value.jit/importercalls.cppimpPopStack().val;discards with oneimpPopStack(2);.jit/utils.cpp(int)cast onsize_ttemplate arg compared againstint.inc/sigparser.hoperator= = defaultto satisfy-Wdeprecated-copy-with-user-provided-copy.utilcode/debug.cpp__assume(0)afterRaiseFailFastExceptionso clang treatsFailFastOnAssertas truly noreturn.utilcode/fstring.cpp+gc/gcpriv.h#pragma optimize("t", on)on_MSC_VER && !__clang__.utilcode/pedecoder.cpp+utilcode/stacktrace.cpp(const char *)andreinterpret_cast<LPVOID>casts.minipal/{guid.c,xoshiro128pp.c}(unsigned int)/size_tcasts for format/sign-compare.gc/vxsort/{vxsort,vxsort_targets_*,smallsort/*}.h+codegen/*.py#ifdef __GNUC__excluded clang-cl; widen to#if defined(__GNUC__) || defined(__clang__)so the AVX2/AVX512__attribute__((target(...)))push gets emitted. The python codegen is updated too.vm/threads.{h,cpp}AreShadowStacksEnabledinto an out-of-line definition decorated with[[gnu::target("shstk")]]for clang-cl, sincecl.exealways exposes_rdsspqbut clang-cl gates it behind theshstktarget feature.vm/dispatchinfo.henum BinderMethodIDwidened toenum BinderMethodID : intto match the actual definition's fixed underlying type.native/eventpipe/ds-ipc-pal-namedpipe.cds_ipc_pollto use a flag — cleaner code, helps clang's CFG analyzer (but does NOT fully sidestep the upstream LLVM crash on this file).native/libs/System.Globalization.Native/pal_icushim.c(unsigned int)cast for%uofGetLastError().Known limitation: upstream LLVM bug
clang-cl 22.1.5 crashes (clang frontend SIGSEGV) when compiling
src/native/eventpipe/ds-ipc-pal-namedpipe.c. We tried:__attribute__((optnone))onds_ipc_poll__attribute__((noinline))combined with optnone#pragma clang attribute push(__attribute__((optnone, noinline)), apply_to = function)/Od-mllvm -opt-bisect-limit=0(which disables every LLVM pass)None sidestep the crash, which appears to happen in clang's IR emission rather than in a pass we can disable.
cl.exebuilds the same source cleanly. This one file currently gates the rest of CoreCLR from building under clang-cl. Filing upstream is the next step — the file is small enough to make a usable repro from.Verification
build.cmd Clr.Jit -c Release /p:NoPgoOptimize=truestill passes.build.cmd Clr.Jit -c Release -clangcl /p:NoPgoOptimize=truepasses; JIT smoke test succeeds (Vector, exception, loop) with output identical to MSVC.build.cmd clr+libs+host -c Release -clangcl /p:NoPgoOptimize=truereaches CMake configure cleanly and compiles ~109/2551 native objects before being blocked by the LLVM bug above. Libs and host targets are unaffected by that one file and would build to completion.Discussion points
gentree.cpp:31645is a real JIT bug, and the\removal inpedecoder.cppfixes a typo cl.exe also miscompiles silently.-clangclbe promoted to a top-levelbuild.cmdflag (as done here) or kept under-cmakeargs?Note
This PR was authored with assistance from GitHub Copilot CLI (Claude Opus 4.7).