SwiftLintのStatic Linux SDK版を修正した話
バグ修正
SwiftLintをStatic Linux SDK(musl libc)でビルドしたバイナリが、v0.61.0から実験的に公開されていたので使おうとしたところ、実行時にクラッシュする問題に遭遇した。Segmentation faultで落ちる。
調べていくと、musl libcのスレッドスタックサイズが128KiBと非常に小さいことが原因であるとわかった。SwiftLintはマルチスレッドで処理を並列化しており、その中で深いスタックを使うコードがあったため、muslの制限に引っかかっていたわけだ。特にソースコードをパースしてASTを作るSwiftSyntaxを利用しているSwiftLintだとその問題が顕著になる。
そのため、リンク時にスレッドスタックサイズを拡張するオプションを付けるよう修正した。-Xlinker -z -Xlinker stack-size=0x80000 を指定し、512KiB確保するようにした。この変更をまとめたのが PR #6291 である。これでクラッシュは解消した(Issue #6287)。512KiBというのはDarwinの環境ことmacOS上でのデフォルトのstack sizeなので、これが妥当だと判断した。
ところが今度は、クラッシュは直ったものの動作が異様に遅いというフィードバックがメンテナーからあった。確かに自分で試してみると、Static Linux SDK版のバイナリを実行すると、通常ビルド版の3倍くらい時間がかかっている。原因を掘り下げたところ、muslのmalloc実装がパフォーマンスボトルネックになっていることが分かった。マルチスレッド環境下ではfutexベースのロックで詰まりが発生しやすく、特に並列Lint処理で顕著に影響していた。
そこで、静的リンクビルド時に標準のmallocを置き換え、microsoft/mimallocを使うようにした。これが PR #6321 である。 libmimalloc.aを静的リンクし、ライセンスファイルも同梱。結果としてStatic Linux SDK版でもmacOS版と同等のパフォーマンスが得られるようになった(Issue #6298)。
今回の対応で、SwiftLintはmuslベースのStatic Linux SDKでも安定して動作し、かつ実用的な速度を保てるようになった。 実際の作業を通じて、静的リンク環境では標準Cライブラリ周りの制限が想定外に効いてくること、またSwiftのビルド環境でも低レイヤのチューニングが必要になることを改めて実感した。
ちなみに、GitHub Releaseのページからダウンロード出来て、AMD64 Linux Binary (x86_64) か ARM64 Linux Binary のzipをダウンロードするとswiftlint-static というバイナリが入っていてこれがSwiftのランタイムなしで即実行できる。
https://github.com/realm/SwiftLint/releases/tag/0.62.2
環境構築
これらの修正を行うのはMacBook Proしか持ってない自分は大変で、VMWare Fusion経由でUbuntu 24.04を導入してSwiftやLLDBの実行環境を整える作業が必要だった。
- パフォーマンスを調べるためのperfコマンドを実行するためにカーネルのバージョンとツールのバージョンを揃えたりする必要があったが存在してなかった
- Ubuntu 25.04以降じゃないとUbuntu DesktopのARM向けイメージが正式リリースでは配布してなくてUbuntu 24.04 LTSのDesktop版はdaily build版を使った
ARM対応がmacOSと比べるとまだ手薄感が感じられたけど、今回は何とかなったのでARM対応がある程度進んでいるのかも。
参考リンク
- https://www.swift.org/documentation/server/guides/linux-perf.html
- https://cdimage.ubuntu.com/noble/daily-live/current/
Static Linux SDK版SwiftLintの使い道
Static Linux SDKでビルドされているということは、Linux向けにバイナリをダウンロードしたらそのまま実行できるセットアップの楽さが一番の売りだと思う。CIをメンテしていると辛いがちなのが、最初の実行環境の構築とビルド時間の増加とそれを緩和するためのキャッシュのメンテナンスで、最初からビルド済みならダウンロードすればもう終わりというのが最高だと思う。
唯一の欠点のバージョン管理・アップデートの問題も、バイナリをラップしたGitHub Actionsを用意していて、SwiftLintのリリースされるバージョンに合わせてこのGitHub Actionsのリリースをすることで解決できるようにしている。そうすると利用側もRenovateなどで簡単に更新できて便利。
https://github.com/ainame/swiftlint-linux
実際に使っている箇所ではubuntu-slimで15秒ほどで完了できるのでかなり速いフィードバックが得られる。
https://github.com/ainame/tuzuru/actions/runs/19063668512/job/54448987547
まとめ
Static Linux SDK、便利なのでみんなも使おう!!iOS開発者でもツールの運用などで利用できる。 試してないけどSwiftFormatも確かSwfit Linux SDKでビルドされたバイナリが配布されていたような気がするので試してみたい。
(この記事の前半はGitHub部分はリンクと簡単なプロンプトでAIに書いてもらいました)