Designing a Linker Argument Parser


秋山馨(lapla)

https://slide.lapla.dev/external/
labsyouth/linkerarg(.pdf)
Designing a Linker Argument Parser @ サイボウズ・ラボユース夏合宿2025 LT
whoami
Designing a Linker Argument Parser @ サイボウズ・ラボユース夏合宿2025 LT
概要
  • GSoCで開発に関わっているリンカー(Wild)で先日引数パーサーを根本的に再実装した
  • リンカーの引数パースという場面での特殊性みたいなやつがあり,既存のパーサーを使わないで実装する判断をした
  • "凝った引数パーサーを自分で書く"というのは案外特殊な経験な気がしたので,なぜそのような判断をしたのかなどについて話します
Designing a Linker Argument Parser @ サイボウズ・ラボユース夏合宿2025 LT

GSoC

Designing a Linker Argument Parser @ サイボウズ・ラボユース夏合宿2025 LT
Google Summer of Code
  • Googleが主催する,若者(~25歳)のOSS貢献を促進するプログラム
  • 今年Rust Foundationで採択されている

center

Designing a Linker Argument Parser @ サイボウズ・ラボユース夏合宿2025 LT
GSoCでやっていること

Rust製の高速な1^1Linux向けリンカーwildの様々な角度からの改善
→ リンカーの開発における低いレイヤー2^2の話をしても良いんですが,今回はそこから少し離れた引数パーサーの話をします

1: 高速とされているRui Ueyamaさんのmoldより大体1.5~2倍くらい速い(参考:ベンチマーク
2: 夏合宿ではGDB indexのサポートを追加しようとしています

Designing a Linker Argument Parser @ サイボウズ・ラボユース夏合宿2025 LT

これまでのwildの
引数パーサーの課題

Designing a Linker Argument Parser @ サイボウズ・ラボユース夏合宿2025 LT
wildの"目的"

リンクをできるだけ高速にやりたい


→ リンクにあたっての各ステップでのオーバーヘッドを極力減らしたい


→ リンカーに渡されるオプションもできるだけ高速にパースしたい

Designing a Linker Argument Parser @ サイボウズ・ラボユース夏合宿2025 LT
これまでの引数パースの様子(概観)
while let Some(arg) = input.next() {
    if long_arg_eq("static") || long_arg_eq("Bstatic") {
        // do something
    } else if long_arg_eq("Bdynamic") {
        // do something
    } else if arg == "-o" {
        // do something
    }
    ...
}
  • whileで全部の引数を舐めてif-elseで全部処理
  • 確かに速いだろうけどオプションは統一的に管理されない
Designing a Linker Argument Parser @ サイボウズ・ラボユース夏合宿2025 LT
結果:--helpが無い
(▰╹◡╹)❯  wild --help
Error: Sorry, help isn't implemented yet

wild: supported targets: elf64-x86-64 elf64-littleaarch64 elf64-littleriscv
wild: supported emulations: elf_x86_64 aarch64elf elf64lriscv
  • さすがにロックすぎる!
  • 利用者側も困るだろうし,開発時にも普通に困る
    • どこまでオプションが実装されているか都度if-elseの山から探す必要がある
      • 効果は同じだが別名のものもあるかもしれない
    • Wild特有のオプションについて見たいときはコード上で動作を追う必要がある
  • これがGitHubへの公開当初(約1.5年前)からずっと続いていた

→ 引数パーサーを再実装したい!

Designing a Linker Argument Parser @ サイボウズ・ラボユース夏合宿2025 LT

リンカーの引数パーサーとしての
要件を考える

Designing a Linker Argument Parser @ サイボウズ・ラボユース夏合宿2025 LT
求められる要件
  • 既存のリンカー(GNU ld等)との整合性を保ちたい(!!)
  • オプションを定義するときにはできるだけ統一されたインターフェースで書けて,内部で自然に様々な形式がパースされるようにしたい
  • --helpも実装したいのでオプションを統一的に管理したい
  • できるだけ高速にパースしたい
Designing a Linker Argument Parser @ サイボウズ・ラボユース夏合宿2025 LT
既存のリンカーとの整合性を保つ

次の条件を全部パースできるようにする必要がある

  • 短いオプションと長いオプションが混在(ex: -s--strip-allは同じ意味)
  • 長いオプションはハイフンが1つでも指定できる(ex: -strip-allはOK)
  • 値を伴うオプションは,値がスペース区切りで渡されたり=で渡されたりする
    (ex: -o foo-o=fooはどちらもOK)
  • 値を伴ったり伴わなかったりするオプションもある
    (ex: --threads ${NUM}でスレッド数を指定できるが--threadsだとリンカー側でスレッド数を決める)
  • prefixが共通するオプション群がある(ex: -z now, -z defs, -z execstack, ...)
  • 副作用を伴うオプションも存在する(ex: --push-state, --pop-state
Designing a Linker Argument Parser @ サイボウズ・ラボユース夏合宿2025 LT
検討1:clap
  • RustでCLIならほぼ必ず視野に入るはず
  • 👍 機能が豊富
  • 😫 (比較的)重い,ハイフン1つでの長いオプションが未サポート1^1,副作用起こせない

1: alias機能を使う回避策はあるがややad-hoc

Designing a Linker Argument Parser @ サイボウズ・ラボユース夏合宿2025 LT
検討2:bpaf
  • 👍 (比較的)軽い
  • 😫 副作用を素直には起こせない,安定性
Designing a Linker Argument Parser @ サイボウズ・ラボユース夏合宿2025 LT
設計する 〜他モジュールとのインターフェース〜
  • 取り回しを良くするために大きな構造体を1つ用意する
    • 引数がパースされたらこの(インスタンスの)中の値が適切に変化する
    • この構造体は他のモジュールからも参照される
pub struct Args {
    pub(crate) unrecognized_options: Vec<String>,

    pub(crate) arch: Architecture,
    pub(crate) lib_search_path: Vec<Box<Path>>,
    pub(crate) inputs: Vec<Input>,
    pub(crate) output: Arc<Path>,
    pub(crate) dynamic_linker: Option<Box<Path>>,
    ...
}
Designing a Linker Argument Parser @ サイボウズ・ラボユース夏合宿2025 LT
設計する 〜宣言インターフェースの統一〜
  • オプションを宣言するときには次のような関数型のようなインターフェースを持つ
    • 値を取るようなものはdeclare_with_param(),そうでないならdeclare()を呼ぶ
    • long()short()help()等でメタデータを設定
    • execute()でコールバックをクロージャーとして登録
    • prefixを持つようなものはそれを前段でチェインさせてから同様の設定
parser
    .declare_with_param()
    .long("output")
    .short("o")
    .help("Set the output filename")
    .execute(|args, _modifier_stack, value| {
        args.output = Arc::from(Path::new(value));
        Ok(())
    });
Designing a Linker Argument Parser @ サイボウズ・ラボユース夏合宿2025 LT
オプション宣言の統一
struct OptionDeclaration<'a, T> {
    parser: &'a mut ArgumentParser,
    long_names: Vec<&'static str>,
    short_names: Vec<&'static str>,
    help_text: &'static str,
    _phantom: std::marker::PhantomData<T>,
}

みたいに核となる構造体を持って

struct NoParam;
struct WithParam;
struct WithOptionalParam;

を型パラメーターにしてそれぞれの処理をimplする

→ パラメーターの有無によらず統一したインターフェースでオプションを宣言可能

Designing a Linker Argument Parser @ サイボウズ・ラボユース夏合宿2025 LT
ヘルプメッセージの遅延解決

余計なオーバーヘッドは載せたくないので各オプションのヘルプメッセージは--helpが指定されて初めて収集されるようにしたい

→ 宣言時に登録されたメタデータを--helpが指定されてから収集して,同じ効果のオプションをまとめたり,表示順をソートしてヘルプの文字列を組み立てる遅延評価を実装

Designing a Linker Argument Parser @ サイボウズ・ラボユース夏合宿2025 LT
実装する

ギャッとやるとできる

Designing a Linker Argument Parser @ サイボウズ・ラボユース夏合宿2025 LT
--helpが出る
(▰╹◡╹)❯  ./target/debug/wild --help
USAGE:
    wild [OPTIONS] [FILES...]

OPTIONS:
    @<VALUE>                        Read options from a file
    -L <VALUE>                      Add directory to library search path
    -R <VALUE>                      Add runtime library search path
    -l <VALUE>                      Link with library
      -l :filename                      Link with specific file
      -l libname                        Link with library libname.so or libname.a
    -m <VALUE>                      Set target architecture
      -m aarch64elf                     AArch64 ELF target
      -m elf64lriscv                    RISC-V 64-bit ELF target
      -m elf_x86_64                     x86-64 ELF target
    -u <VALUE>                      Force resolution of the symbol
...

嬉しい~

Designing a Linker Argument Parser @ サイボウズ・ラボユース夏合宿2025 LT
速度も落としていない

めちゃくちゃ大量の引数を与えて正しくパースできるかのテストで雑に計測

before:

(▰╹◡╹)❯  hyperfine -N -p "cargo build --test integration_tests" "cargo test args::tests::test_parse"
Benchmark 1: cargo test args::tests::test_parse
  Time (mean ± σ):     100.7 ms ±   3.9 ms    [User: 63.6 ms, System: 34.0 ms]
  Range (min … max):    95.9 ms … 109.5 ms    16 runs

after:

(▰╹◡╹)❯  hyperfine -N -p "cargo build --test integration_tests" "cargo test args::tests::test_parse"
Benchmark 1: cargo test args::tests::test_parse
  Time (mean ± σ):     103.7 ms ±   5.3 ms    [User: 65.2 ms, System: 40.3 ms]
  Range (min … max):    96.0 ms … 112.6 ms    14 runs

嬉しい~2

Designing a Linker Argument Parser @ サイボウズ・ラボユース夏合宿2025 LT

END