├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.nim ├── hello.nim └── zigcc /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | paths: 6 | - "**" 7 | - "!**.md" 8 | pull_request: 9 | paths: 10 | - "**" 11 | - "!**.md" 12 | workflow_dispatch: 13 | 14 | jobs: 15 | ci: 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | os: ['ubuntu-22.04', 'macos-12', 'windows-2022'] 20 | nim: ['version-2-0'] 21 | 22 | name: ${{ matrix.os }} / Nim ${{ matrix.nim }} 23 | runs-on: ${{ matrix.os }} 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v2 28 | 29 | - name: Install Nim 30 | uses: alaviss/setup-nim@f81f2a6d1505ab32f440ec9d8adbb81e949d3bf0 # 0.1.1 31 | with: 32 | path: 'nim' 33 | version: ${{ matrix.nim }} 34 | 35 | - name: Run `nim --version` 36 | run: nim --version 37 | 38 | - name: 'Linux: install musl and zig' 39 | if: runner.os == 'Linux' 40 | run: | 41 | sudo apt-get install musl-dev musl-tools 42 | sudo snap install zig --classic --beta 43 | 44 | - name: Compile `build.nim` 45 | run: nim c --styleCheck:error ./build.nim 46 | 47 | - name: Run `build` 48 | run: ./build 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore every file that lacks an extension 2 | * 3 | !/**/ 4 | !*.* 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ee7 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nim binary size 2 | 3 | "Hello, World!" binary size in Nim. 4 | 5 | ## Instructions 6 | 7 | Run 8 | 9 | ```sh 10 | nim c -r build.nim 11 | ``` 12 | 13 | which compiles the Nim program 14 | 15 | ```Nim 16 | echo "Hello, World!" 17 | ``` 18 | 19 | with various sets of compilation options. 20 | 21 | ## Results 22 | 23 | | -d:release[^1] | LTO[^2] | strip[^3] | --opt:size[^4] | statically link | Linux | macOS | Windows | 24 | | :------------: | :-----: | :-------: | :----------------: | :------------------- | --------: | --------: | --------: | 25 | | | | | | | 98.2 KiB | 109.4 KiB | 188.7 KiB | 26 | | ✔️ | | | | | 67.3 KiB | 71.7 KiB | 151.5 KiB | 27 | | ✔️ | ✔️ | | | | 39.5 KiB | 67.6 KiB | 132.0 KiB | 28 | | ✔️ | ✔️ | ✔️ | | | 34.5 KiB | 65.1 KiB | 79.5 KiB | 29 | | ✔️ | ✔️ | ✔️ | ✔️ | | 26.5 KiB | 49.1 KiB | 64.0 KiB | 30 | | ✔️ | ✔️ | ✔️ | ✔️ | via `musl-gcc`[^5] | 30.0 KiB | | | 31 | | ✔️ | ✔️ | ✔️ | ✔️ | via `musl-clang`[^6] | 30.0 KiB | | | 32 | | ✔️ | ✔️ | ✔️ | ✔️ | via `zig cc`[^7] | 6.1 KiB | | | 33 | 34 | [^1]: Perform a release build: `-d:release` (the default is a debug build) 35 | [^2]: Enable Link-Time Optimization: `--passC:-flto --passL:-flto` 36 | [^3]: Remove debug symbols: `--passL:-s` 37 | [^4]: Optimize for reduced binary size: `--opt:size` (the default is `--opt:speed`) 38 | [^5]: Add `--cc:gcc --gcc.exe:musl-gcc --gcc.linkerexe:musl-gcc --passL:-static` 39 | [^6]: Add `--cc:clang --clang.exe:musl-clang --clang.linkerexe:musl-clang --passL:-static` 40 | [^7]: Add `--panics:on -d:useMalloc --os:any -d:posix -d:noSignalHandler --cc=clang --clang.exe='zigcc' --clang.linkerexe='zigcc' --passC:'-target x86_64-linux-musl' --passL:'-target x86_64-linux-musl'` 41 | 42 | ### Details 43 | 44 | All results from 2023-03-31 on x86_64 with Nim 2.0 nightly release 2023-03-30 (corresponding to a Nim compiler built from commit [nim-lang/Nim@`2e4ba4a`](https://github.com/nim-lang/Nim/commit/2e4ba4ad93c6d9021b6de975cf7ac78e67acba26)). 45 | 46 | #### Linux 47 | 48 | - Arch Linux 49 | - glibc 2.37 50 | - gcc 12.2.1 51 | - clang 15.0.7 52 | - musl 1.2.3 53 | - zig 0.10.1 54 | 55 | #### macOS 56 | 57 | - macOS 12 58 | - clang 14.0.0 59 | 60 | #### Windows 61 | 62 | - Windows Server 2022 63 | - Mingw-w64 11.2.0 64 | -------------------------------------------------------------------------------- /build.nim: -------------------------------------------------------------------------------- 1 | import std/[os, osproc, strformat, strutils] 2 | 3 | type 4 | BuildKind = enum 5 | bkDebug = "" 6 | bkRelease = "release" 7 | bkDanger = "danger" 8 | 9 | Mm = enum 10 | mmOrc = "orc" 11 | mmArc = "arc" 12 | mmRefc = "refc" 13 | 14 | Opt = enum 15 | optSpeed = "speed" 16 | optSize = "size" 17 | 18 | LinkingKind = enum 19 | lkDynamic 20 | lkStaticMuslGcc 21 | lkStaticMuslClang 22 | lkStaticMuslZig 23 | 24 | CompileOptions = object 25 | buildKind: BuildKind 26 | flto: bool 27 | strip: bool 28 | mm: Mm 29 | opt: Opt 30 | linkingKind: LinkingKind 31 | 32 | func init(T: typedesc[CompileOptions], 33 | buildKind = bkDebug, 34 | flto = false, 35 | strip = false, 36 | mm = mmOrc, 37 | opt = optSpeed, 38 | linkingKind = lkDynamic): T = 39 | T( 40 | buildKind: buildKind, 41 | flto: flto, 42 | strip: strip, 43 | mm: mm, 44 | opt: opt, 45 | linkingKind: linkingKind, 46 | ) 47 | 48 | when defined(linux): 49 | proc warn(msg: string) = 50 | stderr.write "Warning: " 51 | stderr.writeLine msg 52 | 53 | proc getCompilationOptions: seq[CompileOptions] = 54 | result = @[ 55 | CompileOptions.init(), 56 | CompileOptions.init(bkRelease), 57 | CompileOptions.init(bkRelease, flto = true), 58 | CompileOptions.init(bkRelease, flto = true, strip = true), 59 | CompileOptions.init(bkRelease, flto = true, strip = true, opt = optSize), 60 | ] 61 | 62 | when defined(linux): 63 | if findExe("musl-gcc").len > 0: 64 | result.add CompileOptions.init(bkRelease, flto = true, strip = true, 65 | opt = optSize, 66 | linkingKind = lkStaticMuslGcc) 67 | else: 68 | warn("musl-gcc not found") 69 | 70 | if findExe("musl-clang").len > 0: 71 | result.add CompileOptions.init(bkRelease, flto = true, strip = true, 72 | opt = optSize, 73 | linkingKind = lkStaticMuslClang) 74 | else: 75 | warn("musl-clang not found") 76 | 77 | if findExe("zig").len > 0: 78 | result.add CompileOptions.init(bkRelease, opt = optSize, 79 | linkingKind = lkStaticMuslZig) 80 | else: 81 | warn("zig not found") 82 | 83 | func `$`(c: CompileOptions): string = 84 | result = if c.buildKind == bkDebug: "" else: &"-d:{c.buildKind}" 85 | if c.flto: 86 | result.add " --passC:-flto --passL:-flto" 87 | if c.strip: 88 | result.add " --passL:-s" 89 | if c.mm != mmOrc: 90 | result.add &" --mm:{c.mm}" 91 | if c.opt == optSize: 92 | result.add &" --opt:{c.opt}" 93 | case c.linkingKind 94 | of lkDynamic: 95 | discard 96 | of lkStaticMuslGcc: 97 | result.add " --cc:gcc --gcc.exe:musl-gcc --gcc.linkerexe:musl-gcc --passL:-static" 98 | of lkStaticMuslClang: 99 | result.add " --cc:clang --clang.exe:musl-clang --clang.linkerexe:musl-clang --passL:-static" 100 | of lkStaticMuslZig: 101 | const pathZigcc = currentSourcePath().parentDir() / "zigcc" 102 | result.add " --panics:on -d:useMalloc --os:any -d:posix -d:noSignalHandler" & 103 | &" --cc=clang --clang.exe='{pathZigcc}' --clang.linkerexe='{pathZigcc}'" & 104 | " --passC:'-flto -target x86_64-linux-musl'" & 105 | " --passL:'-flto -target x86_64-linux-musl'" 106 | 107 | proc execAndCheck(cmd: string) = 108 | ## Runs `cmd`, and raises an exception if the exit code is non-zero. 109 | let (output, exitCode) = execCmdEx(cmd) 110 | if exitCode != 0: 111 | stderr.writeLine output 112 | raise newException(OSError, "Command returned non-zero exit code: " & cmd) 113 | 114 | proc main = 115 | const filename = "hello" 116 | const binaryPath = when defined(windows): &"{filename}.exe" else: filename 117 | let options = getCompilationOptions() 118 | for opts in options: 119 | let s = $opts 120 | let cmd = fmt"nim c --skipParentCfg --skipProjCfg {s} {filename}" 121 | execAndCheck(cmd) 122 | 123 | # strip the zigcc binary, where `--passL:-s` doesn't work. 124 | if opts.linkingKind == lkStaticMuslZig: 125 | execAndCheck(&"strip -s -R .comment {filename}") 126 | 127 | let binarySize = getFileSize(binaryPath).float / 1024 128 | echo &"{binarySize:>5.1f} KiB {s}" 129 | let cmdRunHello = when defined(windows): binaryPath else: &"./{binaryPath}" 130 | doAssert execCmdEx(cmdRunHello) == ("Hello, World!\n", 0) 131 | 132 | when isMainModule: 133 | main() 134 | -------------------------------------------------------------------------------- /hello.nim: -------------------------------------------------------------------------------- 1 | echo "Hello, World!" 2 | -------------------------------------------------------------------------------- /zigcc: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | zig cc $@ 3 | --------------------------------------------------------------------------------