Skip to content

lkl: Win64 (x86_64-w64-mingw32) support - #630

Open
xdqi wants to merge 6 commits into
lkl:masterfrom
xdqi:lkl-win64
Open

xdqi wants to merge 6 commits into
lkl:masterfrom
xdqi:lkl-win64

Conversation

@xdqi

@xdqi xdqi commented May 4, 2026 •

Copy link
Copy Markdown

lkl: Win64 (x86_64-w64-mingw32) support

Summary

This PR lets LKL run in a plain 64-bit Windows (MinGW-w64) environment instead of Cygwin/MSYS. Win64 uses the LLP64 data model (long is 32 bit), while the LKL kernel is LP64 (long is 64 bit). The series bridges the two with an LKL-specific long type, builds the kernel with an LP64 compiler that uses the same calling convention as MinGW, and adds CI that runs the tests under wine.

Changes

  1. lkl_long_t / lkl_ulong_t (arch/lkl/include/uapi/asm/lkl_long.h): long long on LLP64 hosts and long everywhere else, so nothing changes for existing hosts. headers_install.py replaces long with these types in the installed UAPI headers. The host library and the tests use them for syscall parameters, pointer casts and return values.
  2. nt-host: implement the memcpy/memset/memmove host ops.
  3. setjmp/longjmp: use MinGW's non-SEH setjmp, so longjmp() doesn't call RtlUnwindEx(), which fails when LKL jumps between thread stacks.
  4. Compiler shim (tools/lkl/scripts/win64-cc.sh, linked as tools/lkl/bin/x86_64-w64-mingw32-gcc): compiles the kernel with x86_64-pc-cygwin-gcc, which emits PE/COFF with the MS x64 calling convention but is LP64. User space is compiled with the real MinGW-w64 gcc.
  5. tools/lkl/scripts/binutils-build.sh: builds binutils 2.47 with the NT weak externals patches LKL needs (tools/lkl/scripts/binutils-patches/), instead of committing prebuilt binaries.
  6. CI: a win64 job that cross compiles on Ubuntu and runs the tests under wine. tools/lkl/scripts/win64-toolchain.sh sets up the toolchain.

Building

tools/lkl/scripts/win64-toolchain.sh   # or provide the compilers yourself
PATH=$HOME/lkl-binutils/bin:/opt/msys2-cross/bin:$PATH \
  make -C tools/lkl CROSS_COMPILE=x86_64-w64-mingw32-
make -C tools/lkl run-tests            # runs the .exe tests with wine on Linux

Toolchain:

Component Source
x86_64-pc-cygwin-gcc (kernel, LP64) msys2-cross release packages
x86_64-w64-mingw32-gcc (user space, LLP64) msys2-cross (any MinGW-w64 gcc should work)
binutils with the NT weak externals patches tools/lkl/scripts/binutils-build.sh

Testing

Under wine: 100 tests, 89 ok, 0 failed, 11 skipped (lklfuse, hijack and vfio don't apply to Windows), including boot and the ext4/btrfs/vfat/xfs disk tests. Native Linux tests are unchanged.

Disclaimer

This pull request is mostly done by Claude Code, but I audited every line of code :).

@tavip tavip left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR @xdqi ! Could you please expand a little bit about the benefits of switching to LLP64?

Comment thread arch/lkl/include/uapi/asm/lkl_long.h Outdated
Comment thread tools/lkl/Makefile.autoconf Outdated

define nt_host
$(call set_autoconf_var,NT,y)
$(call set_kernel_config,INIT_STACK_ALL_ZERO,n)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used to make my old Cygwin GCC 11 happy. We can remove it if we maintain our own newer Cygwin cross compiler.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was only needed for the old Cygwin GCC 11 (Kconfig probes the MinGW gcc, which supports -ftrivial-auto-var-init, while the kernel was built with GCC 11, which doesn't). With a current Cygwin GCC it builds fine, so it's removed.

Comment thread tools/lkl/bin/x86_64-w64-mingw32-ld Outdated
@tavip
tavip requested a review from thehajime May 4, 2026 18:44
@xdqi

xdqi commented May 5, 2026

Copy link
Copy Markdown
Author

Just to make the LKL run under vanilla 64-bit Win32 environment instaed of Cygwin.
I used the Cygwin compiler to compile kernel because kernel have to work in LP64.
But the Win32 is LLP64, so the bridging code (LKL API for user) must use a special LKL long type.

@thehajime

Copy link
Copy Markdown
Member

@xdqi thanks for the patchset and sorry for the late response (resulting required to rebase your branch..).
I'll look into details to your patches very soon.

one quick question: do you think it's better to have a separate test matrix other than current msys2, or drop the msys2 to replace with this patchset ? I'd be fine either case.

@thehajime

Copy link
Copy Markdown
Member

@xdqi read through the patches briefly.
most of them, including lkl_long_t typedefs, looks fine for me.

I'd like to test this new platform over gh actions (or any CI) during any commits, because it's very hard to track down which commits break this feature, especially the platform is non-Linux one. It would be nice to include a test update in this patchset if possible.

@xdqi

xdqi commented May 18, 2026

Copy link
Copy Markdown
Author

@thehajime
I prefer to go with Win64 test matrix instead of MSYS ones.
For the tests, I tried to build a cross toolchain from Linux to all MSYS2 targets, see https://github.com/xdqi/msys-cross/blob/main/scripts/build.sh . But I haven't deeply gone through current test pipelines in lkl/linux repo. I'll try to add two tests with MINGW64/MSYS cross toolchains + wine under Linux, and MSYS gcc and MINGW64 gcc under MSYS2.

xdqi added 6 commits October 4, 2026 21:55
On Win64 (MinGW-w64) long is 32 bit (LLP64) while the LKL kernel is
built for LP64, where long is 64 bit. Using long in the host interface
breaks syscall parameters, UAPI struct layouts and pointer casts between
the application and the kernel.

Add lkl_long_t / lkl_ulong_t (arch/lkl/include/uapi/asm/lkl_long.h),
which are long long on LLP64 hosts and long everywhere else, so nothing
changes for the existing hosts.

headers_install.py replaces long with lkl_long_t in the installed UAPI
headers and adds the lkl_long.h include. The host library and the tests
use lkl_long_t for syscall parameters, ioctl pointer casts and return
values, and print them with %lld. Also fix the case of the Windows
header names in virtio_net_wintap.c so it builds on case sensitive
file systems.

Signed-off-by: Sheldon Qi <3365420+xdqi@users.noreply.github.com>
CONFIG_LKL_HOST_MEMCPY/MEMSET/MEMMOVE are enabled in the kernel config,
so the kernel expects these lkl_host_ops to be set. Without them the
kernel does not start on Win64.

Wrap the libc routines so that the lkl_ulong_t signature matches.

Signed-off-by: Sheldon Qi <3365420+xdqi@users.noreply.github.com>
On Win64 longjmp() unwinds the stack with RtlUnwindEx(), which fails
when LKL jumps between the stacks of different threads.

Define __USE_MINGW_SETJMP_NON_SEH so that setjmp() does not record a
frame; longjmp() then only restores the registers. The Win64 jmp_buf
also holds xmm registers and must be 16 bytes aligned, so align it
inside struct lkl_jmp_buf.

Signed-off-by: Sheldon Qi <3365420+xdqi@users.noreply.github.com>
The kernel has to be built for LP64, but MinGW-w64 compilers are LLP64.
Add a compiler shim, tools/lkl/scripts/win64-cc.sh, linked as
x86_64-w64-mingw32-gcc from tools/lkl/bin, which is in PATH for the
kernel build. It compiles the kernel with x86_64-pc-cygwin-gcc, which
produces PE/COFF objects using the same MS x64 calling convention as
MinGW, but with a 64 bit long. User space code is still compiled with
the real MinGW-w64 gcc and talks to the kernel through lkl_long_t.

The kernel link needs binutils with the NT weak externals fixes, see
tools/lkl/scripts/binutils-build.sh. Build with:

  make -C tools/lkl CROSS_COMPILE=x86_64-w64-mingw32-

Also skip <sys/resource.h> in disk-vfio-pci.c, MinGW does not have it.

Signed-off-by: Sheldon Qi <3365420+xdqi@users.noreply.github.com>
Linking LKL for Windows needs binutils with fixes for PE/COFF NT weak
externals that are not upstream:

- relocate the weak external aux entries in ld -r output (Octavian
  Purdila)
- convert NT weak externals to local symbols in objcopy -G (Octavian
  Purdila)
- resolve the weak externals by name instead of the stale tag index
  left by ld -r, which made objcopy -G resolve some of them to the
  wrong symbol (Sheldon Qi)

binutils 2.47 implements linking NT weak externals natively, so the
older "Fix linker error when using NT weak externals" patch is no
longer needed.

Instead of committing more prebuilt binaries, add binutils-build.sh,
which downloads binutils 2.47, applies the patches from
binutils-patches/ and installs the tools in $BINUTILS_PREFIX.

Signed-off-by: Sheldon Qi <3365420+xdqi@users.noreply.github.com>
Add a win64 job that cross compiles LKL on Linux and runs the tests
with wine.

win64-toolchain.sh installs x86_64-pc-cygwin-gcc and the MinGW-w64 gcc
from the msys2-cross release packages (https://github.com/xdqi/msys-cross)
and builds binutils with binutils-build.sh.

Under wine the tap and wintap backends are not usable, so skip the tap
test on Windows hosts and the wintap test when netsh is missing.

Signed-off-by: Sheldon Qi <3365420+xdqi@users.noreply.github.com>
@xdqi xdqi changed the title Win64 LLP64 ABI support with cross-compiler tooling lkl: Win64 (x86_64-w64-mingw32) support Oct 4, 2026
@xdqi

xdqi commented Oct 4, 2026

Copy link
Copy Markdown
Author

Thanks @tavip @thehajime, and sorry for the long delay. I rebased on the current master and reworked the series around your comments:

No more committed binaries (@tavip): this PR no longer touches any binary. tools/lkl/scripts/binutils-build.sh downloads binutils 2.47, applies the patches in tools/lkl/scripts/binutils-patches/ and installs the tools in $BINUTILS_PREFIX. binutils 2.47 now links NT weak externals natively, so only three patches are still needed: your "Relocate aux entries for NT weak externals" and "Convert NT weak externals to locals", plus the name-based weak external resolution fix. The existing i686 and MSYS binaries are left alone; they could switch to the script in a follow-up.

CI (@thehajime): there's a new win64 job that cross compiles on Ubuntu and runs the tests under wine. tools/lkl/scripts/win64-toolchain.sh installs x86_64-pc-cygwin-gcc and the MinGW-w64 gcc from the msys2-cross release packages (Ubuntu/Debian don't package a Cygwin cross compiler) and builds binutils with the script above. Locally: 100 tests, 89 ok, 0 failed, 11 skipped (lklfuse, hijack and vfio don't apply to Windows), including the ext4/btrfs/vfat/xfs disk tests. I kept the existing MSYS2 job for now; I'm happy to drop it in a follow-up if you'd rather keep only Win64.

Makefile.autoconf: the INIT_STACK_ALL_ZERO=n workaround (@tavip) was only needed for the old Cygwin GCC 11, so I removed it. I also dropped the --image-base 0x10000 change, because nt64_host already passes --image-base 0x10000000 later on the command line and that one wins. This PR no longer changes Makefile.autoconf.

Other changes:

  • lkl_long_t / lkl_ulong_t naming, as you suggested.
  • The hand-written setjmp/longjmp assembly is replaced by MinGW's non-SEH setjmp (__USE_MINGW_SETJMP_NON_SEH), so longjmp() restores registers, including xmm6-15, without RtlUnwindEx(). The old version only worked because GCC happened to inline it without a prologue.
  • disk-vfio-pci.c was rewritten on master in the meantime: skip <sys/resource.h> on MinGW and fix a pointer-to-unsigned long cast.
  • net.sh skips the tap test on Windows hosts and the wintap test when netsh isn't available (under wine).
  • Fixed the %ld format warnings for lkl_long_t in the tests.

Native Linux boot/disk/net tests still pass.

checkpatch's only remaining complaint is "do not add new typedefs" for lkl_long_t / lkl_ulong_t. I kept them as typedefs on purpose, like __kernel_long_t; a #define would silence checkpatch but is worse as a type. Let me know if you'd prefer it the other way.

@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown

Test Results

124 files  + 18  124 suites  +18   10m 21s ⏱️ -9s
206 tests ±  0  195 ✅ ± 0  11 💤 ± 0  0 ❌ ±0 
890 runs  +100  823 ✅ +89  67 💤 +11  0 ❌ ±0 

Results for commit 2046bd2. ± Comparison against base commit d0f76a7.

@tavip tavip left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thanks @xdqi this is great work!

#endif

void *thread_stack(unsigned long *size)
void *thread_stack(lkl_ulong_t *size)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching to lkl_ulong_t int this files is not strictly required, but I agree it is more consistent changing it as well.

#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
#
# Build GNU binutils with the PE/COFF NT weak externals fixes needed to

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, thank you very much for cleaning this up!

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.

3 participants