Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rust: make pkgsCross.wasi32.rustPlatform.buildRustPackage work #323161

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

jcaesar
Copy link
Contributor

@jcaesar jcaesar commented Jun 28, 2024

Description of changes

This PR makes it possible to e.g. build and run rust packages like nix shell .#pkgsCross.wasi32.charasay -c charasay.wasm say moo (assuming you have wasm32-wasi in your emulated systems).

Without this PR, building the cross rustc fails with

thread 'main' panicked at src/core/build_steps/compile.rs:370:17:
Target "wasm32-unknown-wasi" does not have a "wasi-root" ```

I think this is neat, but it admittedly isn't super useful, as a lot of packages fail to compile due to failing C dependencies or some common dependencies assuming that all wasm is for the web. I've tested the 469 rust packages in by-name, and only 60¹ compile. Even those that do compile might fail at runtime, e.g. due to std::thread not being supported and panicking when attempting to create a thread (like alejandra).

I'm only aware of two mildly useful things that work, namely rq and qrtool. But I think it's nice to have this working.

¹ aarch64-esr-decoder, aba, action-validator, adrs, bunbun, cargo-autoinherit, cargo-bloat, cargo-bump, cargo-shear, cargo-swift, catppuccin-whiskers, catppuccinifier-cli, cdwe, circom, clippy-sarif, cmd-wrapped, commitmsgfmt, diesel-cli-ext, dipc, elf-info, emacs-lsp-booster, esbuild-config, french-numbers, galerio, git-upstream, hadolint-sarif, hvm, hyperlink, icnsify, ifrextractor-rs, keedump, kickstart, ldproxy, little_boxes, ludtwig, lutgen, majima, mcumgr-client, mdsh, ndstrim, obsidian-export, okolors, parallel-disk-usage, pdfrip, polylux2pdfpc, preserves-tools, prettypst, protoc-gen-rust-grpc, qrtool, rq, rs-tftpd, rusti-cal, rusty-diceware, sarif-fmt, shellcheck-sarif, tera-cli, thud, toml-cli, typstfmt, unison-fsmonitor, wit-bindgen

Things done

  • Built on platform(s)
    • x86_64-linux
    • aarch64-linux
    • x86_64-darwin
    • aarch64-darwin
  • For non-Linux: Is sandboxing enabled in nix.conf? (See Nix manual)
    • sandbox = relaxed
    • sandbox = true
  • Tested, as applicable:
  • Tested compilation of all packages that depend on this change using nix-shell -p nixpkgs-review --run "nixpkgs-review rev HEAD". Note: all changes have to be committed, also see nixpkgs-review usage
  • Tested basic functionality of all binary files (usually in ./result/bin/)
  • 24.11 Release Notes (or backporting 23.11 and 24.05 Release notes)
    • (Package updates) Added a release notes entry if the change is major or breaking
    • (Module updates) Added a release notes entry if the change is significant
    • (Module addition) Added a release notes entry if adding a new NixOS module
  • Fits CONTRIBUTING.md.

Add a 👍 reaction to pull requests you find important.

@github-actions github-actions bot added 6.topic: rust 6.topic: lib The Nixpkgs function library labels Jun 28, 2024
@jcaesar jcaesar changed the title rust: make pkgsCross.wasi32.buildRustPackage work rust: make pkgsCross.wasi32.rustPlatform.buildRustPackage work Jun 29, 2024
@flokli flokli requested a review from Kranzes June 30, 2024 11:09
@Kranzes Kranzes removed their request for review July 1, 2024 01:13
@@ -23,7 +23,9 @@ rec {

ccForHost = "${stdenv.cc}/bin/${stdenv.cc.targetPrefix}cc";
cxxForHost = "${stdenv.cc}/bin/${stdenv.cc.targetPrefix}c++";
linkerForHost = if shouldUseLLD stdenv.targetPlatform
linkerForHost = if stdenv.targetPlatform.isWasi
then "${pkgsBuildHost.llvmPackages.bintools-unwrapped}/bin/${stdenv.cc.targetPrefix}wasm-ld"
Copy link
Contributor Author

@jcaesar jcaesar Jul 4, 2024

Choose a reason for hiding this comment

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

The problem here is that

  1. wasm-ld isn't included by bintools-wrapper. And even if add that, rustc seems to pass -flavor wasm to the linker, even though its path ends in wasm-ld.)
  2. ld.lld from bintools-unwrapped would also work, but from what I understand, it works because rustc passes -flavor wasm, which is a legacy option. ld.lld from bintools chokes on that argument.
  3. Using "${stdenv.cc}/bin/${stdenv.cc.targetPrefix}clang" as a linker could work (must be "clang", not "cc" as used in ccForHost), but I have been unable to figure out how to explain to clang that the linker it should use is not named wasm-ld but wasm32-unknown-wasi-wasm-ld (it does have that in its search path already).

So this is the only solution I was able to come up with. Since the resulting binaries are statically linked and work, I don't suppose there's a big problem with not using the wrapped linkers.

Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't using a wrapped linker be necessary to find dependencies? What linker does Rust use by default?

Copy link
Contributor Author

@jcaesar jcaesar Jul 8, 2024

Choose a reason for hiding this comment

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

What linker does Rust use by default?

Either "${stdenv.cc}/bin/${stdenv.cc.targetPrefix}cc" if that's clang or ld.lld otherwise, both are wrapped, from what I can see.

Wouldn't using a wrapped linker be necessary to find dependencies?

It's possible that there isn't a single rust package that depends on nixpkgs-built dependencies makes it to linking. Most of them do depend on stuff that doesn't work on wasi, like openssl or gui-related libraries. So I can't answer that question. Why using a wrapped linker breaks certainly needs some investigation… Not sure I have time for that.

In any case: bintools (wrapped): doesn't work. bintools-unwrapped: works. I don't see any other option here.

Copy link
Member

@Mic92 Mic92 Jul 10, 2024

Choose a reason for hiding this comment

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

What are the observed symptoms of it not working? Not that if you set the NIX_DEBUG variable than it will also print what flags it injects into the LD executable.

Copy link
Member

Choose a reason for hiding this comment

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

I would at least leave a comment why we are doing this for now, potentially combined with the error message.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Mic92 Thank you for the NIX_DEBUG hint. Getting rust to print the linker command line in the unwrapped case was a bit more tricky, but with both, I was able to confirm what's going on.

The wrapper script was passing hardening flags (-z relro -z now) to wasm-ld, which

  • cause lld to not recognize the -flavor wasm arguments
  • don't make sense on wasm (warning: unknown -z value: now) anyway

I'm not sure what's best here, I don't think hardening makes a lot of sense when compiling to wasm anyway, but I don't know of a way to disable hardening for an entire target. What I opted for is to instead add hardening_unsupported_flags+=" relro bindnow" in the wrapper script on wasi.


For reference:

The output the linker wrapper is:

error: linking with `/nix/store/6plp6qym01fbp05lwa1rh4ym50vnw1ng-wasm32-unknown-wasi-llvm-binutils-wrapper-17.0.6/bin/wasm32-unknown-wasi-wasm-ld` failed: exit status: 1
  |
  = note: LC_ALL="C" PATH="/nix/store/m9dh8g3r6pvbzsxz6b94136l38wmkvgz-wasm32-unknown-wasi-rustc-1.78.0/lib/rustlib/x86_64-unknown-linux-gnu/bin:/nix/store/m9dh8g3r6pvbzsxz6b94136l38wmkvgz-wasm32-unknown-wasi-rustc-1.78.0/lib/rustlib/x86_64-unknown-linux-gnu/bin/self-contained:/nix/store/708hwm
  = note: HARDENING: disabled flags: pie
          HARDENING: Is active (not completely disabled with "all" flag)
          HARDENING: enabling relro
          HARDENING: enabling bindnow
          extra flags before to /nix/store/5gghgw5dkhw3dx862wynrd26sm3k8xbp-llvm-binutils-17.0.6/bin/wasm32-unknown-wasi-wasm-ld:
            -z
            relro
            -z
            now
          original flags to /nix/store/5gghgw5dkhw3dx862wynrd26sm3k8xbp-llvm-binutils-17.0.6/bin/wasm32-unknown-wasi-wasm-ld:
            -flavor
            wasm
            --rsp-quoting=posix
            --export
            __main_void
            -z
            stack-size=1048576
            --stack-first
            --allow-undefined
            --no-demangle
            /nix/store/m9dh8g3r6pvbzsxz6b94136l38wmkvgz-wasm32-unknown-wasi-rustc-1.78.0/lib/rustlib/wasm32-wasip1/lib/self-contained/crt1-command.o
            /build/source/target/wasm32-wasip1/release/deps/rq-9dc42ad07b27356d.rq.2f192189ed26cc41-cgu.0.rcgu.o
            -L
            /build/source/target/wasm32-wasip1/release/deps
            -L
            /build/source/target/release/deps
            -L
            /nix/store/m9dh8g3r6pvbzsxz6b94136l38wmkvgz-wasm32-unknown-wasi-rustc-1.78.0/lib/rustlib/wasm32-wasip1/lib
            -l
            c
            /nix/store/m9dh8g3r6pvbzsxz6b94136l38wmkvgz-wasm32-unknown-wasi-rustc-1.78.0/lib/rustlib/wasm32-wasip1/lib/libcompiler_builtins-dae8cdcfb0b90f99.rlib
            -L
            /nix/store/m9dh8g3r6pvbzsxz6b94136l38wmkvgz-wasm32-unknown-wasi-rustc-1.78.0/lib/rustlib/wasm32-wasip1/lib
            -L
            /nix/store/m9dh8g3r6pvbzsxz6b94136l38wmkvgz-wasm32-unknown-wasi-rustc-1.78.0/lib/rustlib/wasm32-wasip1/lib/self-contained
            -o
            /build/source/target/wasm32-wasip1/release/deps/rq-9dc42ad07b27356d.wasm
            --gc-sections
            -O3
          extra flags after to /nix/store/5gghgw5dkhw3dx862wynrd26sm3k8xbp-llvm-binutils-17.0.6/bin/wasm32-unknown-wasi-wasm-ld:
            -L/nix/store/56c3y4h4ba85fxz0876ld9qd8l3ww2hy-libcxx-static-wasm32-unknown-wasi-17.0.6/lib
            -L/nix/store/02cizbm0w4akb46mapwb0in8jcr10srp-wasilibc-static-wasm32-unknown-wasi-21/lib
          wasm32-unknown-wasi-wasm-ld: error: unknown argument: -flavor
          wasm32-unknown-wasi-wasm-ld: warning: unknown -z value: relro
          wasm32-unknown-wasi-wasm-ld: warning: unknown -z value: now
          wasm32-unknown-wasi-wasm-ld: error: cannot open wasm: No such file or directory

This is what I was trying with:

diff --git a/pkgs/build-support/bintools-wrapper/default.nix b/pkgs/build-support/bintools-wrapper/default.nix
index e7fcf173c602..1c955fdd8d0d 100644
--- a/pkgs/build-support/bintools-wrapper/default.nix
+++ b/pkgs/build-support/bintools-wrapper/default.nix
@@ -239,6 +239,8 @@ stdenvNoCC.mkDerivation {
         basename=$(basename "$variant")
         wrap $basename ${./ld-wrapper.sh} $variant
       done
+    '' + optionalString targetPlatform.isWasi ''
+      wrap ${targetPrefix}wasm-ld ${./ld-wrapper.sh} $ldPath/${targetPrefix}wasm-ld
     '';
 
   strictDeps = true;
diff --git a/pkgs/build-support/rust/lib/default.nix b/pkgs/build-support/rust/lib/default.nix
index b52eecccbb2f..7f66bf01c1e5 100644
--- a/pkgs/build-support/rust/lib/default.nix
+++ b/pkgs/build-support/rust/lib/default.nix
@@ -24,7 +24,7 @@ rec {
     ccForHost = "${stdenv.cc}/bin/${stdenv.cc.targetPrefix}cc";
     cxxForHost = "${stdenv.cc}/bin/${stdenv.cc.targetPrefix}c++";
     linkerForHost = if stdenv.targetPlatform.isWasi
-      then "${pkgsBuildHost.llvmPackages.bintools-unwrapped}/bin/${stdenv.cc.targetPrefix}wasm-ld"
+      then "${pkgsBuildHost.llvmPackages.bintools}/bin/${stdenv.cc.targetPrefix}wasm-ld"
       else if shouldUseLLD stdenv.targetPlatform
       && !stdenv.cc.bintools.isLLVM
       then "${pkgsBuildHost.llvmPackages.bintools}/bin/${stdenv.cc.targetPrefix}ld.lld"
diff --git a/pkgs/development/tools/rq/default.nix b/pkgs/development/tools/rq/default.nix
index f8ed521ad2cc..589d347dbac5 100644
--- a/pkgs/development/tools/rq/default.nix
+++ b/pkgs/development/tools/rq/default.nix
@@ -24,6 +24,9 @@ rustPlatform.buildRustPackage rec {
     rm build.rs
   '';
 
+  env.NIX_DEBUG = "1";
+  env.RUSTC_LOG = "rustc_codegen_ssa::back::link=info";
+
   VERGEN_SEMVER = version;
 
   meta = with lib; {

Copy link
Member

Choose a reason for hiding this comment

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

By "by default", I mean what linker does a rustc you'd get from rustup use in this case?

Copy link
Contributor Author

@jcaesar jcaesar Jul 12, 2024

Choose a reason for hiding this comment

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

Ah, I see. @alyssais For the docker.io/library/rust docker image (which is currently on 1.79 and made using rustup) it defaults to rust-lld -flavor wasm.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(I'm sorry, I force-pushed over this commit again, I mistakenly thought not changing the linker was going to work. But it does have to be set to lld.)

@jcaesar
Copy link
Contributor Author

jcaesar commented Jul 4, 2024

I've re-verified that this is the best solution I can come up with. I've added comments on the parts that aren't entirely starightforward. @Kranzes, if you only removed your reviewer status because this was still a draft, can I ask you to have a look at some point?

@jcaesar jcaesar marked this pull request as ready for review July 4, 2024 07:24
lib/systems/examples.nix Outdated Show resolved Hide resolved
@@ -23,7 +23,9 @@ rec {

ccForHost = "${stdenv.cc}/bin/${stdenv.cc.targetPrefix}cc";
cxxForHost = "${stdenv.cc}/bin/${stdenv.cc.targetPrefix}c++";
linkerForHost = if shouldUseLLD stdenv.targetPlatform
linkerForHost = if stdenv.targetPlatform.isWasi
then "${pkgsBuildHost.llvmPackages.bintools-unwrapped}/bin/${stdenv.cc.targetPrefix}wasm-ld"
Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't using a wrapped linker be necessary to find dependencies? What linker does Rust use by default?

pkgs/development/compilers/rust/rustc.nix Outdated Show resolved Hide resolved
@jcaesar
Copy link
Contributor Author

jcaesar commented Jul 8, 2024

Thank you for the review.

(The reason I wasn't particularly keen on touching wasilibc is that the following takes ages to build. The floorp failure also happens on master, so that's unrelated.)


Result of nixpkgs-review pr 323161 run on x86_64-linux 1

1 package blacklisted:
  • nixos-install-tools
3 packages failed to build:
  • floorp
  • floorp-unwrapped
  • floorp-unwrapped.debug
32 packages built:
  • adoptopenjdk-icedtea-web
  • betterbird
  • betterbird-unwrapped
  • betterbird-unwrapped.debug
  • eyewitness
  • firefox
  • firefox-beta
  • firefox-beta-unwrapped
  • firefox-beta-unwrapped.debug
  • firefox-beta-unwrapped.symbols
  • firefox-devedition
  • firefox-devedition-unwrapped
  • firefox-devedition-unwrapped.debug
  • firefox-devedition-unwrapped.symbols
  • firefox-esr
  • firefox-esr-115-unwrapped
  • firefox-esr-115-unwrapped.debug
  • firefox-esr-115-unwrapped.symbols
  • firefox-mobile
  • firefox-unwrapped
  • firefox-unwrapped.debug
  • firefox-unwrapped.symbols
  • firefoxpwa
  • librewolf
  • librewolf-unwrapped
  • librewolf-unwrapped.debug
  • sitespeed-io
  • slimerjs
  • thunderbird
  • thunderbird-unwrapped
  • thunderbird-unwrapped.debug
  • thunderbird-unwrapped.symbols

@alyssais
Copy link
Member

I tried building pkgsCross.wasi32.qrtool, but it failed to build wasm32-unknown-wasi-rustc-1.78.0.

thread 'main' panicked at src/core/build_steps/compile.rs:400:17:
Target "wasm32-unknown-wasi" does not have a "wasi-root" key in Config.toml
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Build completed unsuccessfully in 0:00:13

@jcaesar
Copy link
Contributor Author

jcaesar commented Jul 14, 2024

@alyssais Could you tell me exactly what you're trying to build? The following work for me (should be the same thing):

nix build  -vL github:NixOS/nixpkgs/56df69358a7c06f415c33c3cb9543e41b872c220#pkgsCross.wasi32.rq
nix build  -vL github:NixOS/nixpkgs/pull/323161/head#pkgsCross.wasi32.rq

(qrtool will fail in installPhase because of #289517 / #308283. Try rq instead if the above does get past the failure you saw in rustc.)

[Edit:] Did you try that on master / some other PR? Because that's nearly the same error as in the opening post for this PR.

@alyssais
Copy link
Member

Looks like I might have mixed up which branch I was building — sorry!

@@ -23,8 +23,9 @@ rec {

ccForHost = "${stdenv.cc}/bin/${stdenv.cc.targetPrefix}cc";
cxxForHost = "${stdenv.cc}/bin/${stdenv.cc.targetPrefix}c++";
linkerForHost = if shouldUseLLD stdenv.targetPlatform
&& !stdenv.cc.bintools.isLLVM
# For wasi: rustc passes lld-flavored args unless the compiler is named "clang". So use lld.
Copy link
Member

Choose a reason for hiding this comment

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

Do you know which code in rustc does this check?

Copy link
Contributor Author

@jcaesar jcaesar Jul 15, 2024

Choose a reason for hiding this comment

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

After some digging. I think I found the relevant part. So clang or gcc as names would be recognized, but not cc (as set in ccForHost). I've revised the comment accordingly.
(Defaulting to lld without being wrapped in a compiler is here.)

Side notes:

  • Since cc isn't recognized as anything by rust, the current setup for ccForHost is maybe not the best in general…?
  • This doccoment has some helpful insights.

Copy link
Member

Choose a reason for hiding this comment

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

  • Since cc isn't recognized as anything by rust, the current setup for ccForHost is maybe not the best in general…?

Yeah, that's what I was wondering.

Copy link
Member

Choose a reason for hiding this comment

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

It might also make sense to teach Rust to understand "cc", as it's not exactly a Nixpkgs-specific convention.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I read somewhere that not recognizing cc is intentional. Somebody felt that two letters is too little to go on? I might be mistaken, my memory on this is rather hazy. (In any case, this is a finicky part of rustc. Just looking at the amount of "fix" here. I'd rather not touch it.)

In any case, that's not something I intend to fix here. ^^

Copy link
Member

Choose a reason for hiding this comment

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

Given that there are other options, I really don't think we should expand the hack of using a custom linker for rustc. Either fixing this in rustc, or fixing ccForHost would be real fixes, not a hack like this.

Copy link
Contributor Author

@jcaesar jcaesar Jul 15, 2024

Choose a reason for hiding this comment

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

There are other options?

In case you're thinking of just doing

    linkerForHost = if stdenv.cc.bintools.isLLVM
      then "${stdenv.cc}/bin/${stdenv.cc.targetPrefix}clang"
      else "${stdenv.cc}/bin/${stdenv.cc.targetPrefix}gcc";
    # resp. doing this for all of cc/cxx/linkerForHost

that won't work without further adjustments either:

  = note: clang: error: unable to execute command: Executable "wasm-ld" doesn't exist!
          clang: error: linker command failed with exit code 1 (use -v to see invocation)

And I really don't now what those adjustments are, or whether they're benign. I've previously looked into whether clang can somehow be taught a different linker command, but couldn't find out anything.

(This would also severely increase the blast radius of this PR…)

Copy link
Contributor Author

@jcaesar jcaesar Jul 18, 2024

Choose a reason for hiding this comment

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

I dug around a bit (for options), here's a few things I found:

  1. It's unlikely that rustc upstream will accept patches that recognize a -cc suffix as a compiler wrapping a linker, reason being that -cc might be anything, so it's unclear what arguments can be passed to it
  2. cargo-auditable passes linker flags with -Wl, unconditionally (src) meaning we can't just use a plain linker as linkerForHost, it has to be invoked through a compiler as long as we want to use cargo-auditable.
  3. 1 and 2 combined mean that some if foo then bar else blubb is necessary for linkerFor….
  4. Using clang as a linker may work, but it's mechanism for finding wasm-ld is not nice: It searches paths passed with -B for <triple>-wasm-ld and wasm-ld (src). However, it doesn't properly normalize the triple, and just uses the -target wasm32-wasi argument it gets from rustc as is. Clang itself doesn't think that's a good state and does have a TODO about this.
    (This might also be fixable in rustc, which does seem to have logic to not directly pass its own target name. But I havent looked into how that works.)
  5. The way the linker gets passed from bintools to clang is:
    • LLVM bintools gets compiled
    • bintools-wrapper wraps it (currently not including wasm-ld) (src)
    • cc-wrapper symlinks that into ints own bin directory (src)
    • cc-wrapper's add-flags.sh passes the -B flag to its own bin dir (src)
  6. 4+5 could in theory be worked around like this, but that's a far more terrible hack than what I've tried to do here.

So, the only way I see to get this to work cleanly is to submit a fix to clang for triple normalization, and then submit a PR to nixpkgs staging that switches to if stdenv.cc.bintools.isLLVM then clang else gcc (oh the rebuilds, I don't have hardware to test this), and then finally get back to this. I think that's above the level of difficulty I can manage.

If you've got any advice on how else I could approach this, please do tell.

Rust assumes the linker is lld-flavored (unless it's named clang),
and passes arguments accordingly. Two things are required to make that
work:

* Set the linker to ld.lld
* disable linker hardening flags (they'd be passed befor the -flavor
  arg, breakin that, on top of having no effect)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants