Repository navigation
Conversation
`packages.x86_64-windows` has never worked:
$ nix eval .#packages.x86_64-windows.default
error: attribute 'x86_64-windows' missing
`mkCircuits` starts from `nixpkgs.legacyPackages.${system}`, and nixpkgs has no
Windows package set -- nix does not run there. So although this repo builds and
publishes Windows archives (and `circuits-nix-hashes.json` has carried an
x86_64-windows entry since 0.3.2), no flake consumer could reach them.
The derivation is `fetchurl` plus an unpack. It needs no toolchain for the
target at all, so the fix is to stop conflating the system nix builds *on* with
the system whose archives we fetch:
* `mkCircuitsFor { buildSystem, target }` -- `buildSystem` instantiates nixpkgs,
`target` selects the asset and its hash.
* `targetOs` / `targetArch` derive the asset name from the target string rather
than from `stdenv`, so a target that cannot be instantiated can still be named.
* `mkCircuits system` stays as the native shorthand, so nothing else moves.
* Windows is published under every native platform as
`circuits-windows-x86_64`, the shape zerokit and logos-delivery already use
and the one logos-module-builder's `externalLibInputs.<n>.systems.<target>`
override expects.
* `x86_64-windows` leaves `systems`, which is the list nix instantiates. It is a
cross target now, in `crossTargets`.
`meta.platforms` is the build platform, not the target: nix checks it against
the system it builds on, and setting it to the target makes the package refuse
to build anywhere.
Verified on x86_64-linux:
packages.x86_64-linux -> [ "circuits" "circuits-windows-x86_64" "default" ]
.#packages.x86_64-linux.circuits-windows-x86_64
pol.lib / poq.lib / poc.lib / signature.lib / libgmp.a -> all pe-x86-64
.#packages.x86_64-linux.default
/nix/store/wzi7bxa2...-logos-blockchain-circuits-0.5.7
That last path is byte-identical to what upstream produces today, so the native
packages are untouched. aarch64-darwin evaluates too.
Consumers should know the published Windows archives are not self-contained:
they reference `mmap`, `munmap` and out-of-line libstdc++ symbols such as
`std::__cxx11::to_string(int)`, which the MSYS2 MINGW64 toolchain in
`build-windows` supplies via `.github/resources/prover/windows.*`. Linking them
with a different MinGW (a nixpkgs cross toolchain, say) leaves those undefined.
That is worth a follow-up; this change only makes the artifacts reachable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The archives `build-windows` publishes cannot be linked by any toolchain other
than the MSYS2 one that produced them. Measured against a nixpkgs MinGW cross
toolchain, a direct `g++` link of `pol.lib` (no Rust, no nix) leaves 981
undefined references.
Two causes, both in the library rule, neither version-related:
1. `ld -r` on PE merges the COMDAT sections GCC emits for templates, and the
merged object then fails to link with
`relocation truncated to fit: IMAGE_REL_AMD64_REL32`.
2. `objcopy --keep-global-symbol` localizes everything it is not told to keep,
which on PE includes those same COMDAT symbols -- `std::__cxx11::to_string`,
`std::operator+(basic_string&&, basic_string&&)`, ~20 nlohmann/json
instantiations. Localized, the consumer's own copies no longer satisfy them.
Localizing per object instead is not a fix either: it breaks the references
between the objects (ffi.o then cannot see `circom_main` in main.o).
## windows-cross-lib
So this adds a second isolation mechanism rather than changing the one the
native targets use. `isolate-symbols.sh` renames each internal global to
`__<project>_priv_<sym>`, applying the same map to every object: intra-archive
references still resolve because the rename is consistent, nothing collides
across circuits because the prefix is per-circuit, and COMDAT symbols are left
alone precisely because they are meant to be shared with the consumer. No
`ld -r`, so no truncation.
`OS=windows-cross` also keeps the `lib<name>.a` naming GNU `ld` resolves from
`-l<name>`; the MSYS2 bundle's `pol.lib` does not.
## Verified end to end
Cross-built `pol` on Linux with a MinGW toolchain, from this Makefile target and
this script:
renamed 339 internal symbols, kept 73 (2 public + 71 COMDAT)
public entry points : 2
leaked Fr_/Circom : 0
link: undefined 0, truncated 0 -> pei-x86-64
and the resulting binary runs on real Windows (10.0.26200). The same link
against the published `pol.lib` is the control: 981 undefined references.
## The rest
* `build-windows-cross` on ubuntu-latest publishes
`...-windows-x86_64-gnu.tar.gz`, with a gate gating asserting each archive is
PE, exports exactly its 2 entry points, and leaks no `Fr_`/`Circom` globals.
* It bundles `libmman.a` as part of the contract: the circuit objects call
`mmap`/`munmap`, and unlike the MSYS2 bundle nothing else in the tarball
supplies them.
* Debian's MinGW defaults to the win32 threads model, which has no
`std::thread` or `std::mutex`; the job selects the `-posix` alternatives.
* The flake gains `x86_64-windows-gnu`. With a hash present the attribute
appears and resolves to
`logos-blockchain-circuits-v<ver>-windows-x86_64-gnu.tar.gz`; without one it
is simply absent, so this commit changes no existing output. `packages
.x86_64-linux.default` is still `/nix/store/wzi7bxa2...-0.5.7`, byte-identical
to upstream.
`actionlint` reports the same 11 pre-existing shellcheck infos before and after.
The job has not run: it needs a release build to exercise it, and the
`prover.exe` / `verifier.exe` the MSYS2 bundle ships are deliberately omitted
here, since this variant exists for linking rather than for running the tools.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Measured downstream, and it changes what this variant needs to guarantee. Linking the cross variant's archives into I cross-built those archives with MinGW GCC 15.3; that consumer's nixpkgs So the cross variant is coupled to the MinGW version that produced it, and Two ways to make that safe, and I'd rather you pick than guess:
Happy to implement either here. Worth knowing the MSYS2 variant has this |
The cross variant was built with whatever mingw-w64 the runner image shipped,
and archives built with one mingw-w64 do not link with another. Measured:
cross-built with GCC 15.3 and linked by a consumer on GCC 14.3, `wallet-ffi`
stops on a single symbol --
liblogos_blockchain_circuits_pol_sys...(main.o): undefined reference to `fstat64i32'
-- because the circuits' generated main.cpp calls fstat, and the two versions'
headers route it to different CRT names: GCC 14.3 to `_fstat64i32`, GCC 15.3 to
`fstat64i32`. Each CRT provides only its own. Nothing is wrong with the code;
the producer and consumer simply have to agree on a toolchain.
Consumers in this stack are nix builds that follow logos-nix, so that is what
the variant is now built with:
* A `logos-nix` input, used only for this. `nixpkgs` is untouched, so the native
packages are byte-identical to before (default is still
/nix/store/wzi7bxa2...-logos-blockchain-circuits-0.5.7). The lock pins it to
2ab5416a, the rev logos-execution-zone locks today; relock them together.
* `devShells.x86_64-linux.windows-cross` puts that pinned MinGW on PATH with a
static GMP from the same package set.
* `build-windows-cross.sh` and `verify-windows-cross.sh` hold the build and the
gate, so CI and a developer run the same command:
nix develop .#windows-cross -c sh .github/resources/witness-generator/build-windows-cross.sh <out> <circuit>...
libmman is now compiled there too, with the archives' own toolchain, instead
of being shipped as a prebuilt shim that can drift from them -- which it did
(the same fstat64i32, one layer earlier).
* The job sets up Nix with logos-co/setup-nix-cache-action@v1, the org's action,
so the toolchain comes from the Logos cache where present.
* The bundle carries a TOOLCHAIN file,
`x86_64-w64-mingw32-gcc 14.3.0; logos-nix <rev>`, so a consumer can see what
the archives need before it meets an undefined symbol at link.
Verified end to end: the committed scripts, run exactly as the job runs them,
build and pass the gate for all four circuits; and with the circuit and
rapidsnark archives built on this pinned toolchain, logos-execution-zone's
`wallet-windows-x86_64` builds through nix to wallet_ffi.dll (pei-x86-64, 58
exported wallet_ffi_* symbols).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Went with option 1 — pinned in b3e9516. The variant now builds with logos-nix's MinGW (GCC 14.3, locked to the same logos-nix rev as logos-execution-zone), and |
Three commits. The first makes the existing Windows archives reachable; the
second publishes a variant that is actually linkable by a cross toolchain; the
third pins the toolchain that variant is built with.
1.
packages.x86_64-windowshas never workedmkCircuitsstarts fromnixpkgs.legacyPackages.${system}and nixpkgs has noWindows package set. So although this repo publishes Windows archives, and
circuits-nix-hashes.jsonhas carried anx86_64-windowsentry since 0.3.2, noflake consumer could reach them.
mkCircuitsFor { buildSystem, target }splits the system nix builds on fromthe one whose archives we fetch;
targetOs/targetArchderive the asset namefrom the target string rather than
stdenv. Windows is published under eachnative platform as
circuits-windows-x86_64. (meta.platformsis checkedagainst the build system, so it stays
buildSystem— setting it to the targetmakes the package refuse to build anywhere.)
2. …but those archives cannot be linked by anyone else
A direct
g++link of the publishedpol.libwith a nixpkgs MinGW toolchain —no Rust, no nix, just the archive — leaves 981 undefined references. Two
causes, both in the library rule, neither a GCC-version ABI difference:
ld -ron PE merges the COMDAT sections GCC emits for templates, and themerged object then fails with
relocation truncated to fit: IMAGE_REL_AMD64_REL32.objcopy --keep-global-symbollocalizes everything else, which on PEincludes those COMDAT symbols —
std::__cxx11::to_string,std::operator+(basic_string&&, basic_string&&), ~20 nlohmann/jsoninstantiations. Once localized, the consumer's own copies cannot satisfy
them.
Localizing per object instead is not a fix: it breaks references between the
objects (
ffi.othen cannot seecircom_maininmain.o). That is presumablywhy the merge is there in the first place.
windows-cross-libRather than change the mechanism the native targets use, this adds a second one.
isolate-symbols.shrenames every internal global to__<project>_priv_<sym>,applying one map to every object:
No
ld -r, so no truncation.OS=windows-crossalso keeps thelib<name>.anaming GNU
ldresolves from-l<name>— the MSYS2 bundle'spol.libdoes not.Measured
Cross-built
polon Linux from this Makefile target and this script:Fr_/Circomglobalspei-x86-64The resulting binary runs on real Windows (10.0.26200). Control: the same
link against the published
pol.libgives 981 undefined references.What the job publishes
build-windows-crosson ubuntu-latest produces...-windows-x86_64-gnu.tar.gz, with a gate asserting each archive is PE,exports exactly its two entry points, and leaks no internal globals. It bundles
libmman.aas part of the contract — the circuit objects callmmap/munmapand, unlike the MSYS2 bundle, nothing else here supplies them. Debian's MinGW
defaults to the win32 threads model (no
std::thread/std::mutex), so the jobselects the
-posixalternatives.The flake gains
x86_64-windows-gnu. With a hash present the attribute appearsand resolves to
logos-blockchain-circuits-v<ver>-windows-x86_64-gnu.tar.gz;without one it is absent, so nothing changes until a release publishes it.
3. The variant's toolchain is pinned
Archives built with one mingw-w64 do not link with another. Measured downstream:
cross-built with GCC 15.3 and linked by a consumer on GCC 14.3,
wallet-ffistops on one symbol,
undefined reference to fstat64i32— the generatedmain.cppcallsfstat, which GCC 14.3's headers route to_fstat64i32and15.3's to
fstat64i32, and each CRT provides only its own.Consumers in this stack are nix builds following logos-nix, so the variant is
now built with that:
logos-nixinput, used only here —nixpkgsis untouched, so nativeoutputs are byte-identical (
defaultis stillwzi7bxa2…-0.5.7). The lockpins
2ab5416a, the rev logos-execution-zone locks today; relock together.devShells.x86_64-linux.windows-crossputs that MinGW on PATH with astatic GMP from the same package set.
build-windows-cross.sh/verify-windows-cross.shhold the build andthe gate, so CI and a developer run the same
nix develop .#windows-crosscommand.
libmmanis compiled there too, with the archives' own toolchain,rather than shipped prebuilt — the prebuilt one drifted the same way.
logos-co/setup-nix-cache-action@v1.TOOLCHAIN(x86_64-w64-mingw32-gcc 14.3.0; logos-nix <rev>), so a consumer can check before it meets an undefined symbol.Verified end to end: the committed scripts, run exactly as the job runs
them, build and pass the gate for all four circuits; and with circuit and
rapidsnark archives from this pinned toolchain,
execution-zone#900's
wallet-windows-x86_64builds through nix towallet_ffi.dll(pei-x86-64,58 exported
wallet_ffi_*symbols).actionlintis unchanged at 11 pre-existing infos.Publishing to the Logos cache needs this repo listed in infra-ci's
attic.yml;until then the action pulls from the cache and skips publishing rather than failing.
Not verified
The new job has not run — it needs a release build to exercise it, and a PR
cannot trigger one. What is verified is everything it does, reproduced by hand
on a Linux builder using the exact Makefile target and script committed here.
prover.exe/verifier.exeare deliberately omitted from this variant: itexists for linking, not for running the tools. Say the word if you want them and
I will add the cross build for those too.
packages.x86_64-linux.defaultis/nix/store/wzi7bxa2...-0.5.7,byte-identical to upstream, and
actionlintreports the same 11 pre-existingshellcheck infos before and after.
Context
Part of getting
logos-delivery-moduleonto Windows:delivery_module → liblogos_rln_module → liblogos_lez_rln_module → lez_core → wallet_ffi.wallet-ffialready compiles forx86_64-pc-windows-gnu(execution-zone#900,
rapidsnark#1);
these archives are the last thing it needs to link.
🤖 Generated with Claude Code