Skip to content

Native Windows (MSVC) support - #154

Merged
mmelnich merged 13 commits into
mainfrom
windows-support
Aug 6, 2026
Merged

Native Windows (MSVC) support#154
mmelnich merged 13 commits into
mainfrom
windows-support

Conversation

@mmelnich

@mmelnich mmelnich commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Native Windows (MSVC) support, mirroring the recipe RandBLAS PR #179 validated: Visual Studio 2022, Intel oneMKL from vcpkg with ILP64 + sequential linking (RandBLAS's MKL sparse backend requires 64-bit MKL_INT), and BLAS++/LAPACK++ built from the BallisticLA branches that carry the two one-line MSVC fixes still open upstream (blaspp PR #132, lapackpp PR #87).

An audit of the whole tree found the library headers need zero code changes for MSVC — every blocker was at the edges. Four commits:

  1. RandBLAS submodule bump to current main. The old pin (May 2026) predates RandBLAS's Windows support, so this is a prerequisite; it also brings in three sketching-performance PRs (#173, #174, #176). The full local suite — RandLAPACK's 337 tests plus RandBLAS's 449 — passes against the new pin.
  2. MSVC portability, sources + CMake:
    • Tests: __PRETTY_FUNCTION__ (GCC/Clang-only) → the __RANDBLAS_PRETTY_FUNCTION__ macro RandBLAS's comparison.hh now provides (36 sites; every affected file already includes that header).
    • Benchmarks: the repository's only two C variable-length arrays (gejsv integer workspaces) become heap allocations; a pre-existing workspace leak in find_test_mat_spectrum is freed along the way.
    • CMake: warning flags and benchmark -g become compiler-conditional (MSVC gets /W4); the header-only target exports /utf-8 to MSVC consumers (several headers carry non-ASCII characters in string literals); SANITIZE_ADDRESS learns the MSVC spelling; new CMake/rl_runtime_dlls.cmake stages dependency DLLs beside test executables (gtest_discover_tests runs them at build time); the lapackpp_DIR recorded in the installed package config is normalized with TO_CMAKE_PATH so Windows backslash paths don't arrive as escape sequences downstream; the template-heavy test translation units get /bigobj.
  3. core-windows workflow: windows-2022 runner, cached dependency stack via a composite action, Ninja build, full ctest (standard ABRIK exclusion). The job is named build-windows, deliberately distinct from the required check build, so it can prove itself before being made required. (RandBLAS's own tests ran inside this job in the first CI rounds; item 7 turns them off.)
  4. install.ps1: thin companion to install.sh — reuses the CI dependency script (so the recipe exists exactly once), supports -MklRoot for oneAPI installs instead of vcpkg, and mirrors the ..\RandNLA-project layout. INSTALL.md gains a native-Windows section.

OpenMP is off on native Windows for now: rl_rpchol.hh uses an OpenMP collapse(2) clause that MSVC only accepts under /openmp:llvm, while RandBLAS's build system pins MSVC OpenMP to /openmp:experimental. All OpenMP use is guarded, so serial builds are fully functional; reconciling the two runtimes is a RandBLAS-side follow-up.

Also catalogued but deliberately not fixed here (they never touch ILP64 BLAS calls): the long-typed timing fields overflow at ~35.8 minutes per timer on Windows (LLP64), and rl_memory_tracker.hh's byte arithmetic overflows at 2 GiB working sets — diagnostics-only issues, queued for post-1.0 cleanup.

Verified on Linux after all changes: clean build, 785/786 tests (the one failure is a documented machine-local GPU issue predating this work). The first Windows execution is this PR's own CI run.


Added after the first CI round:

  1. Fix from CI iteration 1: rl_hqrrp.hh and three BQRRP benchmarks kept dead #ifdef LAPACK_FORTRAN_STRLEN_END blocks inside LAPACK macro invocations — undefined behavior that GCC/Clang tolerate and MSVC rejects (C2121). Pure deletion; the lapackpp variadic macros append the string-length arguments themselves.
  2. install-windows CI job, filling the slot reserved in the installer workflow: runs install.ps1 end-to-end plus an idempotent re-run, sharing the cached dependency stack with core-windows (new -DependencyRoot installer option).
  3. Core-CI speedups (measured before: core-linux 10–12 min, core-windows 12–16): BLAS++/LAPACK++/Random123 installs are now cached in core-linux/core-macos; builds use all runner cores instead of -j2; the RandBLAS submodule's ~450 tests no longer build and run inside every RandLAPACK job (-DBUILD_TESTS=OFF — the pinned commit is already tested by RandBLAS's own CI; the installer lanes keep them for user parity); and the Debug/ASan build+suite is a parallel build-asan job instead of running sequentially inside build. The required-check job name build is unchanged.
  4. Installer scripts moved to install/ (install/install.sh, install/install.ps1), with a two-line wrapper kept at the root so bash install.sh keeps working — the CI installer lanes call the wrapper on purpose so it stays under test.
  5. docs/CI.md: a map of the CI setup — what each of the eight jobs validates, which checks are required, what is deliberately red (the core-macos canary) or disabled (submodule tests, Windows OpenMP), cache keys and how to bust them, and per-OS local-reproduction commands.
  6. RandBLAS bump to /openmp:llvm (post-RandBLAS#184) and an OpenMP matrix leg in core-windows: with RandBLAS selecting MSVC's LLVM OpenMP runtime, RandLAPACK's OpenMP code (64-bit loop indices, the collapse(2) in rl_rpchol) compiles under MSVC for the first time. The workflow becomes serial + openmp, mirroring RandBLAS's own Windows matrix; the "OpenMP disabled on MSVC" caveat retires from INSTALL.md.
  7. install.ps1 sanity-check fix: the clone check tested a path that never existed (RandLAPACK/RandLAPACK.hh; the umbrella header sits at the repo root), so the install-windows job had never gotten past it.

Advances the pinned submodule six commits: kernel speed improvements
(RandBLAS#173), lazy submatrix sampling for SparseSkOp (RandBLAS#174),
sparse sketching speedups (RandBLAS#176), Windows/MSVC portability
(RandBLAS#179), and two documentation updates. The Windows support
about to be added here requires a RandBLAS that already compiles under
MSVC, which the old pin (May 2026) predates.
RandLAPACK's library headers already compile under MSVC; the blockers
were at the edges. Tests: rename __PRETTY_FUNCTION__ (GCC/Clang-only)
to the __RANDBLAS_PRETTY_FUNCTION__ portability macro that RandBLAS's
comparison.hh now provides -- every affected file already includes that
header. Benchmarks: replace the repository's only two C variable-length
arrays (gejsv iwork workspaces) with heap allocations, freeing a
pre-existing workspace leak in find_test_mat_spectrum along the way.

CMake: warning flags and benchmark -g become compiler-conditional
(MSVC gets /W4); the header-only target exports /utf-8 to MSVC
consumers because several headers carry non-ASCII characters in string
literals; SANITIZE_ADDRESS learns the MSVC spelling; new
rl_runtime_dlls.cmake stages imported dependency DLLs beside test
executables (gtest_discover_tests runs them at build time) -- named
with the rl_ prefix because a module literally called RuntimeDLLs.cmake
shadows RandBLAS's identically-named module through the shared
CMAKE_MODULE_PATH and breaks the submodule configure; the lapackpp_DIR
recorded in the installed package config is normalized with
TO_CMAKE_PATH so native Windows backslash paths do not arrive as escape
sequences in downstream find_package calls; the template-heavy test
translation units get /bigobj.
windows-2022 + MSVC + oneMKL from vcpkg, ILP64 and sequential --
required because RandBLAS's MKL sparse backend static-asserts that its
int64_t indices match MKL_INT. BLAS++ and LAPACK++ build from the
BallisticLA fork branches carrying the two one-line MSVC fixes
(upstream blaspp PR #132 and lapackpp PR #87 are still open). All five
dependencies cache between runs, keyed on the setup script.

The build is serial (no OpenMP) for now: rl_rpchol.hh uses an OpenMP
collapse(2) clause that MSVC only accepts under /openmp:llvm, while
RandBLAS's build system pins MSVC OpenMP to /openmp:experimental.
RandLAPACK guards all OpenMP use, so serial builds are fully
functional. The job is named build-windows, distinct from the required
check 'build', so it can prove itself before anyone makes it required.

.github/scripts/windows/run-ci.ps1 -Task Core -SetupDependencies
reproduces the CI job locally from an MSVC developer prompt.
Thin companion to install.sh for MSVC users: builds or reuses the
dependency stack (oneMKL through vcpkg, or -MklRoot for an existing
oneAPI install), then configures, builds, installs, and tests
RandLAPACK, mirroring install.sh's sibling RandNLA-project layout.
The dependency logic lives in the CI setup script so there is exactly
one copy of the recipe. INSTALL.md gains a native-Windows section.
rl_hqrrp.hh and three BQRRP benchmarks kept dead
#ifdef LAPACK_FORTRAN_STRLEN_END blocks (contents commented out) inside
the argument lists of LAPACK_dlarfb/dlarf/dgeqrf/dgejsv calls. Those
names are function-like macros in lapackpp's fortran.h, and a
preprocessor directive inside a macro invocation is undefined behavior:
GCC and Clang tolerate it, MSVC rejects it (C2121), which broke every
test translation unit in the first core-windows run. The blocks were
dead on all platforms -- lapackpp's variadic macros append the hidden
string-length arguments themselves.
Runs install.ps1 end-to-end on windows-2022 plus an idempotent re-run,
completing the per-OS two-lane structure (hand-written recipe in the
core workflow, installer contract in this one). The job shares the
cached dependency stack with core-windows; install.ps1 gains a
-DependencyRoot option so CI can point it at that shared location
instead of rebuilding dependencies under the project directory.
Measured before this change: core-linux 10-12 min, core-windows 12-16.
Four fixes:

- BLAS++/LAPACK++/Random123 installs are cached in core-linux and
  core-macos (they were rebuilt from scratch on every run; the
  installer workflow has cached them since it was introduced).
- make -j2 -> -j(all cores) on Linux and macOS.
- The RandBLAS submodule's ~450 tests no longer build and run inside
  every RandLAPACK job (-DBUILD_TESTS=OFF in the three core recipes):
  the pinned commit is already tested by RandBLAS's own CI. The
  installer lanes keep them, preserving what a user's install builds.
- The Debug/ASan build ran sequentially inside the same job as the
  Release build; it is now its own parallel job (build-asan) on both
  Linux and macOS. The required-check job name 'build' is unchanged.
install.sh and install.ps1 now live in an install/ directory instead of
cluttering the repository root. A two-line wrapper stays at the root so
the documented `bash install.sh` one-liner keeps working; the CI
installer lanes invoke the wrapper on purpose so it is under test too.
Both scripts learn their new location (the repo root is one directory
up), and all workflow/documentation references are updated.
Four workflows, eight jobs, three cache families, a deliberate canary,
and dependency clones pointing at fork branches -- enough moving parts
that newcomers (and future us) need the map: what each job validates,
which checks are required and why the rest are not yet, what is
deliberately red or disabled, where the caches live and how to bust
them, and how to reproduce each lane locally.
TestCQRRP.CQRRT_large_full_rank's Frobenius orthogonality metric came
in at 4.4e-11 on Windows CI (vcpkg oneMKL, sequential) against
atol = eps^0.7 = 1.1e-11, deterministically across runners. The metric
depends on the BLAS build's accumulation order, and eps^0.7 leaves no
headroom for that; eps^0.65 (2.7e-10) still asserts orthogonality to
ten digits.
The umbrella header RandLAPACK.hh sits at the repository root, not
inside the RandLAPACK/ source directory; the check tested a path that
has never existed, so install.ps1 refused to run anywhere -- the
install-windows job has been failing on this line since it was added,
masked first by the STRLEN compile error and then by the CQRRT
tolerance failure in the sibling jobs.
rileyjmurray pushed a commit to BallisticLA/RandBLAS that referenced this pull request Aug 6, 2026
Follow-up to the Windows portability work in #179.

**What #179 did:** made RandBLAS build natively with MSVC, including the
CMake logic that selects an MSVC OpenMP mode. It chose
`/openmp:experimental`, the minimum needed for RandBLAS itself: plain
`/openmp` cannot compile the `omp simd` directive in the sparse kernels.

**What this PR changes:** the selected mode becomes `/openmp:llvm`. The
experimental mode is still OpenMP 2.0 underneath, which only allows
signed 32-bit loop indices and has no `collapse` clause. RandLAPACK
parallelizes loops with `int64_t` indices and uses `collapse(2)`, so
with the experimental mode pinned here, RandLAPACK's native Windows
builds (BallisticLA/RandLAPACK#154) have to disable OpenMP entirely. The
LLVM mode supports 64-bit indices, `collapse`, and everything the
experimental mode provided, including `omp simd` — so RandBLAS keeps
compiling as before and downstream consumers stop being blocked.

**Also fixed along the way:** the re-stamp of `OpenMP::OpenMP_CXX` after
`find_package` hardcoded the mode string, so a user passing
`-DOpenMP_CXX_FLAGS=...` at configure time was silently overridden. The
requested flags are now captured before the find and re-applied
afterward, in both the build tree (`CMake/OpenMP.cmake`) and the
installed package config (`RandBLASConfig.cmake.in`).

**Validation:** the Windows CI matrix from #179 (openmp-release leg, 459
tests) is the gate — it exercises the sparse kernels' `omp simd` under
the new mode. Serial and ASan legs are unaffected.

**Downstream plan:** once this merges, RandLAPACK bumps its submodule
pin and adds an OpenMP leg to its own Windows CI.
Advances the pin one commit to pick up RandBLAS #184, which selects
MSVC's /openmp:llvm mode (64-bit loop indices and the collapse clause,
which RandLAPACK's parallel loops need) and compiles the sparse
kernels' omp simd directive only where OpenMP actually implements it.
With RandBLAS on /openmp:llvm, RandLAPACK's OpenMP code (int64 loop
indices, the collapse clause in rl_rpchol) compiles under MSVC for the
first time. The workflow becomes a two-leg matrix (serial + openmp),
mirroring RandBLAS's own Windows CI; run-ci.ps1 gains an -OpenMP
switch. INSTALL.md's 'OpenMP is disabled on MSVC' caveat retires.
@mmelnich
mmelnich merged commit 1cbe9e0 into main Aug 6, 2026
15 of 19 checks passed
@mmelnich
mmelnich deleted the windows-support branch August 6, 2026 19:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant