Skip to content

Add explicit MinGW Windows targets - #10637

Merged
lukewilliamboswell merged 6 commits into
mainfrom
fix/windows-abi-targets
Aug 24, 2026
Merged

lukewilliamboswell merged 6 commits into
mainfrom
fix/windows-abi-targets

Conversation

@lukewilliamboswell

Copy link
Copy Markdown
Collaborator

Summary

  • keep x64win, x64v1win, arm64win, and arm64v1win as MSVC targets
  • add explicit x64mingw, x64v1mingw, arm64mingw, and arm64v1mingw targets using GNU Windows triples and target-specific embedded objects
  • carry the selected Windows runtime ABI into the linker, using SDK/MSVC discovery only for MSVC and LLD MinGW mode with explicit platform runtime inputs for MinGW
  • document the target identity and linker invariants in design.md

Root cause

Roc represented Windows as one target family. Both target triples were MSVC triples, and the Windows linker path unconditionally resolved an MSVC libc installation, added Windows SDK/MSVC library paths, and linked MSVC default libraries. COFF alone cannot identify whether a platform archive expects the MSVC or MinGW runtime, so a Go cgo archive built with a GNU Windows toolchain could not be linked correctly.

This change makes that ABI choice explicit at target selection and preserves it through code generation, embedded builtins, platform input lookup, and final linking. MinGW linking uses lld-link -lldmingw, disables implicit default libraries, and consumes the startup objects, runtime archives, and import libraries declared by the platform.

Validation

  • zig build minici --summary failures — 75/75 phases passed
  • focused Roc target and CLI linker tests
  • generated and linked Go c-archive hosts for windows/amd64 and windows/arm64 using Zig GNU Windows targets
  • built PE32+ x86-64 and AArch64 executables through Roc with both --opt=speed and --opt=dev

Closes #8779.

@lukewilliamboswell

Copy link
Copy Markdown
Collaborator Author

Validated this downstream in roc-platform-template-go#13 using this PR at 149cc4f8e50fbcc7bd1301fe770137f07c1df189.

Results:

  • built Go c-archive hosts and linked the 12-example suite for x64mingw, x64v1mingw, arm64mingw, and arm64v1mingw (48 Windows target/app combinations, including speed and dev builds)
  • linked against explicit Zig 0.16.0 MinGW startup/runtime and UCRT/Windows import inputs carried by the platform
  • all Windows roc build invocations completed with zero warnings
  • verified PE machine types 0x8664 for x64 and 0xaa64 for ARM64
  • bundled the four Windows targets together with the existing six macOS/Linux targets under the normal Roc dependency-size limit
  • also cross-linked the existing macOS/Linux matrices and ran all 27 native Linux behavioral cases

One downstream detail found during validation: Go Windows/ARM64 c-archives contained the required exported callbacks but lacked the archive symbol index needed by lld-link; running zig ar s on generated Windows c-archives fixed extraction and is now part of the platform build script.

The downstream PR remains draft until these target changes are available in a nightly, at which point its Roc pin can be advanced and the new Windows x64 consumer jobs can run.

@lukewilliamboswell
lukewilliamboswell marked this pull request as ready for review August 6, 2026 02:30
@greptile-apps

greptile-apps Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR separates MSVC and MinGW Windows target identities and carries that ABI choice through target selection, builtins, platform inputs, and linking.

  • Adds x86-64 and AArch64 MinGW targets, including baseline variants.
  • Uses explicit MinGW linker mode and platform-declared startup/runtime libraries while retaining SDK/MSVC discovery for MSVC.
  • Vendors MinGW runtime fixtures and extends cross-target platform-host generation and validation.
  • Documents Windows target identity and linker invariants.

Confidence Score: 4/5

The PR does not yet appear safe to merge because unsupported Windows ABIs can still abort target detection and build configuration.

windowsAbiFromStd returns null outside MSVC and GNU, but all Windows branches of RocTarget.fromStdTarget still force-unwrap that value, and native/build target naming reaches this conversion without an earlier diagnostic.

Files Needing Attention: src/target/mod.zig, build.zig

Important Files Changed

Filename Overview
src/target/mod.zig Adds explicit Windows ABI-aware target identities, but the previously reported unsupported-ABI forced unwrap remains.
src/cli/target_selection.zig Extends CLI target parsing and selection to distinguish MinGW targets from existing MSVC targets.
src/cli/linker.zig Carries the selected Windows ABI into linking and separates MSVC discovery from explicit MinGW runtime inputs.
build.zig Builds MinGW target artifacts and distributes vendored runtime inputs across test platforms, while still calling the panic-prone target conversion path.
ci/vendor_mingw_runtime.py Reproducibly discovers and vendors fixed MinGW startup objects, runtime archives, and import libraries.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    Selection[Target selection] --> ABI{Windows ABI}
    ABI -->|MSVC| MSVCTarget[x64win / arm64win]
    ABI -->|GNU| MinGWTarget[x64mingw / arm64mingw]
    MSVCTarget --> MSVCInputs[SDK and MSVC discovery]
    MinGWTarget --> MinGWInputs[Platform-declared startup and runtime inputs]
    MSVCInputs --> Linker[Windows linker]
    MinGWInputs --> LLD[LLD MinGW mode]
    Linker --> Artifact[Windows artifact]
    LLD --> Artifact
Loading

Reviews (3): Last reviewed commit: "Make the dylib test host a multi-member ..." | Re-trigger Greptile

Comment thread src/target/mod.zig
switch (classifyOs(os)) {
.macos => return .x64mac,
.windows => return .x64win,
.windows => return switch (windowsAbiFromStd(abi).?) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Unsupported Windows ABI unwrap

A Windows target with an ABI outside .msvc or .gnu makes windowsAbiFromStd return null, which this forced unwrap turns into a build-time panic rather than an actionable unsupported-target diagnostic.

lukewilliamboswell added a commit that referenced this pull request Aug 23, 2026
On Windows, `roc <app>` fails to link any app with a platform that carries
its own compiler-rt:

    lld-link: error: duplicate symbol: memcpy
    >>> defined at roc_machine_code_shim.lib(compiler_rt.obj)

In run mode the machine-code shim is substituted at the `app` position of the
platform's `targets:` inputs, so it lands next to a platform host archive that
is already a compiler-rt carrier. COFF rejects the overlapping definitions
outright -- the same hazard already noted on the boxy object in this file.
`roc build` is unaffected because it never links the shim.

The shim does not need the bundled copy. Its own objects reference only
memcpy, memmove and memset:

    roc_machine_code_shim_zcu.obj  -> memcpy, memmove, memset
    roc_builtins.obj               -> memcpy, memmove, memset
    host_trampoline.obj            -> (none)

`bundle_compiler_rt` additionally defines __extendhfsf2, floor, fmaxl,
__fixdfti, __modti3, __netf2 and the rest, none of which the shim calls, and
those surplus definitions are exactly what collide.

Applied to every target rather than just Windows. Keeping it ABI-conditional
would get harder as more Windows targets land (#10637), and the surplus
definitions are no more wanted on ELF and Mach-O than on COFF -- those formats
just tolerate the overlap today.

Verified on Windows with a rebuilt roc:
- running a roc-ray app against its released platform bundle links, opens a
  window and exits cleanly
- headerless default-platform runs still work, including a runtime app doing
  I128 division, Dec.sqrt and F64.sqrt -- this is the path where the shim was
  the sole compiler-rt carrier, since default_platform_runtime_obj and
  builtins_obj both set bundle_compiler_rt = false

The Linux shim is -nostdlib with link_libc = false, so its mem* references now
have to resolve from the rest of the link; Linux and macOS coverage here is
CI's, not local.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lukewilliamboswell and others added 6 commits August 23, 2026 21:30
A stock `zig build` on Windows targets the gnu ABI, so the native
target is now x64mingw rather than x64win, and a MinGW link only sees
what the platform declares. Give every test platform x64mingw and
arm64mingw entries that list Zig 0.16.0's mingw-w64 startup objects,
runtime archives, and UCRT/Win32 import libraries, and check those
files in under test/fx (regenerate with ci/vendor_mingw_runtime.py),
the same way the musl crt1.o/libc.a are. build.zig copies them into
the other test platforms next to host.lib, and the hosts that were
hard-coded to the MSVC target dir now follow the native ABI.

Shared outputs list dllcrt2.obj (DllMainCRTStartup) instead of
crt2.obj, archives list no startup object, and http-headers adds
ws2_32.lib for its Winsock host.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
matchesHostOsAndArch ignores the ABI, so on a mingw-native host both
x64win and x64mingw matched and the platform's listing order decided
the default. The interpreter backend only builds for the exact native
target, so a platform listing x64win first failed `roc build` outright.

Skip a host target in the other ABI whenever the platform lists one in
the native ABI (in either CPU-level spelling). Listing order still
decides among wasm and cross targets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
LLD's MinGW-mode DLL entry is _DllMainCRTStartup while mingw-w64's
dllcrt2 defines DllMainCRTStartup; alias them the way `zig cc -shared`
does.

Vtable-ABI links wrapped every Windows platform input in /wholearchive.
A MinGW platform lists its C runtime and import libraries after `app`,
and force-loading every member of libmingw32 and of each import library
collides on overlapping definitions (__NULL_IMPORT_DESCRIPTOR, the
delay-load helpers, ...). Link the post-app inputs lazily on MinGW; host
archives that need full inclusion come before `app`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The LLVM backend references hosted functions weakly, and a weak
reference alone never extracts an archive member. LLD's COFF symbol
table goes further: a weak reference that reaches a lazy archive entry
before a strong one replaces it with a plain undefined, so with a
multi-member host archive (a Go c-archive, say) every hosted symbol the
app used resolved to null and the program called through a null
pointer. Whether it happened depended on which other symbols the host's
own startup object still had to drag in.

Pass every hosted symbol the app references as a link root
(/include on COFF, --undefined on ELF, -u on Mach-O) so the link must
resolve it from the platform inputs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every test platform host was a single object, so its hosted functions
always came along with whatever pulled that object in, and the hosted
symbol rooting in the previous commit had no in-repo test. Move the
dylib platform's hosted functions into host_hosted.zig, compiled as a
second archive member that nothing but the app references. Without the
rooting, that member is never extracted: the shared DCE canary is
missing from the DLL and the loader dies calling through null. With it,
run-test-dylib passes unchanged, including its section-GC assertions,
which still hold within the member.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Want your agent to iterate on Greptile's feedback? Try greploops.

lukewilliamboswell added a commit that referenced this pull request Aug 24, 2026
On Windows, `roc <app>` fails to link any app with a platform that carries
its own compiler-rt:

    lld-link: error: duplicate symbol: memcpy
    >>> defined at roc_machine_code_shim.lib(compiler_rt.obj)

In run mode the machine-code shim is substituted at the `app` position of the
platform's `targets:` inputs, so it lands next to a platform host archive that
is already a compiler-rt carrier. COFF rejects the overlapping definitions
outright -- the same hazard already noted on the boxy object in this file.
`roc build` is unaffected because it never links the shim.

The shim does not need the bundled copy. Its own objects reference only
memcpy, memmove and memset:

    roc_machine_code_shim_zcu.obj  -> memcpy, memmove, memset
    roc_builtins.obj               -> memcpy, memmove, memset
    host_trampoline.obj            -> (none)

`bundle_compiler_rt` additionally defines __extendhfsf2, floor, fmaxl,
__fixdfti, __modti3, __netf2 and the rest, none of which the shim calls, and
those surplus definitions are exactly what collide.

Applied to every target rather than just Windows. Keeping it ABI-conditional
would get harder as more Windows targets land (#10637), and the surplus
definitions are no more wanted on ELF and Mach-O than on COFF -- those formats
just tolerate the overlap today.

Verified on Windows with a rebuilt roc:
- running a roc-ray app against its released platform bundle links, opens a
  window and exits cleanly
- headerless default-platform runs still work, including a runtime app doing
  I128 division, Dec.sqrt and F64.sqrt -- this is the path where the shim was
  the sole compiler-rt carrier, since default_platform_runtime_obj and
  builtins_obj both set bundle_compiler_rt = false

The Linux shim is -nostdlib with link_libc = false, so its mem* references now
have to resolve from the rest of the link; Linux and macOS coverage here is
CI's, not local.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@lukewilliamboswell
lukewilliamboswell merged commit 8d0b99d into main Aug 24, 2026
21 checks passed
@lukewilliamboswell
lukewilliamboswell deleted the fix/windows-abi-targets branch August 24, 2026 04:47
ESRogs pushed a commit to ESRogs/roc that referenced this pull request Aug 24, 2026
roc-lang#10637 added x64mingw, arm64mingw, x64v1mingw and arm64v1mingw to RocTarget
and roc-lang#10904 added a switch over RocTarget for the default platform's
compiler-rt object. Neither branch saw the other, so both merged cleanly and
left the switch missing four cases, which stops roc compiling at all on
every platform.

The default platform only carries a compiler-rt object for musl and glibc,
so the MinGW targets join the other Windows targets in returning null.
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.

Windows support - split roc targets into x64msvc and x64mingw ABI

1 participant