Closure size of binaries produced by Nim

I started playing around with Nim and the first impression is quite nice. Getting started on NixOS was very smooth.
For my first project I’m aiming with building via a Nix-Flake, but execution should be on a ephemeral Nix-Less Ubuntu-Image, where I load the binary at runtime, so I’m looking for a small closure size.
While I understand that I can only get rid of GLIBC with probably a Zig/musl backend and a matching build of OpenSSL etc., I’m wondering why nim-unwrapped is part of the closure.

See e.g. this minimal (flake) example:

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs?rev=dfef2e61107dc19c211ead99a5a61374ad8317f4";
    utils.url = "github:numtide/flake-utils";
  };

  outputs = {
    self,
    nixpkgs,
    utils,
  }:
    utils.lib.eachDefaultSystem (system: let
      pkgs = import nixpkgs {inherit system;};
      nimPackages = pkgs.nimPackages;
    in rec {
      packages.demo = nimPackages.buildNimPackage rec {
        pname = "demo";
        version = "0.1.0";

        src = ./.;
        unpackPhase = ''
          echo 'echo "Hello World"' > demo.nim
          echo 'bin           = @["demo"]' > demo.nimble
          echo 'equires "nim >= 1.6.10"' >> demo.nimble
        '';

        nimBinOnly = true;
        nimRelease = true;
        nimDefines = [];
        buildInputs = with nimPackages; [];
        propagatedBuildInputs = [];
      };
      apps.demo = {
        type = "app";
        program = "${packages.demo}/bin/demo";
      };
      devShell = with pkgs;
        mkShell {
          buildInputs = [nim nimble-unwrapped];
        };
    });
}
> nix path-info -rsSh .#demo | sort -nk3
/nix/store/34xlpp3j3vy7ksn09zh44f1c04w77khf-libunistring-1.0   	   1.7M	   1.7M
/nix/store/5mh5019jigj0k14rdnjam1xwk5avn1id-libidn2-2.3.2      	 254.1K	   2.0M
/nix/store/4nlgxhb09sdr51nc9hdm8az5b08vzkgx-glibc-2.35-163     	  28.9M	  30.8M
/nix/store/1dgws25664p544znpc6f1nh9xmjf4ykc-pcre-8.45          	 506.5K	  31.3M
/nix/store/fzb7khbic8vpcr3m69v6y8qp6jqspdgw-openssl-1.1.1s     	   4.0M	  34.8M
/nix/store/n0nzzxszqqvi64br0sra58jg74zldcn1-nim-unwrapped-1.6.8	  31.5M	  66.9M
/nix/store/c3gvf94ym6fdfbip6w29f3f348gvrbxb-demo-0.1.0         	  82.3K	  67.0M

That is quite a hefty package.

Doing a bit of grepping I found two references to /nix/store/n0nzzxszqqvi64br0sra58jg74zldcn1-nim-unwrapped-1.6.8/nim/lib/system.nim in the resulting binary, which is probably the reason for Nix to include nim-unwrapped. If I’m not mistaken, the runtime should be compiled in and no reference should be left.

So, help me understand and in the end maybe fix the issue!

I have no experience with Nim, but it could be that this is either debug information or part of a log message, and not strictly required at runtime. In that case, you can use

postFixup = ''
  remove-references-to -t ${nim-unwrapped} $out/bin/demo
''

to remove the reference (need to add removeReferencesTo to nativeBuildInputs).

2 Likes

Yes, that still can be a per-project solution (thanks for pointing it out!), but nothing I would add to buildNimPackage without consulting @ehmry first.

Is it actually working in practice? (I guess you need to garbage collect and check that the path has been removed to be 100% sure it works) If it’s working, then it might be worth checking why it points to this path exactly. You can maybe try to compile to another language like C to see where is used. Also, is there any content around your strings? You can use cat binary | strings to display each string in a different line.

Yes, the binary runs fine when I add the remove-references step, copy the resulting binary from the store and gc. The closure size also drops as expected. But that also demonstrates that the “Hello World” does not exercise the relevant code path (assuming it is not only meta info).

Thank you for noticing this, I created an issue - #207795.

I wont have time to fix this right away but I’ll get to it before the next release.

1 Like

@ehmry Thank you for fixing this!

can be confirmed by calling the flake above with:

nix build --override-input nixpkgs "github:NixOS/nixpkgs?rev=eafc69b131391423cf2c37587af9444435db3dbb" .#demo
nix path-info --override-input nixpkgs "github:NixOS/nixpkgs?rev=eafc69b131391423cf2c37587af9444435db3dbb" -rsSh .#demo | sort -nk3

which results in

/nix/store/q7hi3rvpfgc232qkdq2dacmvkmsrnldg-libunistring-1.1  	   1.8M	   1.8M
/nix/store/fz2c8qahxza5ygy4yvwdqzbck1bs3qag-libidn2-2.3.4     	 350.4K	   2.1M
/nix/store/3n58xw4373jp0ljirf06d8077j15pc4j-glibc-2.37-8      	  28.8M	  31.1M
/nix/store/ih2fi411sh3m6dsvwiq2xvzlqaivwfbk-demo-0.1.0        	  90.3K	  31.2M
/nix/store/ryvnrp5n6kqv3fl20qy2xgcgdsza7i0m-xgcc-12.3.0-libgcc	 139.1K	 139.1K